aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/acl.erl36
-rw-r--r--src/adhoc.erl12
-rw-r--r--src/cyrsasl.erl6
-rw-r--r--src/cyrsasl_anonymous.erl5
-rw-r--r--src/cyrsasl_digest.erl14
-rw-r--r--src/cyrsasl_plain.erl13
-rw-r--r--src/cyrsasl_scram.erl3
-rw-r--r--src/ejabberd.erl2
-rw-r--r--src/ejabberd_admin.erl21
-rw-r--r--src/ejabberd_app.erl51
-rw-r--r--src/ejabberd_auth.erl40
-rw-r--r--src/ejabberd_auth_anonymous.erl12
-rw-r--r--src/ejabberd_auth_external.erl54
-rw-r--r--src/ejabberd_auth_internal.erl28
-rw-r--r--src/ejabberd_auth_ldap.erl12
-rw-r--r--src/ejabberd_auth_pam.erl14
-rw-r--r--src/ejabberd_auth_riak.erl28
-rw-r--r--src/ejabberd_auth_sql.erl (renamed from src/ejabberd_auth_odbc.erl)239
-rw-r--r--src/ejabberd_c2s.erl389
-rw-r--r--src/ejabberd_captcha.erl2
-rw-r--r--src/ejabberd_commands.erl320
-rw-r--r--src/ejabberd_commands_doc.erl11
-rw-r--r--src/ejabberd_config.erl128
-rw-r--r--src/ejabberd_ctl.erl146
-rw-r--r--src/ejabberd_frontend_socket.erl16
-rw-r--r--src/ejabberd_http.erl13
-rw-r--r--src/ejabberd_http_bind.erl52
-rw-r--r--src/ejabberd_http_ws.erl20
-rw-r--r--src/ejabberd_local.erl17
-rw-r--r--src/ejabberd_logger.erl56
-rw-r--r--src/ejabberd_oauth.erl10
-rw-r--r--src/ejabberd_piefxis.erl115
-rw-r--r--src/ejabberd_rdbms.erl34
-rw-r--r--src/ejabberd_receiver.erl30
-rw-r--r--src/ejabberd_riak.erl25
-rw-r--r--src/ejabberd_riak_sup.erl43
-rw-r--r--src/ejabberd_router.erl111
-rw-r--r--src/ejabberd_s2s.erl10
-rw-r--r--src/ejabberd_s2s_in.erl46
-rw-r--r--src/ejabberd_s2s_out.erl66
-rw-r--r--src/ejabberd_service.erl30
-rw-r--r--src/ejabberd_sm.erl127
-rw-r--r--src/ejabberd_sm_redis.erl4
-rw-r--r--src/ejabberd_sm_sql.erl (renamed from src/ejabberd_sm_odbc.erl)38
-rw-r--r--src/ejabberd_socket.erl16
-rw-r--r--src/ejabberd_sql.erl (renamed from src/ejabberd_odbc.erl)320
-rw-r--r--src/ejabberd_sql_pt.erl544
-rw-r--r--src/ejabberd_sql_sup.erl (renamed from src/ejabberd_odbc_sup.erl)44
-rw-r--r--src/ejabberd_stun.erl4
-rw-r--r--src/ejabberd_system_monitor.erl12
-rw-r--r--src/ejabberd_web_admin.erl47
-rw-r--r--src/ejabberd_websocket.erl6
-rw-r--r--src/ejabberd_xmlrpc.erl8
-rw-r--r--src/ejd2sql.erl (renamed from src/ejd2odbc.erl)53
-rw-r--r--src/elixir_logger_backend.erl122
-rw-r--r--src/ext_mod.erl9
-rw-r--r--src/gen_mod.erl72
-rw-r--r--src/jd2ejd.erl14
-rw-r--r--src/jid.erl10
-rw-r--r--src/jlib.erl54
-rw-r--r--src/mod_adhoc.erl9
-rw-r--r--src/mod_admin_extra.erl204
-rw-r--r--src/mod_announce.erl437
-rw-r--r--src/mod_announce_mnesia.erl129
-rw-r--r--src/mod_announce_riak.erl87
-rw-r--r--src/mod_announce_sql.erl132
-rw-r--r--src/mod_blocking.erl277
-rw-r--r--src/mod_blocking_mnesia.erl85
-rw-r--r--src/mod_blocking_riak.erl98
-rw-r--r--src/mod_blocking_sql.erl87
-rw-r--r--src/mod_caps.erl184
-rw-r--r--src/mod_caps_mnesia.erl73
-rw-r--r--src/mod_caps_riak.erl38
-rw-r--r--src/mod_caps_sql.erl71
-rw-r--r--src/mod_carboncopy.erl71
-rw-r--r--src/mod_carboncopy_mnesia.erl68
-rw-r--r--src/mod_client_state.erl2
-rw-r--r--src/mod_configure.erl387
-rw-r--r--src/mod_configure2.erl31
-rw-r--r--src/mod_disco.erl52
-rw-r--r--src/mod_echo.erl9
-rw-r--r--src/mod_http_api.erl294
-rw-r--r--src/mod_http_upload.erl87
-rw-r--r--src/mod_http_upload_quota.erl5
-rw-r--r--src/mod_irc.erl257
-rw-r--r--src/mod_irc_connection.erl36
-rw-r--r--src/mod_irc_mnesia.erl69
-rw-r--r--src/mod_irc_riak.erl49
-rw-r--r--src/mod_irc_sql.erl91
-rw-r--r--src/mod_last.erl185
-rw-r--r--src/mod_last_mnesia.erl72
-rw-r--r--src/mod_last_riak.erl53
-rw-r--r--src/mod_last_sql.erl75
-rw-r--r--src/mod_mam.erl693
-rw-r--r--src/mod_mam_mnesia.erl178
-rw-r--r--src/mod_mam_sql.erl309
-rw-r--r--src/mod_metrics.erl5
-rw-r--r--src/mod_mix.erl348
-rw-r--r--src/mod_muc.erl519
-rw-r--r--src/mod_muc_admin.erl74
-rw-r--r--src/mod_muc_log.erl16
-rw-r--r--src/mod_muc_mnesia.erl163
-rw-r--r--src/mod_muc_riak.erl117
-rw-r--r--src/mod_muc_room.erl391
-rw-r--r--src/mod_muc_sql.erl202
-rw-r--r--src/mod_multicast.erl53
-rw-r--r--src/mod_offline.erl1061
-rw-r--r--src/mod_offline_mnesia.erl232
-rw-r--r--src/mod_offline_riak.erl153
-rw-r--r--src/mod_offline_sql.erl252
-rw-r--r--src/mod_ping.erl5
-rw-r--r--src/mod_pres_counter.erl2
-rw-r--r--src/mod_privacy.erl834
-rw-r--r--src/mod_privacy_mnesia.erl198
-rw-r--r--src/mod_privacy_riak.erl160
-rw-r--r--src/mod_privacy_sql.erl397
-rw-r--r--src/mod_private.erl254
-rw-r--r--src/mod_private_mnesia.erl97
-rw-r--r--src/mod_private_riak.erl67
-rw-r--r--src/mod_private_sql.erl97
-rw-r--r--src/mod_proxy65_service.erl30
-rw-r--r--src/mod_proxy65_stream.erl2
-rw-r--r--src/mod_pubsub.erl353
-rw-r--r--src/mod_register.erl75
-rw-r--r--src/mod_register_web.erl34
-rw-r--r--src/mod_roster.erl708
-rw-r--r--src/mod_roster_mnesia.erl171
-rw-r--r--src/mod_roster_riak.erl113
-rw-r--r--src/mod_roster_sql.erl308
-rw-r--r--src/mod_shared_roster.erl508
-rw-r--r--src/mod_shared_roster_ldap.erl120
-rw-r--r--src/mod_shared_roster_mnesia.erl167
-rw-r--r--src/mod_shared_roster_riak.erl139
-rw-r--r--src/mod_shared_roster_sql.erl212
-rw-r--r--src/mod_sic.erl21
-rw-r--r--src/mod_stats.erl28
-rw-r--r--src/mod_time.erl5
-rw-r--r--src/mod_vcard.erl776
-rw-r--r--src/mod_vcard_ldap.erl32
-rw-r--r--src/mod_vcard_mnesia.erl213
-rw-r--r--src/mod_vcard_riak.erl151
-rw-r--r--src/mod_vcard_sql.erl268
-rw-r--r--src/mod_vcard_xupdate.erl149
-rw-r--r--src/mod_vcard_xupdate_mnesia.erl69
-rw-r--r--src/mod_vcard_xupdate_riak.erl44
-rw-r--r--src/mod_vcard_xupdate_sql.erl79
-rw-r--r--src/mod_version.erl5
-rw-r--r--src/node_buddy.erl3
-rw-r--r--src/node_club.erl3
-rw-r--r--src/node_dag.erl3
-rw-r--r--src/node_dispatch.erl3
-rw-r--r--src/node_flat.erl3
-rw-r--r--src/node_flat_sql.erl (renamed from src/node_flat_odbc.erl)154
-rw-r--r--src/node_hometree_sql.erl (renamed from src/node_hometree_odbc.erl)66
-rw-r--r--src/node_mb.erl3
-rw-r--r--src/node_mix.erl168
-rw-r--r--src/node_mix_sql.erl171
-rw-r--r--src/node_online.erl3
-rw-r--r--src/node_pep.erl5
-rw-r--r--src/node_pep_sql.erl (renamed from src/node_pep_odbc.erl)98
-rw-r--r--src/node_private.erl3
-rw-r--r--src/node_public.erl3
-rw-r--r--src/nodetree_dag.erl12
-rw-r--r--src/nodetree_tree.erl14
-rw-r--r--src/nodetree_tree_sql.erl (renamed from src/nodetree_tree_odbc.erl)87
-rw-r--r--src/odbc_queries.erl659
-rw-r--r--src/prosody2ejabberd.erl341
-rw-r--r--src/pubsub_db_sql.erl (renamed from src/pubsub_db_odbc.erl)90
-rw-r--r--src/pubsub_subscription.erl35
-rw-r--r--src/pubsub_subscription_sql.erl (renamed from src/pubsub_subscription_odbc.erl)41
-rw-r--r--src/sql_queries.erl644
171 files changed, 13207 insertions, 8650 deletions
diff --git a/src/acl.erl b/src/acl.erl
index fdf397d88..06202c67e 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -31,9 +31,11 @@
-export([start/0, to_record/3, add/3, add_list/3,
add_local/3, add_list_local/3, load_from_config/0,
- match_rule/3, match_acl/3, transform_options/1,
+ match_rule/3, match_access/4, match_acl/3, transform_options/1,
opt_type/1]).
+-export([add_access/3, clear/0]).
+
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
@@ -43,6 +45,7 @@
rules = [] :: [access_rule()]}).
-type regexp() :: binary().
+-type iprange() :: {inet:ip_address(), integer()} | binary().
-type glob() :: binary().
-type access_name() :: atom().
-type access_rule() :: {atom(), any()}.
@@ -61,7 +64,7 @@
{user_glob, {glob(), host()} | glob()} |
{server_glob, glob()} |
{resource_glob, glob()} |
- {ip, {inet:ip_address(), integer()}} |
+ {ip, iprange()} |
{node_glob, {glob(), glob()}}.
-type acl() :: #acl{aclname :: aclname(),
@@ -204,6 +207,12 @@ load_from_config() ->
end, AccessRules)
end, Hosts).
+%% Delete all previous set ACLs and Access rules
+clear() ->
+ mnesia:clear_table(acl),
+ mnesia:clear_table(access),
+ ok.
+
b(S) ->
iolist_to_binary(S).
@@ -246,6 +255,19 @@ normalize_spec(Spec) ->
end
end.
+-spec match_access(global | binary(), access_name(),
+ jid() | ljid() | inet:ip_address(),
+ atom()) -> any().
+
+match_access(_Host, all, _JID, _Default) ->
+ allow;
+match_access(_Host, none, _JID, _Default) ->
+ deny;
+match_access(_Host, {user, UserPattern}, JID, Default) ->
+ match_user_spec({user, UserPattern}, JID, Default);
+match_access(Host, AccessRule, JID, _Default) ->
+ match_rule(Host, AccessRule, JID).
+
-spec match_rule(global | binary(), access_name(),
jid() | ljid() | inet:ip_address()) -> any().
@@ -348,6 +370,16 @@ match_acl(ACL, JID, Host) ->
get_aclspecs(ACL, Host) ->
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
+
+match_user_spec(Spec, JID, Default) ->
+ case do_match_user_spec(Spec, jid:tolower(JID)) of
+ true -> Default;
+ false -> deny
+ end.
+
+do_match_user_spec({user, {U, S}}, {User, Server, _Resource}) ->
+ U == User andalso S == Server.
+
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
nomatch -> false;
diff --git a/src/adhoc.erl b/src/adhoc.erl
index d252a6cbb..83a113a0c 100644
--- a/src/adhoc.erl
+++ b/src/adhoc.erl
@@ -51,9 +51,9 @@
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
?DEBUG("entering parse_request...", []),
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
- SessionID = xml:get_tag_attr_s(<<"sessionid">>, SubEl),
- Action = xml:get_tag_attr_s(<<"action">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
+ SessionID = fxml:get_tag_attr_s(<<"sessionid">>, SubEl),
+ Action = fxml:get_tag_attr_s(<<"action">>, SubEl),
XData = find_xdata_el(SubEl),
#xmlel{children = AllEls} = SubEl,
Others = case XData of
@@ -68,7 +68,9 @@ parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}
xdata = XData,
others = Others
};
-parse_request(_) -> {error, ?ERR_BAD_REQUEST}.
+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}) ->
@@ -76,7 +78,7 @@ find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1([]) -> false;
find_xdata_el1([El | Els]) when is_record(El, xmlel) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
?NS_XDATA -> El;
_ -> find_xdata_el1(Els)
end;
diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl
index 5d2d89d67..21fbc9660 100644
--- a/src/cyrsasl.erl
+++ b/src/cyrsasl.erl
@@ -111,12 +111,12 @@ register_mechanism(Mechanism, Module, PasswordType) ->
%%-include("ejabberd.hrl").
%%-include("jlib.hrl").
%%check_authzid(_State, Props) ->
-%% AuthzId = xml:get_attr_s(authzid, Props),
+%% AuthzId = fxml:get_attr_s(authzid, Props),
%% case jid:from_string(AuthzId) of
%% error ->
%% {error, "invalid-authzid"};
%% JID ->
-%% LUser = jid:nodeprep(xml:get_attr_s(username, Props)),
+%% LUser = jid:nodeprep(fxml:get_attr_s(username, Props)),
%% {U, S, R} = jid:tolower(JID),
%% case R of
%% "" ->
@@ -132,7 +132,7 @@ register_mechanism(Mechanism, Module, PasswordType) ->
%% end.
check_credentials(_State, Props) ->
- User = proplists:get_value(username, Props, <<>>),
+ User = proplists:get_value(authzid, Props, <<>>),
case jid:nodeprep(User) of
error -> {error, <<"not-authorized">>};
<<"">> -> {error, <<"not-authorized">>};
diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl
index 2b2a9f63c..802e1cd7b 100644
--- a/src/cyrsasl_anonymous.erl
+++ b/src/cyrsasl_anonymous.erl
@@ -45,9 +45,8 @@ mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
mech_step(#state{server = Server} = S, ClientIn) ->
User = iolist_to_binary([randoms:get_string(),
- randoms:get_string(),
- randoms:get_string()]),
+ jlib: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}, {auth_module, ejabberd_auth_anonymous}]}
+ false -> {ok, [{username, User}, {authzid, User}, {auth_module, ejabberd_auth_anonymous}]}
end.
diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl
index 8ccd90952..e58cb3035 100644
--- a/src/cyrsasl_digest.erl
+++ b/src/cyrsasl_digest.erl
@@ -50,7 +50,7 @@
username = <<"">> :: binary(),
authzid = <<"">> :: binary(),
get_password = fun(_) -> {false, <<>>} end :: get_password_fun(),
- check_password = fun(_, _, _, _) -> false end :: check_password_fun(),
+ check_password = fun(_, _, _, _, _) -> false end :: check_password_fun(),
auth_module :: atom(),
host = <<"">> :: binary(),
hostfqdn = <<"">> :: binary()}).
@@ -83,9 +83,7 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
bad -> {error, <<"bad-protocol">>};
KeyVals ->
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
- %DigestURI = xml:get_attr_s(<<"digest-uri">>, KeyVals),
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
- %UserName = xml:get_attr_s(<<"username">>, KeyVals),
case is_digesturi_valid(DigestURI, State#state.host,
State#state.hostfqdn)
of
@@ -97,13 +95,11 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
{error, <<"not-authorized">>, UserName};
true ->
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
- %AuthzId = xml:get_attr_s(<<"authzid">>, KeyVals),
case (State#state.get_password)(UserName) of
{false, _} -> {error, <<"not-authorized">>, UserName};
{Passwd, AuthModule} ->
- case (State#state.check_password)(UserName, <<"">>,
+ case (State#state.check_password)(UserName, UserName, <<"">>,
proplists:get_value(<<"response">>, KeyVals, <<>>),
- %xml:get_attr_s(<<"response">>, KeyVals),
fun (PW) ->
response(KeyVals,
UserName,
@@ -130,7 +126,11 @@ mech_step(#state{step = 5, auth_module = AuthModule,
username = UserName, authzid = AuthzId},
<<"">>) ->
{ok,
- [{username, UserName}, {authzid, AuthzId},
+ [{username, UserName}, {authzid, case AuthzId of
+ <<"">> -> UserName;
+ _ -> AuthzId
+ end
+ },
{auth_module, AuthModule}]};
mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl
index a9c1de056..82d68f87f 100644
--- a/src/cyrsasl_plain.erl
+++ b/src/cyrsasl_plain.erl
@@ -45,7 +45,7 @@ mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
mech_step(State, ClientIn) ->
case prepare(ClientIn) of
[AuthzId, User, Password] ->
- case (State#state.check_password)(User, Password) of
+ case (State#state.check_password)(User, AuthzId, Password) of
{true, AuthModule} ->
{ok,
[{username, User}, {authzid, AuthzId},
@@ -60,12 +60,17 @@ prepare(ClientIn) ->
[<<"">>, UserMaybeDomain, Password] ->
case parse_domain(UserMaybeDomain) of
%% <NUL>login@domain<NUL>pwd
- [User, _Domain] -> [UserMaybeDomain, User, Password];
+ [User, _Domain] -> [User, User, Password];
%% <NUL>login<NUL>pwd
- [User] -> [<<"">>, User, Password]
+ [User] -> [User, User, Password]
end;
+ [AuthzId, User, Password] ->
+ case parse_domain(AuthzId) of
%% login@domain<NUL>login<NUL>pwd
- [AuthzId, User, Password] -> [AuthzId, User, Password];
+ [AuthzUser, _Domain] -> [AuthzUser, User, Password];
+ %% login<NUL>login<NUL>pwd
+ [AuthzUser] -> [AuthzUser, User, Password]
+ end;
_ -> error
end.
diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl
index 059938f5e..18f52b48f 100644
--- a/src/cyrsasl_scram.erl
+++ b/src/cyrsasl_scram.erl
@@ -159,7 +159,8 @@ mech_step(#state{step = 4} = State, ClientIn) ->
ServerSignature =
scram:server_signature(State#state.server_key,
AuthMessage),
- {ok, [{username, State#state.username}],
+ {ok, [{username, State#state.username},
+ {authzid, State#state.username}],
<<"v=",
(jlib:encode_base64(ServerSignature))/binary>>};
true -> {error, <<"bad-auth">>, State#state.username}
diff --git a/src/ejabberd.erl b/src/ejabberd.erl
index e1b92c26b..6bd2422ae 100644
--- a/src/ejabberd.erl
+++ b/src/ejabberd.erl
@@ -79,7 +79,7 @@ is_loaded() ->
start_app(App, Type, StartFlag) when not is_list(App) ->
start_app([App], Type, StartFlag);
start_app([App|Apps], Type, StartFlag) ->
- case application:start(App) of
+ case application:start(App,Type) of
ok ->
spawn(fun() -> check_app_modules(App, StartFlag) end),
start_app(Apps, Type, StartFlag);
diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl
index 4aa5faedb..a22f8f3d3 100644
--- a/src/ejabberd_admin.erl
+++ b/src/ejabberd_admin.erl
@@ -192,15 +192,24 @@ get_commands_spec() ->
module = ejabberd_piefxis, function = export_host,
args = [{dir, string}, {host, string}], result = {res, rescode}},
- #ejabberd_commands{name = export_odbc, tags = [mnesia, odbc],
+ #ejabberd_commands{name = export_sql, tags = [mnesia, sql],
desc = "Export all tables as SQL queries to a file",
- module = ejd2odbc, function = export,
+ module = ejd2sql, function = export,
args = [{host, string}, {file, string}], result = {res, rescode}},
- #ejabberd_commands{name = convert_to_scram, tags = [odbc],
+ #ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
+ desc = "Export all tables as SQL queries to a file",
+ module = ejd2sql, function = delete,
+ args = [{host, string}], result = {res, rescode}},
+ #ejabberd_commands{name = convert_to_scram, tags = [sql],
desc = "Convert the passwords in 'users' ODBC table to SCRAM",
- module = ejabberd_auth_odbc, function = convert_to_scram,
+ module = ejabberd_auth_sql, function = convert_to_scram,
args = [{host, binary}], result = {res, rescode}},
+ #ejabberd_commands{name = import_prosody, tags = [mnesia, sql, riak],
+ desc = "Import data from Prosody",
+ module = prosody2ejabberd, function = from_dir,
+ args = [{dir, string}], result = {res, rescode}},
+
#ejabberd_commands{name = convert_to_yaml, tags = [config],
desc = "Convert the input file from Erlang to YAML format",
module = ejabberd_config, function = convert_to_yaml,
@@ -216,9 +225,9 @@ get_commands_spec() ->
module = ?MODULE, function = delete_old_messages,
args = [{days, integer}], result = {res, rescode}},
- #ejabberd_commands{name = export2odbc, tags = [mnesia],
+ #ejabberd_commands{name = export2sql, tags = [mnesia],
desc = "Export virtual host information from Mnesia tables to SQL files",
- module = ejd2odbc, function = export,
+ module = ejd2sql, function = export,
args = [{host, string}, {directory, string}],
result = {res, rescode}},
#ejabberd_commands{name = set_master, tags = [mnesia],
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index 628b4a8ad..b25bf231b 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -30,7 +30,7 @@
-behaviour(application).
--export([start_modules/0, start/2, prep_stop/1, stop/1,
+-export([start/2, prep_stop/1, stop/1,
init/0, opt_type/1]).
-include("ejabberd.hrl").
@@ -71,7 +71,7 @@ start(normal, _Args) ->
maybe_add_nameservers(),
ejabberd_auth:start(),
ejabberd_oauth:start(),
- start_modules(),
+ gen_mod:start_modules(),
ejabberd_listener:start_listeners(),
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
Sup;
@@ -83,7 +83,7 @@ start(_, _) ->
%% before shutting down the processes of the application.
prep_stop(State) ->
ejabberd_listener:stop_listeners(),
- stop_modules(),
+ gen_mod:stop_modules(),
ejabberd_admin:stop(),
broadcast_c2s_shutdown(),
timer:sleep(5000),
@@ -137,42 +137,6 @@ db_init() ->
ejabberd:start_app(mnesia, permanent),
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
-%% Start all the modules in all the hosts
-start_modules() ->
- lists:foreach(
- fun(Host) ->
- Modules = ejabberd_config:get_option(
- {modules, Host},
- fun(Mods) ->
- lists:map(
- fun({M, A}) when is_atom(M), is_list(A) ->
- {M, A}
- end, Mods)
- end, []),
- lists:foreach(
- fun({Module, Args}) ->
- gen_mod:start_module(Host, Module, Args)
- end, Modules)
- end, ?MYHOSTS).
-
-%% Stop all the modules in all the hosts
-stop_modules() ->
- lists:foreach(
- fun(Host) ->
- Modules = ejabberd_config:get_option(
- {modules, Host},
- fun(Mods) ->
- lists:map(
- fun({M, A}) when is_atom(M), is_list(A) ->
- {M, A}
- end, Mods)
- end, []),
- lists:foreach(
- fun({Module, _Args}) ->
- gen_mod:stop_module_keep_config(Host, Module)
- end, Modules)
- end, ?MYHOSTS).
-
connect_nodes() ->
Nodes = ejabberd_config:get_option(
cluster_nodes,
@@ -252,11 +216,10 @@ start_apps() ->
crypto:start(),
ejabberd:start_app(sasl),
ejabberd:start_app(ssl),
- ejabberd:start_app(p1_yaml),
- ejabberd:start_app(p1_tls),
- ejabberd:start_app(p1_xml),
- ejabberd:start_app(p1_stringprep),
- ejabberd:start_app(p1_zlib),
+ ejabberd:start_app(fast_yaml),
+ ejabberd:start_app(fast_tls),
+ ejabberd:start_app(fast_xml),
+ ejabberd:start_app(stringprep),
ejabberd:start_app(cache_tab).
opt_type(net_ticktime) ->
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index 2cc37c6e0..0267a2192 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -32,9 +32,9 @@
-author('alexey@process-one.net').
%% External exports
--export([start/0, set_password/3, check_password/3,
- check_password/5, check_password_with_authmodule/3,
- check_password_with_authmodule/5, try_register/3,
+-export([start/0, set_password/3, check_password/4,
+ 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,
@@ -63,8 +63,8 @@
-callback remove_user(binary(), binary()) -> any().
-callback remove_user(binary(), binary(), binary()) -> any().
-callback is_user_exists(binary(), binary()) -> boolean() | {error, atom()}.
--callback check_password(binary(), binary(), binary()) -> boolean().
--callback check_password(binary(), binary(), binary(), binary(),
+-callback check_password(binary(), binary(), binary(), binary()) -> boolean().
+-callback check_password(binary(), binary(), binary(), binary(), binary(),
fun((binary()) -> binary())) -> boolean().
-callback try_register(binary(), binary(), binary()) -> {atomic, atom()} |
{error, atom()}.
@@ -102,10 +102,10 @@ store_type(Server) ->
end,
plain, auth_modules(Server)).
--spec check_password(binary(), binary(), binary()) -> boolean().
+-spec check_password(binary(), binary(), binary(), binary()) -> boolean().
-check_password(User, Server, Password) ->
- case check_password_with_authmodule(User, Server,
+check_password(User, AuthzId, Server, Password) ->
+ case check_password_with_authmodule(User, AuthzId, Server,
Password)
of
{true, _AuthModule} -> true;
@@ -113,15 +113,15 @@ check_password(User, Server, Password) ->
end.
%% @doc Check if the user and password can login in server.
-%% @spec (User::string(), Server::string(), Password::string(),
+%% @spec (User::string(), AuthzId::string(), Server::string(), Password::string(),
%% Digest::string(), DigestGen::function()) ->
%% true | false
--spec check_password(binary(), binary(), binary(), binary(),
+-spec check_password(binary(), binary(), binary(), binary(), binary(),
fun((binary()) -> binary())) -> boolean().
-check_password(User, Server, Password, Digest,
+check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- case check_password_with_authmodule(User, Server,
+ case check_password_with_authmodule(User, AuthzId, Server,
Password, Digest, DigestGen)
of
{true, _AuthModule} -> true;
@@ -132,28 +132,28 @@ check_password(User, Server, Password, Digest,
%% The user can login if at least an authentication method accepts the user
%% and the password.
%% The first authentication method that accepts the credentials is returned.
-%% @spec (User::string(), Server::string(), Password::string()) ->
+%% @spec (User::string(), AuthzId::string(), Server::string(), Password::string()) ->
%% {true, AuthModule} | false
%% where
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
%% | ejabberd_auth_internal | ejabberd_auth_ldap
-%% | ejabberd_auth_odbc | ejabberd_auth_pam
--spec check_password_with_authmodule(binary(), binary(), binary()) -> false |
+%% | ejabberd_auth_sql | ejabberd_auth_pam | ejabberd_auth_riak
+-spec check_password_with_authmodule(binary(), binary(), binary(), binary()) -> false |
{true, atom()}.
-check_password_with_authmodule(User, Server,
+check_password_with_authmodule(User, AuthzId, Server,
Password) ->
check_password_loop(auth_modules(Server),
- [User, Server, Password]).
+ [User, AuthzId, Server, Password]).
--spec check_password_with_authmodule(binary(), binary(), binary(), binary(),
+-spec check_password_with_authmodule(binary(), binary(), binary(), binary(), binary(),
fun((binary()) -> binary())) -> false |
{true, atom()}.
-check_password_with_authmodule(User, Server, Password,
+check_password_with_authmodule(User, AuthzId, Server, Password,
Digest, DigestGen) ->
check_password_loop(auth_modules(Server),
- [User, Server, Password, Digest, DigestGen]).
+ [User, AuthzId, Server, Password, Digest, DigestGen]).
check_password_loop([], _Args) -> false;
check_password_loop([AuthModule | AuthModules], Args) ->
diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl
index 7b21752a2..5a5b395bf 100644
--- a/src/ejabberd_auth_anonymous.erl
+++ b/src/ejabberd_auth_anonymous.erl
@@ -38,8 +38,8 @@
unregister_connection/3
]).
--export([login/2, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([login/2, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -56,7 +56,7 @@
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
- sid = {p1_time_compat:timestamp(), self()} :: ejabberd_sm:sid()}).
+ sid = ejabberd_sm:make_sid() :: ejabberd_sm:sid()}).
start(Host) ->
%% TODO: Check cluster mode
@@ -175,11 +175,11 @@ purge_hook(true, LUser, LServer) ->
%% When anonymous login is enabled, check the password for permenant users
%% before allowing access
-check_password(User, Server, Password) ->
- check_password(User, Server, Password, undefined,
+check_password(User, AuthzId, Server, Password) ->
+ check_password(User, AuthzId, Server, Password, undefined,
undefined).
-check_password(User, Server, _Password, _Digest,
+check_password(User, _AuthzId, Server, _Password, _Digest,
_DigestGen) ->
case
ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl
index 2a1cbf085..5897fba5b 100644
--- a/src/ejabberd_auth_external.erl
+++ b/src/ejabberd_auth_external.erl
@@ -31,8 +31,8 @@
-behaviour(ejabberd_auth).
--export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -76,16 +76,20 @@ plain_password_required() -> true.
store_type() -> external.
-check_password(User, Server, Password) ->
+check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
case get_cache_option(Server) of
- false -> check_password_extauth(User, Server, Password);
+ false -> check_password_extauth(User, AuthzId, Server, Password);
{true, CacheTime} ->
- check_password_cache(User, Server, Password, CacheTime)
+ check_password_cache(User, AuthzId, Server, Password, CacheTime)
+ end
end.
-check_password(User, Server, Password, _Digest,
+check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
- check_password(User, Server, Password).
+ check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of
@@ -178,8 +182,8 @@ get_cache_option(Host) ->
CacheTime -> {true, CacheTime}
end.
-%% @spec (User, Server, Password) -> true | false
-check_password_extauth(User, Server, Password) ->
+%% @spec (User, AuthzId, Server, Password) -> true | false
+check_password_extauth(User, _AuthzId, Server, Password) ->
extauth:check_password(User, Server, Password) andalso
Password /= <<"">>.
@@ -187,42 +191,42 @@ check_password_extauth(User, Server, Password) ->
try_register_extauth(User, Server, Password) ->
extauth:try_register(User, Server, Password).
-check_password_cache(User, Server, Password, 0) ->
- check_password_external_cache(User, Server, Password);
-check_password_cache(User, Server, Password,
+check_password_cache(User, AuthzId, Server, Password, 0) ->
+ check_password_external_cache(User, AuthzId, Server, Password);
+check_password_cache(User, AuthzId, Server, Password,
CacheTime) ->
case get_last_access(User, Server) of
online ->
- check_password_internal(User, Server, Password);
+ check_password_internal(User, AuthzId, Server, Password);
never ->
- check_password_external_cache(User, Server, Password);
+ check_password_external_cache(User, AuthzId, Server, Password);
mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled "
"but mod_last is not enabled in that "
"host",
[]),
- check_password_external_cache(User, Server, Password);
+ check_password_external_cache(User, AuthzId, Server, Password);
TimeStamp ->
case is_fresh_enough(TimeStamp, CacheTime) of
%% If no need to refresh, check password against Mnesia
true ->
- case check_password_internal(User, Server, Password) of
+ case check_password_internal(User, AuthzId, Server, Password) of
%% If password valid in Mnesia, accept it
true -> true;
%% Else (password nonvalid in Mnesia), check in extauth and cache result
false ->
- check_password_external_cache(User, Server, Password)
+ check_password_external_cache(User, AuthzId, Server, Password)
end;
%% Else (need to refresh), check in extauth and cache result
false ->
- check_password_external_cache(User, Server, Password)
+ check_password_external_cache(User, AuthzId, Server, Password)
end
end.
get_password_internal(User, Server) ->
ejabberd_auth_internal:get_password(User, Server).
-%% @spec (User, Server, CacheTime) -> false | Password::string()
+-spec get_password_cache(User::binary(), Server::binary(), CacheTime::integer()) -> Password::string() | false.
get_password_cache(User, Server, CacheTime) ->
case get_last_access(User, Server) of
online -> get_password_internal(User, Server);
@@ -241,8 +245,8 @@ get_password_cache(User, Server, CacheTime) ->
end.
%% Check the password using extauth; if success then cache it
-check_password_external_cache(User, Server, Password) ->
- case check_password_extauth(User, Server, Password) of
+check_password_external_cache(User, AuthzId, Server, Password) ->
+ case check_password_extauth(User, AuthzId, Server, Password) of
true ->
set_password_internal(User, Server, Password), true;
false -> false
@@ -256,9 +260,9 @@ try_register_external_cache(User, Server, Password) ->
_ -> {error, not_allowed}
end.
-%% @spec (User, Server, Password) -> true | false
-check_password_internal(User, Server, Password) ->
- ejabberd_auth_internal:check_password(User, Server,
+%% @spec (User, AuthzId, Server, Password) -> true | false
+check_password_internal(User, AuthzId, Server, Password) ->
+ ejabberd_auth_internal:check_password(User, AuthzId, Server,
Password).
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
@@ -273,10 +277,10 @@ is_fresh_enough(TimeStampLast, CacheTime) ->
Now = p1_time_compat:system_time(seconds),
TimeStampLast + CacheTime > Now.
-%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer()
%% Code copied from mod_configure.erl
%% Code copied from web/ejabberd_web_admin.erl
%% TODO: Update time format to XEP-0202: Entity Time
+-spec(get_last_access(User::binary(), Server::binary()) -> (online | never | mod_last_required | integer())).
get_last_access(User, Server) ->
case ejabberd_sm:get_user_resources(User, Server) of
[] ->
diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl
index d60e0fc5f..acbbfe506 100644
--- a/src/ejabberd_auth_internal.erl
+++ b/src/ejabberd_auth_internal.erl
@@ -31,8 +31,8 @@
-behaviour(ejabberd_auth).
--export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -86,9 +86,12 @@ store_type() ->
true -> scram %% allows: PLAIN SCRAM
end.
-check_password(User, Server, Password) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Password}]
@@ -98,12 +101,16 @@ check_password(User, Server, Password) ->
when is_record(Scram, scram) ->
is_password_scram_valid(Password, Scram);
_ -> false
+ end
end.
-check_password(User, Server, Password, Digest,
+check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Passwd}] when is_binary(Passwd) ->
@@ -125,6 +132,7 @@ check_password(User, Server, Password, Digest,
true -> (Passwd == Password) and (Password /= <<"">>)
end;
_ -> false
+ end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
@@ -466,8 +474,8 @@ export(_Server) ->
[{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
+ Username = ejabberd_sql:escape(LUser),
+ Pass = ejabberd_sql:escape(Password),
[[<<"delete from users where username='">>, Username, <<"';">>],
[<<"insert into users(username, password) "
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl
index dd5d54a74..51b466ef4 100644
--- a/src/ejabberd_auth_ldap.erl
+++ b/src/ejabberd_auth_ldap.erl
@@ -37,7 +37,7 @@
handle_cast/2, terminate/2, code_change/3]).
-export([start/1, stop/1, start_link/1, set_password/3,
- check_password/3, check_password/5, try_register/3,
+ check_password/4, check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -116,7 +116,10 @@ plain_password_required() -> true.
store_type() -> external.
-check_password(User, Server, Password) ->
+check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
if Password == <<"">> -> false;
true ->
case catch check_password_ldap(User, Server, Password)
@@ -124,11 +127,12 @@ check_password(User, Server, Password) ->
{'EXIT', _} -> false;
Result -> Result
end
+ end
end.
-check_password(User, Server, Password, _Digest,
+check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
- check_password(User, Server, Password).
+ check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl
index ee5123c5e..fa4b9f078 100644
--- a/src/ejabberd_auth_pam.erl
+++ b/src/ejabberd_auth_pam.erl
@@ -30,8 +30,8 @@
-behaviour(ejabberd_auth).
--export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -46,11 +46,14 @@ start(_Host) ->
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
-check_password(User, Server, Password, _Digest,
+check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
- check_password(User, Server, Password).
+ check_password(User, AuthzId, Server, Password).
-check_password(User, Host, Password) ->
+check_password(User, AuthzId, Host, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
Service = get_pam_service(Host),
UserInfo = case get_pam_userinfotype(Host) of
username -> User;
@@ -61,6 +64,7 @@ check_password(User, Host, Password) ->
of
true -> true;
_ -> false
+ end
end.
try_register(_User, _Server, _Password) ->
diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl
index 64bf12c6a..c48b94410 100644
--- a/src/ejabberd_auth_riak.erl
+++ b/src/ejabberd_auth_riak.erl
@@ -30,8 +30,8 @@
-behaviour(ejabberd_auth).
%% External exports
--export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -66,9 +66,12 @@ store_type() ->
passwd_schema() ->
{record_info(fields, passwd), #passwd{}}.
-check_password(User, Server, Password) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}} when is_binary(Password) ->
Password /= <<"">>;
@@ -76,12 +79,16 @@ check_password(User, Server, Password) ->
is_password_scram_valid(Password, Scram);
_ ->
false
+ end
end.
-check_password(User, Server, Password, Digest,
+check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
DigRes = if Digest /= <<"">> ->
@@ -102,6 +109,7 @@ check_password(User, Server, Password, Digest,
true -> (Passwd == Password) and (Password /= <<"">>)
end;
_ -> false
+ end
end.
set_password(User, Server, Password) ->
@@ -283,8 +291,8 @@ export(_Server) ->
[{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
+ Username = ejabberd_sql:escape(LUser),
+ Pass = ejabberd_sql:escape(Password),
[[<<"delete from users where username='">>, Username, <<"';">>],
[<<"insert into users(username, password) "
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_sql.erl
index b8b4594b6..000b4a4f4 100644
--- a/src/ejabberd_auth_odbc.erl
+++ b/src/ejabberd_auth_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : ejabberd_auth_odbc.erl
+%%% File : ejabberd_auth_sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Authentification via ODBC
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(ejabberd_auth_odbc).
+-module(ejabberd_auth_sql).
-behaviour(ejabberd_config).
@@ -31,8 +31,8 @@
-behaviour(ejabberd_auth).
--export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -63,31 +63,30 @@ store_type() ->
true -> scram %% allows: PLAIN SCRAM
end.
-%% @spec (User, Server, Password) -> true | false | {error, Error}
-check_password(User, Server, Password) ->
- LServer = jid:nameprep(Server),
- LUser = jid:nodeprep(User),
+%% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error}
+check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
- try odbc_queries:get_password_scram(LServer, Username) of
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>],
- [[StoredKey, ServerKey, Salt, IterationCount]]} ->
+ try sql_queries:get_password_scram(LServer, LUser) of
+ {selected,
+ [{StoredKey, ServerKey, Salt, IterationCount}]} ->
Scram =
#scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt,
- iterationcount = binary_to_integer(
- IterationCount)},
+ iterationcount = IterationCount},
is_password_scram_valid(Password, Scram);
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>], []} ->
+ {selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
@@ -96,12 +95,12 @@ check_password(User, Server, Password) ->
false %% Typical error is database not accessible
end;
false ->
- try odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[Password]]} ->
+ try sql_queries:get_password(LServer, LUser) of
+ {selected, [{Password}]} ->
Password /= <<"">>;
- {selected, [<<"password">>], [[_Password2]]} ->
+ {selected, [{_Password2}]} ->
false; %% Password is not correct
- {selected, [<<"password">>], []} ->
+ {selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
@@ -110,13 +109,17 @@ check_password(User, Server, Password) ->
false %% Typical error is database not accessible
end
end
+ end
end.
-%% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
-check_password(User, Server, Password, Digest,
+%% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
+check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- LServer = jid:nameprep(Server),
- LUser = jid:nodeprep(User),
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
@@ -124,10 +127,9 @@ check_password(User, Server, Password, Digest,
true ->
case is_scrammed() of
false ->
- Username = ejabberd_odbc:escape(LUser),
- try odbc_queries:get_password(LServer, Username) of
+ try sql_queries:get_password(LServer, LUser) of
%% Account exists, check if password is valid
- {selected, [<<"password">>], [[Passwd]]} ->
+ {selected, [{Passwd}]} ->
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
@@ -135,7 +137,7 @@ check_password(User, Server, Password, Digest,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
- {selected, [<<"password">>], []} ->
+ {selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
@@ -146,6 +148,7 @@ check_password(User, Server, Password, Digest,
true ->
false
end
+ end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
@@ -158,26 +161,24 @@ set_password(User, Server, Password) ->
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
Scram = password_to_scram(Password),
- case catch odbc_queries:set_password_scram_t(
+ case catch sql_queries:set_password_scram_t(
LServer,
- Username,
- ejabberd_odbc:escape(Scram#scram.storedkey),
- ejabberd_odbc:escape(Scram#scram.serverkey),
- ejabberd_odbc:escape(Scram#scram.salt),
- integer_to_binary(Scram#scram.iterationcount)
+ LUser,
+ Scram#scram.storedkey,
+ Scram#scram.serverkey,
+ Scram#scram.salt,
+ Scram#scram.iterationcount
)
of
{atomic, ok} -> ok;
Other -> {error, Other}
end;
false ->
- Pass = ejabberd_odbc:escape(Password),
- case catch odbc_queries:set_password_t(LServer,
- Username, Pass)
+ case catch sql_queries:set_password_t(LServer,
+ LUser, Password)
of
{atomic, ok} -> ok;
Other -> {error, Other}
@@ -194,26 +195,23 @@ try_register(User, Server, Password) ->
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
Scram = password_to_scram(Password),
- case catch odbc_queries:add_user_scram(
+ case catch sql_queries:add_user_scram(
LServer,
- Username,
- ejabberd_odbc:escape(Scram#scram.storedkey),
- ejabberd_odbc:escape(Scram#scram.serverkey),
- ejabberd_odbc:escape(Scram#scram.salt),
- integer_to_binary(Scram#scram.iterationcount)
+ LUser,
+ Scram#scram.storedkey,
+ Scram#scram.serverkey,
+ Scram#scram.salt,
+ Scram#scram.iterationcount
) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
end;
false ->
- Pass = ejabberd_odbc:escape(Password),
- case catch odbc_queries:add_user(LServer, Username,
- Pass)
- of
+ case catch sql_queries:add_user(LServer, LUser,
+ Password) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
end
@@ -221,42 +219,58 @@ try_register(User, Server, Password) ->
end.
dirty_get_registered_users() ->
- Servers = ejabberd_config:get_vh_by_auth_method(odbc),
+ Servers = ejabberd_config:get_vh_by_auth_method(sql),
lists:flatmap(fun (Server) ->
get_vh_registered_users(Server)
end,
Servers).
get_vh_registered_users(Server) ->
- LServer = jid:nameprep(Server),
- case catch odbc_queries:list_users(LServer) of
- {selected, [<<"username">>], Res} ->
- [{U, LServer} || [U] <- Res];
- _ -> []
+ case jid:nameprep(Server) of
+ error -> [];
+ <<>> -> [];
+ LServer ->
+ case catch sql_queries:list_users(LServer) of
+ {selected, Res} ->
+ [{U, LServer} || {U} <- Res];
+ _ -> []
+ end
end.
get_vh_registered_users(Server, Opts) ->
- LServer = jid:nameprep(Server),
- case catch odbc_queries:list_users(LServer, Opts) of
- {selected, [<<"username">>], Res} ->
- [{U, LServer} || [U] <- Res];
- _ -> []
+ case jid:nameprep(Server) of
+ error -> [];
+ <<>> -> [];
+ LServer ->
+ case catch sql_queries:list_users(LServer, Opts) of
+ {selected, Res} ->
+ [{U, LServer} || {U} <- Res];
+ _ -> []
+ end
end.
get_vh_registered_users_number(Server) ->
- LServer = jid:nameprep(Server),
- case catch odbc_queries:users_number(LServer) of
- {selected, [_], [[Res]]} ->
- jlib:binary_to_integer(Res);
- _ -> 0
+ case jid:nameprep(Server) of
+ error -> 0;
+ <<>> -> 0;
+ LServer ->
+ case catch sql_queries:users_number(LServer) of
+ {selected, [{Res}]} ->
+ Res;
+ _ -> 0
+ end
end.
get_vh_registered_users_number(Server, Opts) ->
- LServer = jid:nameprep(Server),
- case catch odbc_queries:users_number(LServer, Opts) of
- {selected, [_], [[Res]]} ->
- jlib:binary_to_integer(Res);
- _Other -> 0
+ case jid:nameprep(Server) of
+ error -> 0;
+ <<>> -> 0;
+ LServer ->
+ case catch sql_queries:users_number(LServer, Opts) of
+ {selected, [{Res}]} ->
+ Res;
+ _Other -> 0
+ end
end.
get_password(User, Server) ->
@@ -267,24 +281,22 @@ get_password(User, Server) ->
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
- case catch odbc_queries:get_password_scram(
- LServer, Username) of
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>],
- [[StoredKey, ServerKey, Salt, IterationCount]]} ->
+ case catch sql_queries:get_password_scram(
+ LServer, LUser) of
+ {selected,
+ [{StoredKey, ServerKey, Salt, IterationCount}]} ->
{jlib:decode_base64(StoredKey),
jlib:decode_base64(ServerKey),
jlib:decode_base64(Salt),
- binary_to_integer(IterationCount)};
+ IterationCount};
_ -> false
end;
false ->
- case catch odbc_queries:get_password(LServer, Username)
+ case catch sql_queries:get_password(LServer, LUser)
of
- {selected, [<<"password">>], [[Password]]} -> Password;
+ {selected, [{Password}]} -> Password;
_ -> false
end
end
@@ -300,9 +312,8 @@ get_password_s(User, Server) ->
true ->
case is_scrammed() of
false ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[Password]]} -> Password;
+ case catch sql_queries:get_password(LServer, LUser) of
+ {selected, [{Password}]} -> Password;
_ -> <<"">>
end;
true -> <<"">>
@@ -311,15 +322,17 @@ get_password_s(User, Server) ->
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
- case jid:nodeprep(User) of
- error -> false;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jid:nameprep(Server),
- try odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[_Password]]} ->
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ false;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ false;
+ true ->
+ try sql_queries:get_password(LServer, LUser) of
+ {selected, [{_Password}]} ->
true; %% Account exists
- {selected, [<<"password">>], []} ->
+ {selected, []} ->
false; %% Account does not exist
{error, Error} -> {error, Error}
catch
@@ -331,12 +344,14 @@ is_user_exists(User, Server) ->
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
- case jid:nodeprep(User) of
- error -> error;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jid:nameprep(Server),
- catch odbc_queries:del_user(LServer, Username),
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ error;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ error;
+ true ->
+ catch sql_queries:del_user(LServer, LUser),
ok
end.
@@ -352,27 +367,23 @@ remove_user(User, Server, Password) ->
true ->
case is_scrammed() of
true ->
- case check_password(User, Server, Password) of
+ case check_password(User, <<"">>, Server, Password) of
true ->
remove_user(User, Server),
ok;
false -> not_allowed
end;
false ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
F = fun () ->
- Result = odbc_queries:del_user_return_password(
- LServer, Username, Pass),
+ Result = sql_queries:del_user_return_password(
+ LServer, LUser, Password),
case Result of
- {selected, [<<"password">>],
- [[Password]]} -> ok;
- {selected, [<<"password">>],
- []} -> not_exists;
+ {selected, [{Password}]} -> ok;
+ {selected, []} -> not_exists;
_ -> not_allowed
end
end,
- {atomic, Result} = odbc_queries:sql_transaction(
+ {atomic, Result} = sql_queries:sql_transaction(
LServer, F),
Result
end
@@ -416,7 +427,7 @@ is_password_scram_valid(Password, Scram) ->
set_password_scram_t(Username,
StoredKey, ServerKey, Salt, IterationCount) ->
- odbc_queries:update_t(<<"users">>,
+ sql_queries:update_t(<<"users">>,
[<<"username">>,
<<"password">>,
<<"serverkey">>,
@@ -436,7 +447,7 @@ convert_to_scram(Server) ->
{error, {incorrect_server_name, Server}};
true ->
F = fun () ->
- case ejabberd_odbc:sql_query_t(
+ case ejabberd_sql:sql_query_t(
[<<"select username, password from users where "
"iterationcount=0 limit ">>,
integer_to_binary(?BATCH_SIZE),
@@ -446,13 +457,13 @@ convert_to_scram(Server) ->
{selected, [<<"username">>, <<"password">>], Rs} ->
lists:foreach(
fun([LUser, Password]) ->
- Username = ejabberd_odbc:escape(LUser),
+ Username = ejabberd_sql:escape(LUser),
Scram = password_to_scram(Password),
set_password_scram_t(
Username,
- ejabberd_odbc:escape(Scram#scram.storedkey),
- ejabberd_odbc:escape(Scram#scram.serverkey),
- ejabberd_odbc:escape(Scram#scram.salt),
+ ejabberd_sql:escape(Scram#scram.storedkey),
+ ejabberd_sql:escape(Scram#scram.serverkey),
+ ejabberd_sql:escape(Scram#scram.salt),
integer_to_binary(Scram#scram.iterationcount)
)
end, Rs),
@@ -460,7 +471,7 @@ convert_to_scram(Server) ->
Err -> {bad_reply, Err}
end
end,
- case odbc_queries:sql_transaction(LServer, F) of
+ case sql_queries:sql_transaction(LServer, F) of
{atomic, ok} -> ok;
{atomic, continue} -> convert_to_scram(Server);
{atomic, Error} -> {error, Error};
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 04e41f468..948e2027b 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -115,6 +115,7 @@
mgmt_resend,
mgmt_stanzas_in = 0,
mgmt_stanzas_out = 0,
+ ask_offline = true,
lang = <<"">>}).
%-define(DBGFSM, true).
@@ -133,13 +134,13 @@
%% session:
-define(C2S_OPEN_TIMEOUT, 60000).
--define(C2S_HIBERNATE_TIMEOUT, 90000).
+-define(C2S_HIBERNATE_TIMEOUT, ejabberd_config:get_option(c2s_hibernate, fun(X) when is_integer(X); X == hibernate-> X end, 90000)).
-define(STREAM_HEADER,
<<"<?xml version='1.0'?><stream:stream "
"xmlns='jabber:client' xmlns:stream='http://et"
- "herx.jabber.org/streams' id='~s' from='~s'~s~"
- "s>">>).
+ "herx.jabber.org/streams' id='~s' from='~s'~s"
+ "~s>">>).
-define(STREAM_TRAILER, <<"</stream:stream>">>).
@@ -326,7 +327,7 @@ init([{SockMod, Socket}, Opts]) ->
xml_socket = XMLSocket, zlib = Zlib, tls = TLS,
tls_required = StartTLSRequired,
tls_enabled = TLSEnabled, tls_options = TLSOpts,
- sid = {p1_time_compat:timestamp(), self()}, streamid = new_id(),
+ sid = ejabberd_sm:make_sid(), streamid = new_id(),
access = Access, shaper = Shaper, ip = IP,
mgmt_state = StreamMgmtState,
mgmt_max_queue = MaxAckQueue,
@@ -342,15 +343,15 @@ get_subscribed(FsmRef) ->
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
DefaultLang = ?MYLANG,
- case xml:get_attr_s(<<"xmlns:stream">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns:stream">>, Attrs) of
?NS_STREAM ->
Server =
case StateData#state.server of
<<"">> ->
- jid:nameprep(xml:get_attr_s(<<"to">>, Attrs));
+ jid:nameprep(fxml:get_attr_s(<<"to">>, Attrs));
S -> S
end,
- Lang = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
+ 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
@@ -367,7 +368,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case lists:member(Server, ?MYHOSTS) of
true when IsBlacklistedIP == false ->
change_shaper(StateData, jid:make(<<"">>, Server, <<"">>)),
- case xml:get_attr_s(<<"version">>, Attrs) of
+ case fxml:get_attr_s(<<"version">>, Attrs) of
<<"1.0">> ->
send_header(StateData, Server, <<"1.0">>, DefaultLang),
case StateData#state.authenticated of
@@ -381,13 +382,13 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
ejabberd_auth:get_password_with_authmodule(
U, Server)
end,
- fun (U, P) ->
+ fun(U, AuthzId, P) ->
ejabberd_auth:check_password_with_authmodule(
- U, Server, P)
+ U, AuthzId, Server, P)
end,
- fun (U, P, D, DG) ->
+ fun(U, AuthzId, P, D, DG) ->
ejabberd_auth:check_password_with_authmodule(
- U, Server, P, D, DG)
+ U, AuthzId, Server, P, D, DG)
end),
Mechs =
case TLSEnabled or not TLSRequired of
@@ -408,7 +409,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
Zlib = StateData#state.zlib,
CompressFeature = case Zlib andalso
- ((SockMod == gen_tcp) orelse (SockMod == p1_tls)) of
+ ((SockMod == gen_tcp) orelse (SockMod == fast_tls)) of
true ->
[#xmlel{name = <<"compression">>,
attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
@@ -468,6 +469,22 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
false ->
[]
end,
+ SockMod =
+ (StateData#state.sockmod):get_sockmod(
+ StateData#state.socket),
+ Zlib = StateData#state.zlib,
+ CompressFeature =
+ case Zlib andalso
+ ((SockMod == gen_tcp) orelse (SockMod == fast_tls)) of
+ true ->
+ [#xmlel{name = <<"compression">>,
+ attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
+ children = [#xmlel{name = <<"method">>,
+ attrs = [],
+ children = [{xmlcdata, <<"zlib">>}]}]}];
+ _ ->
+ []
+ end,
StreamFeatures1 = [#xmlel{name = <<"bind">>,
attrs = [{<<"xmlns">>, ?NS_BIND}],
children = []},
@@ -478,6 +495,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
++
RosterVersioningFeature ++
StreamManagementFeature ++
+ CompressFeature ++
ejabberd_hooks:run_fold(c2s_post_auth_features,
Server, [], [Server]),
StreamFeatures = ejabberd_hooks:run_fold(c2s_stream_features,
@@ -504,7 +522,6 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
<<"Use of STARTTLS required">>)),
- send_trailer(StateData),
{stop, normal, StateData};
true ->
fsm_next_state(wait_for_auth,
@@ -519,34 +536,28 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
[jlib:ip_to_list(IP), LogReason]),
send_header(StateData, Server, <<"">>, DefaultLang),
send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)),
- send_trailer(StateData),
{stop, normal, StateData};
_ ->
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
send_element(StateData, ?HOST_UNKNOWN_ERR),
- send_trailer(StateData),
{stop, normal, StateData}
end;
_ ->
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
send_element(StateData, ?INVALID_NS_ERR),
- send_trailer(StateData),
{stop, normal, StateData}
end;
wait_for_stream(timeout, StateData) ->
{stop, normal, StateData};
wait_for_stream({xmlstreamelement, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream({xmlstreamend, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>),
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
{stop, normal, StateData};
@@ -601,8 +612,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
send_element(StateData, Res),
fsm_next_state(wait_for_auth, StateData);
{auth, _ID, set, {_U, _P, _D, <<"">>}} ->
- Err = jlib:make_error_reply(El,
- ?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))),
+ Lang = StateData#state.lang,
+ Txt = <<"No resource provided">>,
+ Err = jlib:make_error_reply(El, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)),
send_element(StateData, Err),
fsm_next_state(wait_for_auth, StateData);
{auth, _ID, set, {U, P, D, R}} ->
@@ -616,7 +628,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
DGen = fun (PW) ->
p1_sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>)
end,
- case ejabberd_auth:check_password_with_authmodule(U,
+ case ejabberd_auth:check_password_with_authmodule(U, U,
StateData#state.server,
P, D, DGen)
of
@@ -667,7 +679,10 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[false, U, StateData#state.server,
StateData#state.ip]),
- Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED),
+ 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;
@@ -688,7 +703,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[false, U, StateData#state.server,
StateData#state.ip]),
- Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
+ 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
@@ -700,10 +717,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
wait_for_auth(timeout, StateData) ->
{stop, normal, StateData};
wait_for_auth({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
+ {stop, normal, StateData};
wait_for_auth({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_auth(closed, StateData) ->
{stop, normal, StateData};
@@ -724,19 +740,17 @@ wait_for_feature_request({xmlstreamelement, El},
TLSRequired = StateData#state.tls_required,
SockMod =
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
- case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
{?NS_SASL, <<"auth">>}
when TLSEnabled or not TLSRequired ->
- Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
- ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
+ Mech = fxml:get_attr_s(<<"mechanism">>, Attrs),
+ ClientIn = jlib:decode_base64(fxml:get_cdata(Els)),
case cyrsasl:server_start(StateData#state.sasl_state,
Mech, ClientIn)
of
{ok, Props} ->
(StateData#state.sockmod):reset_stream(StateData#state.socket),
- %U = xml:get_attr_s(username, Props),
- U = proplists:get_value(username, Props, <<>>),
- %AuthModule = xml:get_attr_s(auth_module, Props),
+ U = identity(Props),
AuthModule = proplists:get_value(auth_module, Props, undefined),
?INFO_MSG("(~w) Accepted authentication for ~s "
"by ~p from ~s",
@@ -802,7 +816,7 @@ wait_for_feature_request({xmlstreamelement, El},
StateData#state.tls_options)]
end,
Socket = StateData#state.socket,
- BProceed = xml:element_to_binary(#xmlel{name = <<"proceed">>,
+ BProceed = fxml:element_to_binary(#xmlel{name = <<"proceed">>,
attrs = [{<<"xmlns">>, ?NS_TLS}]}),
TLSSocket = (StateData#state.sockmod):starttls(Socket,
TLSOpts,
@@ -813,46 +827,14 @@ wait_for_feature_request({xmlstreamelement, El},
tls_enabled = true});
{?NS_COMPRESS, <<"compress">>}
when Zlib == true,
- (SockMod == gen_tcp) or (SockMod == p1_tls) ->
- case xml:get_subtag(El, <<"method">>) of
- false ->
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
- children =
- [#xmlel{name = <<"setup-failed">>,
- attrs = [], children = []}]}),
- fsm_next_state(wait_for_feature_request, StateData);
- Method ->
- case xml:get_tag_cdata(Method) of
- <<"zlib">> ->
- Socket = StateData#state.socket,
- BCompressed = xml:element_to_binary(#xmlel{name = <<"compressed">>,
- attrs = [{<<"xmlns">>, ?NS_COMPRESS}]}),
- ZlibSocket = (StateData#state.sockmod):compress(Socket,
- BCompressed),
- fsm_next_state(wait_for_stream,
- StateData#state{socket = ZlibSocket,
- streamid = new_id()});
- _ ->
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
- children =
- [#xmlel{name =
- <<"unsupported-method">>,
- attrs = [],
- children = []}]}),
- fsm_next_state(wait_for_feature_request, StateData)
- end
- end;
+ (SockMod == gen_tcp) or (SockMod == fast_tls) ->
+ process_compression_request(El, wait_for_feature_request, StateData);
_ ->
if TLSRequired and not TLSEnabled ->
Lang = StateData#state.lang,
send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
<<"Use of STARTTLS required">>)),
- send_trailer(StateData),
{stop, normal, StateData};
true ->
process_unauthenticated_stanza(StateData, El),
@@ -863,11 +845,10 @@ wait_for_feature_request(timeout, StateData) ->
{stop, normal, StateData};
wait_for_feature_request({xmlstreamend, _Name},
StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
+ {stop, normal, StateData};
wait_for_feature_request({xmlstreamerror, _},
StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_feature_request(closed, StateData) ->
{stop, normal, StateData};
@@ -880,18 +861,16 @@ wait_for_sasl_response({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
wait_for_sasl_response({xmlstreamelement, El},
StateData) ->
#xmlel{name = Name, attrs = Attrs, children = Els} = El,
- case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
{?NS_SASL, <<"response">>} ->
- ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
+ ClientIn = jlib:decode_base64(fxml:get_cdata(Els)),
case cyrsasl:server_step(StateData#state.sasl_state,
ClientIn)
of
{ok, Props} ->
catch
(StateData#state.sockmod):reset_stream(StateData#state.socket),
-% U = xml:get_attr_s(username, Props),
- U = proplists:get_value(username, Props, <<>>),
-% AuthModule = xml:get_attr_s(auth_module, Props),
+ U = identity(Props),
AuthModule = proplists:get_value(auth_module, Props, <<>>),
?INFO_MSG("(~w) Accepted authentication for ~s "
"by ~p from ~s",
@@ -912,9 +891,7 @@ wait_for_sasl_response({xmlstreamelement, El},
user = U});
{ok, Props, ServerOut} ->
(StateData#state.sockmod):reset_stream(StateData#state.socket),
-% U = xml:get_attr_s(username, Props),
- U = proplists:get_value(username, Props, <<>>),
-% AuthModule = xml:get_attr_s(auth_module, Props),
+ U = identity(Props),
AuthModule = proplists:get_value(auth_module, Props, undefined),
?INFO_MSG("(~w) Accepted authentication for ~s "
"by ~p from ~s",
@@ -976,11 +953,10 @@ wait_for_sasl_response(timeout, StateData) ->
{stop, normal, StateData};
wait_for_sasl_response({xmlstreamend, _Name},
StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
+ {stop, normal, StateData};
wait_for_sasl_response({xmlstreamerror, _},
StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_sasl_response(closed, StateData) ->
{stop, normal, StateData};
@@ -1012,7 +988,7 @@ resource_conflict_action(U, S, R) ->
acceptnew -> {accept_resource, R};
closenew -> closenew;
setresource ->
- Rnew = iolist_to_binary([randoms:get_string(),randoms:get_string(),randoms:get_string()]),
+ Rnew = new_uniq_id(),
{accept_resource, Rnew}
end.
@@ -1032,20 +1008,20 @@ wait_for_bind({xmlstreamelement, #xmlel{name = Name, attrs = Attrs} = El},
end;
wait_for_bind({xmlstreamelement, El}, StateData) ->
case jlib:iq_query_info(El) of
- #iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} =
+ #iq{type = set, lang = Lang, xmlns = ?NS_BIND, sub_el = SubEl} =
IQ ->
U = StateData#state.user,
- R1 = xml:get_path_s(SubEl,
+ R1 = fxml:get_path_s(SubEl,
[{elem, <<"resource">>}, cdata]),
R = case jid:resourceprep(R1) of
error -> error;
- <<"">> ->
- iolist_to_binary([randoms:get_string(),randoms:get_string(),randoms:get_string()]);
+ <<"">> -> new_uniq_id();
Resource -> Resource
end,
case R of
error ->
- Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST),
+ 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);
_ ->
@@ -1088,15 +1064,26 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
end
end
end;
- _ -> fsm_next_state(wait_for_bind, StateData)
+ _ ->
+ #xmlel{name = Name, attrs = Attrs, children = _Els} = El,
+ Zlib = StateData#state.zlib,
+ SockMod =
+ (StateData#state.sockmod):get_sockmod(StateData#state.socket),
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ {?NS_COMPRESS, <<"compress">>}
+ when Zlib == true,
+ (SockMod == gen_tcp) or (SockMod == fast_tls) ->
+ process_compression_request(El, wait_for_bind, StateData);
+ _ ->
+ fsm_next_state(wait_for_bind, StateData)
+ end
end;
wait_for_bind(timeout, StateData) ->
{stop, normal, StateData};
wait_for_bind({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
+ {stop, normal, StateData};
wait_for_bind({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_bind(closed, StateData) ->
{stop, normal, StateData};
@@ -1107,6 +1094,7 @@ open_session(StateData) ->
U = StateData#state.user,
R = StateData#state.resource,
JID = StateData#state.jid,
+ Lang = StateData#state.lang,
case acl:match_rule(StateData#state.server,
StateData#state.access, JID) of
allow ->
@@ -1144,7 +1132,8 @@ open_session(StateData) ->
StateData#state.server, [JID]),
?INFO_MSG("(~w) Forbidden session for ~s",
[StateData#state.socket, jid:to_string(JID)]),
- {error, ?ERR_NOT_ALLOWED}
+ Txt = <<"Denied by ACL">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end.
session_established({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
@@ -1167,7 +1156,6 @@ session_established({xmlstreamelement, El},
case check_from(El, FromJID) of
'invalid-from' ->
send_element(StateData, ?INVALID_FROM),
- send_trailer(StateData),
{stop, normal, StateData};
_NewEl ->
session_established2(El, StateData)
@@ -1180,17 +1168,15 @@ session_established(timeout, StateData) ->
[?MODULE, Options, session_established, StateData]),
fsm_next_state(session_established, StateData);
session_established({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
+ {stop, normal, StateData};
session_established({xmlstreamerror,
<<"XML stanza is too big">> = E},
StateData) ->
send_element(StateData,
?POLICY_VIOLATION_ERR((StateData#state.lang), E)),
- send_trailer(StateData),
{stop, normal, StateData};
session_established({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
session_established(closed, #state{mgmt_state = active} = StateData) ->
catch (StateData#state.sockmod):close(StateData#state.socket),
@@ -1207,24 +1193,24 @@ session_established2(El, StateData) ->
User = NewStateData#state.user,
Server = NewStateData#state.server,
FromJID = NewStateData#state.jid,
- To = xml:get_attr_s(<<"to">>, Attrs),
+ To = fxml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of
<<"">> -> jid:make(User, Server, <<"">>);
_ -> jid:from_string(To)
end,
NewEl1 = jlib:remove_attr(<<"xmlns">>, El),
- NewEl = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
+ NewEl = case fxml:get_attr_s(<<"xml:lang">>, Attrs) of
<<"">> ->
case NewStateData#state.lang of
<<"">> -> NewEl1;
Lang ->
- xml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1)
+ fxml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1)
end;
_ -> NewEl1
end,
NewState = case ToJID of
error ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> NewStateData;
<<"result">> -> NewStateData;
_ ->
@@ -1345,7 +1331,6 @@ handle_info(kick, StateName, StateData) ->
handle_info({kick, kicked_by_admin, Xmlelement}, StateName, StateData);
handle_info({kick, Reason, Xmlelement}, _StateName, StateData) ->
send_element(StateData, Xmlelement),
- send_trailer(StateData),
{stop, normal,
StateData#state{authenticated = Reason}};
handle_info({route, _From, _To, {broadcast, Data}},
@@ -1358,7 +1343,6 @@ handle_info({route, _From, _To, {broadcast, Data}},
{exit, Reason} ->
Lang = StateData#state.lang,
send_element(StateData, ?SERRT_CONFLICT(Lang, Reason)),
- catch send_trailer(StateData),
{stop, normal, StateData};
{privacy_list, PrivList, PrivListName} ->
case ejabberd_hooks:run_fold(privacy_updated_list,
@@ -1408,7 +1392,7 @@ handle_info({route, From, To,
StateData,
[{From, To,
Packet}]),
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"probe">> ->
LFrom = jid:tolower(From),
LBFrom =
@@ -1625,7 +1609,7 @@ handle_info({route, From, To,
allow ->
{true, Attrs, StateData};
deny ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"groupchat">> -> ok;
<<"headline">> -> ok;
@@ -1672,11 +1656,9 @@ handle_info(system_shutdown, StateName, StateData) ->
wait_for_stream ->
send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>),
send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
- send_trailer(StateData),
ok;
_ ->
send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
- send_trailer(StateData),
ok
end,
{stop, normal, StateData};
@@ -1737,6 +1719,8 @@ handle_info({broadcast, Type, From, Packet}, StateName, StateData) ->
From, jid:make(USR), Packet)
end, lists:usort(Recipients)),
fsm_next_state(StateName, StateData);
+handle_info(dont_ask_offline, StateName, StateData) ->
+ fsm_next_state(StateName, StateData#state{ask_offline = false});
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
fsm_next_state(StateName, StateData).
@@ -1806,6 +1790,7 @@ terminate(_Reason, StateName, StateData) ->
ok
end
end,
+ catch send_trailer(StateData),
(StateData#state.sockmod):close(StateData#state.socket),
ok.
@@ -1844,7 +1829,7 @@ send_element(StateData, El) when StateData#state.xml_socket ->
(StateData#state.sockmod):send_xml(StateData#state.socket,
{xmlstreamelement, El});
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
send_stanza(StateData, Stanza) when StateData#state.csi_state == inactive ->
csi_filter_stanza(StateData, Stanza);
@@ -1912,6 +1897,10 @@ send_trailer(StateData) ->
new_id() -> randoms:get_string().
+new_uniq_id() ->
+ iolist_to_binary([randoms:get_string(),
+ jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]).
+
is_auth_packet(El) ->
case jlib:iq_query_info(El) of
#iq{id = ID, type = Type, xmlns = ?NS_AUTH, sub_el = SubEl} ->
@@ -1924,7 +1913,7 @@ is_auth_packet(El) ->
is_stanza(#xmlel{name = Name, attrs = Attrs}) when Name == <<"message">>;
Name == <<"presence">>;
Name == <<"iq">> ->
- case xml:get_attr(<<"xmlns">>, Attrs) of
+ case fxml:get_attr(<<"xmlns">>, Attrs) of
{value, NS} when NS /= <<"jabber:client">>,
NS /= <<"jabber:server">> ->
false;
@@ -1936,7 +1925,7 @@ is_stanza(_El) ->
get_auth_tags([#xmlel{name = Name, children = Els} | L],
U, P, D, R) ->
- CData = xml:get_cdata(Els),
+ CData = fxml:get_cdata(Els),
case Name of
<<"username">> -> get_auth_tags(L, CData, P, D, R);
<<"password">> -> get_auth_tags(L, U, CData, D, R);
@@ -1955,11 +1944,11 @@ get_auth_tags([], U, P, D, R) ->
get_conn_type(StateData) ->
case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
gen_tcp -> c2s;
- p1_tls -> c2s_tls;
+ fast_tls -> c2s_tls;
ezlib ->
case ezlib:get_sockmod((StateData#state.socket)#socket_state.socket) of
gen_tcp -> c2s_compressed;
- p1_tls -> c2s_compressed_tls
+ fast_tls -> c2s_compressed_tls
end;
ejabberd_http_bind -> http_bind;
ejabberd_http_ws -> websocket;
@@ -2003,11 +1992,11 @@ process_presence_probe(From, To, StateData) ->
%% User updates his presence (non-directed presence packet)
presence_update(From, Packet, StateData) ->
#xmlel{attrs = Attrs} = Packet,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"unavailable">> ->
- Status = case xml:get_subtag(Packet, <<"status">>) of
+ Status = case fxml:get_subtag(Packet, <<"status">>) of
false -> <<"">>;
- StatusTag -> xml:get_tag_cdata(StatusTag)
+ StatusTag -> fxml:get_tag_cdata(StatusTag)
end,
Info = [{ip, StateData#state.ip},
{conn, StateData#state.conn},
@@ -2068,7 +2057,7 @@ presence_track(From, To, Packet, StateData) ->
LTo = jid:tolower(To),
User = StateData#state.user,
Server = StateData#state.server,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"unavailable">> ->
A = remove_element(LTo, StateData#state.pres_a),
check_privacy_route(From, StateData#state{pres_a = A}, From, To, Packet);
@@ -2265,11 +2254,11 @@ update_priority(Priority, Packet, StateData) ->
StateData#state.resource, Priority, Packet, Info).
get_priority_from_presence(PresencePacket) ->
- case xml:get_subtag(PresencePacket, <<"priority">>) of
+ case fxml:get_subtag(PresencePacket, <<"priority">>) of
false -> 0;
SubEl ->
case catch
- jlib:binary_to_integer(xml:get_tag_cdata(SubEl))
+ jlib:binary_to_integer(fxml:get_tag_cdata(SubEl))
of
P when is_integer(P) -> P;
_ -> 0
@@ -2277,30 +2266,32 @@ get_priority_from_presence(PresencePacket) ->
end.
process_privacy_iq(From, To,
- #iq{type = Type, sub_el = SubEl} = IQ, StateData) ->
- {Res, NewStateData} = case Type of
- get ->
- R = ejabberd_hooks:run_fold(privacy_iq_get,
- StateData#state.server,
- {error,
- ?ERR_FEATURE_NOT_IMPLEMENTED},
- [From, To, IQ,
- StateData#state.privacy_list]),
- {R, StateData};
- set ->
- case ejabberd_hooks:run_fold(privacy_iq_set,
- StateData#state.server,
- {error,
- ?ERR_FEATURE_NOT_IMPLEMENTED},
- [From, To, IQ])
- of
- {result, R, NewPrivList} ->
- {{result, R},
- StateData#state{privacy_list =
- NewPrivList}};
- R -> {R, StateData}
- end
- end,
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ, StateData) ->
+ Txt = <<"No module is handling this query">>,
+ {Res, NewStateData} =
+ case Type of
+ get ->
+ 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]),
+ {R, StateData};
+ set ->
+ case ejabberd_hooks:run_fold(
+ privacy_iq_set,
+ StateData#state.server,
+ {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)},
+ [From, To, IQ])
+ of
+ {result, R, NewPrivList} ->
+ {{result, R},
+ StateData#state{privacy_list =
+ NewPrivList}};
+ R -> {R, StateData}
+ end
+ end,
IQRes = case Res of
{result, Result} ->
IQ#iq{type = result, sub_el = Result};
@@ -2310,7 +2301,7 @@ process_privacy_iq(From, To,
ejabberd_router:route(To, From, jlib:iq_to_xml(IQRes)),
NewStateData.
-resend_offline_messages(StateData) ->
+resend_offline_messages(#state{ask_offline = true} = StateData) ->
case ejabberd_hooks:run_fold(resend_offline_messages_hook,
StateData#state.server, [],
[StateData#state.user, StateData#state.server])
@@ -2331,7 +2322,9 @@ resend_offline_messages(StateData) ->
end
end,
Rs)
- end.
+ end;
+resend_offline_messages(_StateData) ->
+ ok.
resend_subscription_requests(#state{user = User,
server = Server} = StateData) ->
@@ -2346,34 +2339,35 @@ resend_subscription_requests(#state{user = User,
get_showtag(undefined) -> <<"unavailable">>;
get_showtag(Presence) ->
- case xml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
+ case fxml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
<<"">> -> <<"available">>;
ShowTag -> ShowTag
end.
get_statustag(undefined) -> <<"">>;
get_statustag(Presence) ->
- xml:get_path_s(Presence, [{elem, <<"status">>}, cdata]).
+ fxml:get_path_s(Presence, [{elem, <<"status">>}, cdata]).
process_unauthenticated_stanza(StateData, El) ->
- NewEl = case xml:get_tag_attr_s(<<"xml:lang">>, El) of
+ NewEl = case fxml:get_tag_attr_s(<<"xml:lang">>, El) of
<<"">> ->
case StateData#state.lang of
<<"">> -> El;
- Lang -> xml:replace_tag_attr(<<"xml:lang">>, Lang, El)
+ Lang -> fxml:replace_tag_attr(<<"xml:lang">>, Lang, El)
end;
_ -> El
end,
case jlib:iq_query_info(NewEl) of
- #iq{} = IQ ->
+ #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 = [?ERR_SERVICE_UNAVAILABLE]},
+ sub_el = [?ERRT_SERVICE_UNAVAILABLE(L, Txt)]},
Res1 = jlib:replace_from_to(jid:make(<<"">>,
StateData#state.server,
<<"">>),
@@ -2419,7 +2413,6 @@ fsm_next_state(session_established, #state{mgmt_max_queue = exceeded} =
Err = ?SERRT_POLICY_VIOLATION(StateData#state.lang,
<<"Too many unacked stanzas">>),
send_element(StateData, Err),
- send_trailer(StateData),
{stop, normal, StateData#state{mgmt_resend = false}};
fsm_next_state(session_established, #state{mgmt_state = pending} = StateData) ->
fsm_next_state(wait_for_resume, StateData);
@@ -2462,7 +2455,7 @@ is_ip_blacklisted({IP, _Port}, Lang) ->
%% Check from attributes
%% returns invalid-from|NewElement
check_from(El, FromJID) ->
- case xml:get_tag_attr(<<"from">>, El) of
+ case fxml:get_tag_attr(<<"from">>, El) of
false ->
El;
{value, SJID} ->
@@ -2505,6 +2498,41 @@ bounce_messages() ->
after 0 -> ok
end.
+process_compression_request(El, StateName, StateData) ->
+ case fxml:get_subtag(El, <<"method">>) of
+ false ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
+ children =
+ [#xmlel{name = <<"setup-failed">>,
+ attrs = [], children = []}]}),
+ fsm_next_state(StateName, StateData);
+ Method ->
+ case fxml:get_tag_cdata(Method) of
+ <<"zlib">> ->
+ Socket = StateData#state.socket,
+ BCompressed = fxml:element_to_binary(
+ #xmlel{name = <<"compressed">>,
+ attrs = [{<<"xmlns">>,
+ ?NS_COMPRESS}]}),
+ ZlibSocket = (StateData#state.sockmod):compress(
+ Socket, BCompressed),
+ fsm_next_state(wait_for_stream,
+ StateData#state{socket = ZlibSocket,
+ streamid = new_id()});
+ _ ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
+ children =
+ [#xmlel{name = <<"unsupported-method">>,
+ attrs = [],
+ children = []}]}),
+ fsm_next_state(StateName, StateData)
+ end
+ end.
+
%%%----------------------------------------------------------------------
%%% XEP-0191
%%%----------------------------------------------------------------------
@@ -2573,7 +2601,7 @@ negotiate_stream_mgmt(_El, #state{resource = <<"">>} = StateData) ->
send_element(StateData, ?MGMT_UNEXPECTED_REQUEST(?NS_STREAM_MGMT_3)),
StateData;
negotiate_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
Xmlns when ?IS_SUPPORTED_MGMT_XMLNS(Xmlns) ->
case stream_mgmt_enabled(StateData) of
true ->
@@ -2601,7 +2629,7 @@ negotiate_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
end.
perform_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
Xmlns when Xmlns == StateData#state.mgmt_xmlns ->
case Name of
<<"r">> ->
@@ -2626,10 +2654,10 @@ perform_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
handle_enable(#state{mgmt_timeout = DefaultTimeout,
mgmt_max_timeout = MaxTimeout} = StateData, Attrs) ->
- Timeout = case xml:get_attr_s(<<"resume">>, Attrs) of
+ Timeout = case fxml:get_attr_s(<<"resume">>, Attrs) of
ResumeAttr when ResumeAttr == <<"true">>;
ResumeAttr == <<"1">> ->
- MaxAttr = xml:get_attr_s(<<"max">>, Attrs),
+ MaxAttr = fxml:get_attr_s(<<"max">>, Attrs),
case catch jlib:binary_to_integer(MaxAttr) of
Max when is_integer(Max), Max > 0, Max =< MaxTimeout ->
Max;
@@ -2669,7 +2697,7 @@ handle_r(StateData) ->
StateData.
handle_a(StateData, Attrs) ->
- case catch jlib:binary_to_integer(xml:get_attr_s(<<"h">>, Attrs)) of
+ case catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs)) of
H when is_integer(H), H >= 0 ->
check_h_attribute(StateData, H);
_ ->
@@ -2679,12 +2707,12 @@ handle_a(StateData, Attrs) ->
end.
handle_resume(StateData, Attrs) ->
- R = case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ R = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
Xmlns when ?IS_SUPPORTED_MGMT_XMLNS(Xmlns) ->
case stream_mgmt_enabled(StateData) of
true ->
- case {xml:get_attr(<<"previd">>, Attrs),
- catch jlib:binary_to_integer(xml:get_attr_s(<<"h">>, Attrs))}
+ case {fxml:get_attr(<<"previd">>, Attrs),
+ catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs))}
of
{{value, PrevID}, H} when is_integer(H), H >= 0 ->
case inherit_session_state(StateData, PrevID) of
@@ -2815,9 +2843,9 @@ handle_unacked_stanzas(StateData, F)
[N, jid:to_string(StateData#state.jid)]),
lists:foreach(
fun({_, Time, #xmlel{attrs = Attrs} = El}) ->
- From_s = xml:get_attr_s(<<"from">>, Attrs),
+ From_s = fxml:get_attr_s(<<"from">>, Attrs),
From = jid:from_string(From_s),
- To_s = xml:get_attr_s(<<"to">>, Attrs),
+ To_s = fxml:get_attr_s(<<"to">>, Attrs),
To = jid:from_string(To_s),
F(From, To, El, Time)
end, queue:to_list(Queue))
@@ -2833,9 +2861,18 @@ handle_unacked_stanzas(StateData)
Resend when is_boolean(Resend) ->
Resend;
if_offline ->
- ejabberd_sm:get_user_resources(StateData#state.user,
- StateData#state.server) == []
+ Resource = StateData#state.resource,
+ case ejabberd_sm:get_user_resources(StateData#state.user,
+ StateData#state.server) of
+ [Resource] -> % Same resource opened new session
+ true;
+ [] ->
+ true;
+ _ ->
+ false
+ end
end,
+ Lang = StateData#state.lang,
ReRoute = case ResendOnTimeout of
true ->
fun(From, To, El, Time) ->
@@ -2844,9 +2881,11 @@ handle_unacked_stanzas(StateData)
end;
false ->
fun(From, To, El, _Time) ->
+ Txt = <<"User session not found">>,
Err =
- jlib:make_error_reply(El,
- ?ERR_SERVICE_UNAVAILABLE),
+ jlib:make_error_reply(
+ El,
+ ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end
end,
@@ -2854,7 +2893,9 @@ handle_unacked_stanzas(StateData)
?DEBUG("Dropping presence stanza from ~s",
[jid:to_string(From)]);
(From, To, #xmlel{name = <<"iq">>} = El, _Time) ->
- Err = jlib:make_error_reply(El, ?ERR_SERVICE_UNAVAILABLE),
+ Txt = <<"User session not found">>,
+ Err = jlib:make_error_reply(
+ El, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err);
(From, To, El, Time) ->
%% We'll drop the stanza if it was <forwarded/> by some
@@ -2867,7 +2908,7 @@ handle_unacked_stanzas(StateData)
case is_encapsulated_forward(El) of
true ->
?DEBUG("Dropping forwarded message stanza from ~s",
- [xml:get_attr_s(<<"from">>, El#xmlel.attrs)]);
+ [fxml:get_attr_s(<<"from">>, El#xmlel.attrs)]);
false ->
case ejabberd_hooks:run_fold(message_is_archived,
StateData#state.server,
@@ -2886,9 +2927,9 @@ handle_unacked_stanzas(_StateData) ->
ok.
is_encapsulated_forward(#xmlel{name = <<"message">>} = El) ->
- SubTag = case {xml:get_subtag(El, <<"sent">>),
- xml:get_subtag(El, <<"received">>),
- xml:get_subtag(El, <<"result">>)} of
+ SubTag = case {fxml:get_subtag(El, <<"sent">>),
+ fxml:get_subtag(El, <<"received">>),
+ fxml:get_subtag(El, <<"result">>)} of
{false, false, false} ->
false;
{Tag, false, false} ->
@@ -2901,7 +2942,7 @@ is_encapsulated_forward(#xmlel{name = <<"message">>} = El) ->
if SubTag == false ->
false;
true ->
- case xml:get_subtag(SubTag, <<"forwarded">>) of
+ case fxml:get_subtag(SubTag, <<"forwarded">>) of
false ->
false;
_ ->
@@ -2989,7 +3030,7 @@ csi_filter_stanza(#state{csi_state = CsiState, jid = JID} = StateData,
queue -> csi_queue_add(StateData, Stanza);
drop -> StateData;
send ->
- From = xml:get_tag_attr_s(<<"from">>, Stanza),
+ From = fxml:get_tag_attr_s(<<"from">>, Stanza),
StateData1 = csi_queue_send(StateData, From),
StateData2 = send_stanza(StateData1#state{csi_state = active},
Stanza),
@@ -3000,7 +3041,7 @@ csi_queue_add(#state{csi_queue = Queue} = StateData, Stanza) ->
case length(StateData#state.csi_queue) >= csi_max_queue(StateData) of
true -> csi_queue_add(csi_queue_flush(StateData), Stanza);
false ->
- From = xml:get_tag_attr_s(<<"from">>, Stanza),
+ From = fxml:get_tag_attr_s(<<"from">>, Stanza),
NewQueue = lists:keystore(From, 1, Queue, {From, p1_time_compat:timestamp(), Stanza}),
StateData#state{csi_queue = NewQueue}
end.
@@ -3080,6 +3121,12 @@ pack_string(String, Pack) ->
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
+identity(Props) ->
+ case proplists:get_value(authzid, Props, <<>>) of
+ <<>> -> proplists:get_value(username, Props, <<>>);
+ AuthzId -> AuthzId
+ end.
+
opt_type(domain_certfile) -> fun iolist_to_binary/1;
opt_type(max_fsm_queue) ->
fun (I) when is_integer(I), I > 0 -> I end;
diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl
index cc3e2e9f4..157700c47 100644
--- a/src/ejabberd_captcha.erl
+++ b/src/ejabberd_captcha.erl
@@ -320,7 +320,7 @@ build_captcha_html(Id, Lang) ->
-spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}.
process_reply(#xmlel{} = El) ->
- case xml:get_subtag(El, <<"x">>) of
+ case fxml:get_subtag(El, <<"x">>) of
false -> {error, malformed};
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl
index 265d7141f..55ecba5d1 100644
--- a/src/ejabberd_commands.erl
+++ b/src/ejabberd_commands.erl
@@ -90,7 +90,8 @@
%%% PowFloat = math:pow(Base, Exponent),
%%% round(PowFloat).</pre>
%%%
-%%% Since this function will be called by ejabberd_commands, it must be exported.
+%%% Since this function will be called by ejabberd_commands, it must
+%%% be exported.
%%% Add to your module:
%%% <pre>-export([calc_power/2]).</pre>
%%%
@@ -201,24 +202,34 @@
%%% TODO: consider this feature:
%%% All commands are catched. If an error happens, return the restuple:
%%% {error, flattened error string}
-%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc) need to allows this.
-%%% And ejabberd_xmlrpc must be prepared to handle such an unexpected response.
+%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc)
+%%% need to allows this. And ejabberd_xmlrpc must be prepared to
+%%% handle such an unexpected response.
-module(ejabberd_commands).
-author('badlop@process-one.net').
+-define(DEFAULT_VERSION, 1000000).
+
-export([init/0,
list_commands/0,
+ list_commands/1,
get_command_format/1,
- get_command_format/2,
+ get_command_format/2,
+ get_command_format/3,
+ get_command_policy/1,
get_command_definition/1,
+ get_command_definition/2,
get_tags_commands/0,
+ get_tags_commands/1,
get_commands/0,
register_commands/1,
unregister_commands/1,
execute_command/2,
- execute_command/4,
+ execute_command/3,
+ execute_command/4,
+ execute_command/5,
opt_type/1,
get_commands_spec/0
]).
@@ -226,6 +237,7 @@
-include("ejabberd_commands.hrl").
-include("ejabberd.hrl").
-include("logger.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
-define(POLICY_ACCESS, '$policy').
@@ -260,23 +272,26 @@ get_commands_spec() ->
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
result_example = ok}].
init() ->
- ets:new(ejabberd_commands, [named_table, set, public,
- {keypos, #ejabberd_commands.name}]),
+ mnesia:delete_table(ejabberd_commands),
+ mnesia:create_table(ejabberd_commands,
+ [{ram_copies, [node()]},
+ {local_content, true},
+ {attributes, record_info(fields, ejabberd_commands)},
+ {type, bag}]),
+ mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
register_commands(get_commands_spec()).
-spec register_commands([ejabberd_commands()]) -> ok.
%% @doc Register ejabberd commands.
-%% If a command is already registered, a warning is printed and the old command is preserved.
+%% If a command is already registered, a warning is printed and the
+%% old command is preserved.
register_commands(Commands) ->
lists:foreach(
fun(Command) ->
- case ets:insert_new(ejabberd_commands, Command) of
- true ->
- ok;
- false ->
- ?DEBUG("This command is already defined:~n~p", [Command])
- end
+ % XXX check if command exists
+ mnesia:dirty_write(Command)
+ % ?DEBUG("This command is already defined:~n~p", [Command])
end,
Commands).
@@ -286,7 +301,7 @@ register_commands(Commands) ->
unregister_commands(Commands) ->
lists:foreach(
fun(Command) ->
- ets:delete_object(ejabberd_commands, Command)
+ mnesia:dirty_delete_object(Command)
end,
Commands).
@@ -294,94 +309,194 @@ unregister_commands(Commands) ->
%% @doc Get a list of all the available commands, arguments and description.
list_commands() ->
- Commands = ets:match(ejabberd_commands,
- #ejabberd_commands{name = '$1',
- args = '$2',
- desc = '$3',
- _ = '_'}),
- [{A, B, C} || [A, B, C] <- Commands].
-
--spec list_commands_policy() -> [{atom(), [aterm()], string(), atom()}].
-
-%% @doc Get a list of all the available commands, arguments, description, and
-%% policy.
-list_commands_policy() ->
- Commands = ets:match(ejabberd_commands,
- #ejabberd_commands{name = '$1',
- args = '$2',
- desc = '$3',
- policy = '$4',
- _ = '_'}),
- [{A, B, C, D} || [A, B, C, D] <- Commands].
-
--spec get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}.
+ list_commands(?DEFAULT_VERSION).
+
+-spec list_commands(integer()) -> [{atom(), [aterm()], string()}].
+
+%% @doc Get a list of all the available commands, arguments and
+%% description in a given API verion.
+list_commands(Version) ->
+ Commands = get_commands_definition(Version),
+ [{Name, Args, Desc} || #ejabberd_commands{name = Name,
+ args = Args,
+ desc = Desc} <- Commands].
+
+
+-spec list_commands_policy(integer()) ->
+ [{atom(), [aterm()], string(), atom()}].
+
+%% @doc Get a list of all the available commands, arguments,
+%% description, and policy in a given API version.
+list_commands_policy(Version) ->
+ Commands = get_commands_definition(Version),
+ [{Name, Args, Desc, Policy} ||
+ #ejabberd_commands{name = Name,
+ args = Args,
+ desc = Desc,
+ policy = Policy} <- Commands].
+
+-spec get_command_format(atom()) -> {[aterm()], rterm()}.
%% @doc Get the format of arguments and result of a command.
get_command_format(Name) ->
- get_command_format(Name, noauth).
-
-get_command_format(Name, Auth) ->
+ get_command_format(Name, noauth, ?DEFAULT_VERSION).
+get_command_format(Name, Version) when is_integer(Version) ->
+ get_command_format(Name, noauth, Version);
+get_command_format(Name, Auth) ->
+ get_command_format(Name, Auth, ?DEFAULT_VERSION).
+
+-spec get_command_format(atom(),
+ {binary(), binary(), binary(), boolean()} |
+ noauth | admin,
+ integer()) ->
+ {[aterm()], rterm()}.
+
+get_command_format(Name, Auth, Version) ->
Admin = is_admin(Name, Auth),
- Matched = ets:match(ejabberd_commands,
- #ejabberd_commands{name = Name,
- args = '$1',
- result = '$2',
- policy = '$3',
- _ = '_'}),
- case Matched of
- [] ->
- {error, command_unknown};
- [[Args, Result, user]] when Admin;
- Auth == noauth ->
+ #ejabberd_commands{args = Args,
+ result = Result,
+ policy = Policy} =
+ get_command_definition(Name, Version),
+ case Policy of
+ user when Admin;
+ Auth == noauth ->
{[{user, binary}, {server, binary} | Args], Result};
- [[Args, Result, _]] ->
+ _ ->
{Args, Result}
end.
--spec get_command_definition(atom()) -> ejabberd_commands() | command_not_found.
+-spec get_command_policy(atom()) -> {ok, open|user|admin|restricted} | {error, command_not_found}.
+
+%% @doc return command policy.
+get_command_policy(Name) ->
+ case get_command_definition(Name) of
+ #ejabberd_commands{policy = Policy} ->
+ {ok, Policy};
+ command_not_found ->
+ {error, command_not_found}
+ end.
+
+-spec get_command_definition(atom()) -> ejabberd_commands().
%% @doc Get the definition record of a command.
get_command_definition(Name) ->
- case ets:lookup(ejabberd_commands, Name) of
- [E] -> E;
- [] -> command_not_found
+ get_command_definition(Name, ?DEFAULT_VERSION).
+
+-spec get_command_definition(atom(), integer()) -> ejabberd_commands().
+
+%% @doc Get the definition record of a command in a given API version.
+get_command_definition(Name, Version) ->
+ case lists:reverse(
+ lists:sort(
+ mnesia:dirty_select(
+ ejabberd_commands,
+ ets:fun2ms(
+ fun(#ejabberd_commands{name = N, version = V} = C)
+ when N == Name, V =< Version ->
+ {V, C}
+ end)))) of
+ [{_, Command} | _ ] -> Command;
+ _E -> throw(unknown_command)
end.
-%% @spec (Name::atom(), Arguments) -> ResultTerm | {error, command_unknown}
+-spec get_commands_definition(integer()) -> [ejabberd_commands()].
+
+% @doc Returns all commands for a given API version
+get_commands_definition(Version) ->
+ L = lists:reverse(
+ lists:sort(
+ mnesia:dirty_select(
+ ejabberd_commands,
+ ets:fun2ms(
+ fun(#ejabberd_commands{name = Name, version = V} = C)
+ when V =< Version ->
+ {Name, V, C}
+ end)))),
+ F = fun({_Name, _V, Command}, []) ->
+ [Command];
+ ({Name, _V, _Command}, [#ejabberd_commands{name=Name}|_T] = Acc) ->
+ Acc;
+ ({_Name, _V, Command}, Acc) -> [Command | Acc]
+ end,
+ lists:foldl(F, [], L).
+
+%% @spec (Name::atom(), Arguments) -> ResultTerm
+%% where
+%% Arguments = [any()]
%% @doc Execute a command.
+%% Can return the following exceptions:
+%% command_unknown | account_unprivileged | invalid_account_data |
+%% no_auth_provided
execute_command(Name, Arguments) ->
- execute_command([], noauth, Name, Arguments).
+ execute_command(Name, Arguments, ?DEFAULT_VERSION).
--spec execute_command([{atom(), [atom()], [any()]}],
+-spec execute_command(atom(),
+ [any()],
+ integer() |
+ {binary(), binary(), binary(), boolean()} |
+ noauth | admin
+ ) -> any().
+
+%% @spec (Name::atom(), Arguments, integer() | Auth) -> ResultTerm
+%% where
+%% Auth = {User::string(), Server::string(), Password::string(),
+%% Admin::boolean()}
+%% | noauth
+%% | admin
+%% Arguments = [any()]
+%%
+%% @doc Execute a command in a given API version
+%% Can return the following exceptions:
+%% command_unknown | account_unprivileged | invalid_account_data |
+%% no_auth_provided
+execute_command(Name, Arguments, Version) when is_integer(Version) ->
+ execute_command([], noauth, Name, Arguments, Version);
+execute_command(Name, Arguments, Auth) ->
+ execute_command([], Auth, Name, Arguments, ?DEFAULT_VERSION).
+
+%% @spec (AccessCommands, Auth, Name::atom(), Arguments) ->
+%% ResultTerm | {error, Error}
+%% where
+%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
+%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
+%% | noauth
+%% | admin
+%% Arguments = [any()]
+%%
+%% @doc Execute a command
+%% Can return the following exceptions:
+%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
+execute_command(AccessCommands, Auth, Name, Arguments) ->
+ execute_command(AccessCommands, Auth, Name, Arguments, ?DEFAULT_VERSION).
+
+-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
{binary(), binary(), binary(), boolean()} |
noauth | admin,
atom(),
- [any()]
+ [any()],
+ integer()
) -> any().
-%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error}
+%% @spec (AccessCommands, Auth, Name::atom(), Arguments, integer()) -> ResultTerm
%% where
-%% AccessCommands = [{Access, CommandNames, Arguments}]
+%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
%% | noauth
%% | admin
-%% Method = atom()
%% Arguments = [any()]
-%% Error = command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
-execute_command(AccessCommands1, Auth1, Name, Arguments) ->
+%%
+%% @doc Execute a command in a given API version
+%% Can return the following exceptions:
+%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
+execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
Auth = case is_admin(Name, Auth1) of
true -> admin;
false -> Auth1
end,
- case ets:lookup(ejabberd_commands, Name) of
- [Command] ->
- AccessCommands = get_access_commands(AccessCommands1),
- try check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
- ok -> execute_command2(Auth, Command, Arguments)
- catch
- {error, Error} -> {error, Error}
- end;
- [] -> {error, command_unknown}
+ Command = get_command_definition(Name, Version),
+ AccessCommands = get_access_commands(AccessCommands1, Version),
+ case check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
+ ok -> execute_command2(Auth, Command, Arguments)
end.
execute_command2(
@@ -407,26 +522,25 @@ execute_command2(Command, Arguments) ->
Module = Command#ejabberd_commands.module,
Function = Command#ejabberd_commands.function,
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
- try apply(Module, Function, Arguments) of
- Response ->
- Response
- catch
- Problem ->
- {error, Problem}
- end.
+ apply(Module, Function, Arguments).
-spec get_tags_commands() -> [{string(), [string()]}].
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
%% @doc Get all the tags and associated commands.
get_tags_commands() ->
- CommandTags = ets:match(ejabberd_commands,
- #ejabberd_commands{
- name = '$1',
- tags = '$2',
- _ = '_'}),
+ get_tags_commands(?DEFAULT_VERSION).
+
+-spec get_tags_commands(integer()) -> [{string(), [string()]}].
+
+%% @spec (integer) -> [{Tag::string(), [CommandName::string()]}]
+%% @doc Get all the tags and associated commands in a given API version
+get_tags_commands(Version) ->
+ CommandTags = [{Name, Tags} ||
+ #ejabberd_commands{name = Name, tags = Tags}
+ <- get_commands_definition(Version)],
Dict = lists:foldl(
- fun([CommandNameAtom, CTags], D) ->
+ fun({CommandNameAtom, CTags}, D) ->
CommandName = atom_to_list(CommandNameAtom),
case CTags of
[] ->
@@ -445,7 +559,6 @@ get_tags_commands() ->
CommandTags),
orddict:to_list(Dict).
-
%% -----------------------------
%% Access verification
%% -----------------------------
@@ -465,7 +578,7 @@ check_access_commands([], _Auth, _Method, _Command, _Arguments) ->
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
Command =
case {Command1#ejabberd_commands.policy, Auth} of
- {user, {_, _, _}} ->
+ {user, {_, _, _, _}} ->
Command1;
{user, _} ->
Command1#ejabberd_commands{
@@ -479,7 +592,8 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
fun({Access, Commands, ArgumentRestrictions}) ->
case check_access(Command, Access, Auth) of
true ->
- check_access_command(Commands, Command, ArgumentRestrictions,
+ check_access_command(Commands, Command,
+ ArgumentRestrictions,
Method, Arguments);
false ->
false
@@ -488,7 +602,8 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
ArgumentRestrictions = [],
case check_access(Command, Access, Auth) of
true ->
- check_access_command(Commands, Command, ArgumentRestrictions,
+ check_access_command(Commands, Command,
+ ArgumentRestrictions,
Method, Arguments);
false ->
false
@@ -517,7 +632,7 @@ check_auth(Command, {User, Server, {oauth, Token}, _}) ->
end;
check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
%% Check the account exists and password is valid
- case ejabberd_auth:check_password(User, Server, Password) of
+ case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
true -> {ok, User, Server};
_ -> throw({error, invalid_account_data})
end.
@@ -551,9 +666,11 @@ check_access2(Access, User, Server) ->
deny -> false
end.
-check_access_command(Commands, Command, ArgumentRestrictions, Method, Arguments) ->
+check_access_command(Commands, Command, ArgumentRestrictions,
+ Method, Arguments) ->
case Commands==all orelse lists:member(Method, Commands) of
- true -> check_access_arguments(Command, ArgumentRestrictions, Arguments);
+ true -> check_access_arguments(Command, ArgumentRestrictions,
+ Arguments);
false -> false
end.
@@ -577,25 +694,28 @@ tag_arguments(ArgsDefs, Args) ->
Args).
-get_access_commands(undefined) ->
- Cmds = get_commands(),
+get_access_commands(undefined, Version) ->
+ Cmds = get_commands(Version),
[{?POLICY_ACCESS, Cmds, []}];
-get_access_commands(AccessCommands) ->
+get_access_commands(AccessCommands, _Version) ->
AccessCommands.
get_commands() ->
- Opts = ejabberd_config:get_option(
+ get_commands(?DEFAULT_VERSION).
+get_commands(Version) ->
+ Opts0 = ejabberd_config:get_option(
commands,
fun(V) when is_list(V) -> V end,
[]),
- CommandsList = list_commands_policy(),
+ Opts = lists:map(fun(V) when is_tuple(V) -> [V]; (V) -> V end, Opts0),
+ CommandsList = list_commands_policy(Version),
OpenCmds = [N || {N, _, _, open} <- CommandsList],
RestrictedCmds = [N || {N, _, _, restricted} <- CommandsList],
AdminCmds = [N || {N, _, _, admin} <- CommandsList],
UserCmds = [N || {N, _, _, user} <- CommandsList],
Cmds =
lists:foldl(
- fun({add_commands, L}, Acc) ->
+ fun([{add_commands, L}], Acc) ->
Cmds = case L of
open -> OpenCmds;
restricted -> RestrictedCmds;
@@ -604,7 +724,7 @@ get_commands() ->
_ when is_list(L) -> L
end,
lists:usort(Cmds ++ Acc);
- ({remove_commands, L}, Acc) ->
+ ([{remove_commands, L}], Acc) ->
Cmds = case L of
open -> OpenCmds;
restricted -> RestrictedCmds;
diff --git a/src/ejabberd_commands_doc.erl b/src/ejabberd_commands_doc.erl
index 277ed0c78..dc00c5d2a 100644
--- a/src/ejabberd_commands_doc.erl
+++ b/src/ejabberd_commands_doc.erl
@@ -32,7 +32,7 @@
-include("ejabberd_commands.hrl").
-include("ejabberd.hrl").
--define(RAW(V), if HTMLOutput -> xml:crypt(iolist_to_binary(V)); true -> iolist_to_binary(V) end).
+-define(RAW(V), if HTMLOutput -> fxml:crypt(iolist_to_binary(V)); true -> iolist_to_binary(V) end).
-define(TAG(N), if HTMLOutput -> [<<"<", ??N, "/>">>]; true -> md_tag(N, <<"">>) end).
-define(TAG(N, V), if HTMLOutput -> [<<"<", ??N, ">">>, V, <<"</", ??N, ">">>]; true -> md_tag(N, V) end).
-define(TAG(N, C, V), if HTMLOutput -> [<<"<", ??N, " class='", C, "'>">>, V, <<"</", ??N, ">">>]; true -> md_tag(N, V) end).
@@ -79,7 +79,7 @@ md_tag(pre, V) ->
md_tag(p, V) ->
[<<"\n\n">>, V, <<"\n">>];
md_tag(h1, V) ->
- [<<"\\nn## ">>, V, <<"\n">>];
+ [<<"\n\n## ">>, V, <<"\n">>];
md_tag(h2, V) ->
[<<"\n\n### ">>, V, <<"\n">>];
md_tag(strong, V) ->
@@ -158,7 +158,7 @@ java_call(Name, ArgsDesc, Values, HTMLOutput) ->
Indent, ?ID_L("client"), ?OP_L("."), ?ID_L("setConfig"), ?OP_L("("), ?ID_L("config"), ?OP_L(");"), ?BR, Indent, ?BR,
Indent, ?ID_L("client"), ?OP_L("."), ?ID_L("execute"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "),
java_gen_map(lists:map(fun({A,B})->java_gen(A, B, Indent, HTMLOutput) end, lists:zip(ArgsDesc, Values)), Indent, HTMLOutput),
- ?OP_L(");"), ?BR].
+ ?OP_L(");")].
-define(XML_S(N, V), ?OP_L("<"), ?FIELD_L(??N), ?OP_L(">"), V).
-define(XML_E(N), ?OP_L("</"), ?FIELD_L(??N), ?OP_L(">")).
@@ -360,8 +360,9 @@ gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
none ->
[?RAW(io_lib:format("~p", [Result]))];
_ ->
- [?RAW(io_lib:format("~p", [Result])),
- ?TAG_R(p, ResultDesc)]
+ [?TAG(dl, [
+ ?TAG(dt, io_lib:format("~p", [Result])),
+ ?TAG_R(dd, ResultDesc)])]
end,
[?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 8d2d19975..f73474fe7 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -38,6 +38,8 @@
convert_to_yaml/1, convert_to_yaml/2,
env_binary_to_list/2, opt_type/1, may_hide_data/1]).
+-export([start/2]).
+
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_config.hrl").
@@ -52,22 +54,12 @@
%% @type macro_value() = term().
-
start() ->
- case catch mnesia:table_info(local_config, storage_type) of
- disc_copies ->
- mnesia:delete_table(local_config);
- _ ->
- ok
- end,
- mnesia:create_table(local_config,
- [{ram_copies, [node()]},
- {local_content, true},
- {attributes, record_info(fields, local_config)}]),
- mnesia:add_table_copy(local_config, node(), ram_copies),
+ mnesia_init(),
Config = get_ejabberd_config_path(),
State0 = read_file(Config),
- State = validate_opts(State0),
+ State1 = hosts_to_start(State0),
+ State2 = validate_opts(State1),
%% This start time is used by mod_last:
UnixTime = p1_time_compat:system_time(seconds),
SharedKey = case erlang:get_cookie() of
@@ -76,9 +68,45 @@ start() ->
Cookie ->
p1_sha:sha(jlib:atom_to_binary(Cookie))
end,
- State1 = set_option({node_start, global}, UnixTime, State),
- State2 = set_option({shared_key, global}, SharedKey, State1),
- set_opts(State2).
+ State3 = set_option({node_start, global}, UnixTime, State2),
+ State4 = set_option({shared_key, global}, SharedKey, State3),
+ set_opts(State4).
+
+%% When starting ejabberd for testing, we sometimes want to start a
+%% subset of hosts from the one define in the config file.
+%% This function override the host list read from config file by the
+%% one we provide.
+%% Hosts to start are defined in an ejabberd application environment
+%% variable 'hosts' to make it easy to ignore some host in config
+%% file.
+hosts_to_start(State) ->
+ case application:get_env(ejabberd, hosts) of
+ undefined ->
+ %% Start all hosts as defined in config file
+ State;
+ {ok, Hosts} ->
+ set_hosts_in_options(Hosts, State)
+ end.
+
+%% @private
+%% At the moment, these functions are mainly used to setup unit tests.
+-spec(start/2 :: (Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok).
+start(Hosts, Opts) ->
+ mnesia_init(),
+ set_opts(set_hosts_in_options(Hosts, #state{opts = Opts})).
+
+mnesia_init() ->
+ case catch mnesia:table_info(local_config, storage_type) of
+ disc_copies ->
+ mnesia:delete_table(local_config);
+ _ ->
+ ok
+ end,
+ mnesia:create_table(local_config,
+ [{ram_copies, [node()]},
+ {local_content, true},
+ {attributes, record_info(fields, local_config)}]),
+ mnesia:add_table_copy(local_config, node(), ram_copies).
%% @doc Get the filename of the ejabberd configuration file.
%% The filename can be specified with: erl -config "/path/to/ejabberd.yml".
@@ -112,7 +140,7 @@ get_env_config() ->
%% @doc Read the ejabberd configuration file.
%% It also includes additional configuration files and replaces macros.
%% This function will crash if finds some error in the configuration file.
-%% @spec (File::string()) -> #state{}.
+%% @spec (File::string()) -> #state{}
read_file(File) ->
read_file(File, [{replace_macros, true},
{include_files, true},
@@ -162,7 +190,7 @@ convert_to_yaml(File, Output) ->
fun({Host, Opts1}) ->
{host_config, [{Host, Opts1}]}
end, HOpts),
- Data = p1_yaml:encode(lists:reverse(NewOpts)),
+ Data = fast_yaml:encode(lists:reverse(NewOpts)),
case Output of
stdout ->
io:format("~s~n", [Data]);
@@ -226,14 +254,14 @@ get_plain_terms_file(File1, Opts) ->
consult(File) ->
case filename:extension(File) of
Ex when (Ex == ".yml") or (Ex == ".yaml") ->
- case p1_yaml:decode_from_file(File, [plain_as_atom]) of
+ case fast_yaml:decode_from_file(File, [plain_as_atom]) of
{ok, []} ->
{ok, []};
{ok, [Document|_]} ->
{ok, parserl(Document)};
{error, Err} ->
Msg1 = "Cannot load " ++ File ++ ": ",
- Msg2 = p1_yaml:format_error(Err),
+ Msg2 = fast_yaml:format_error(Err),
{error, Msg1 ++ Msg2}
end;
_ ->
@@ -277,7 +305,7 @@ search_hosts(Term, State) ->
{host, Host} ->
if
State#state.hosts == [] ->
- add_hosts_to_option([Host], State);
+ set_hosts_in_options([Host], State);
true ->
?ERROR_MSG("Can't load config file: "
"too many hosts definitions", []),
@@ -286,7 +314,7 @@ search_hosts(Term, State) ->
{hosts, Hosts} ->
if
State#state.hosts == [] ->
- add_hosts_to_option(Hosts, State);
+ set_hosts_in_options(Hosts, State);
true ->
?ERROR_MSG("Can't load config file: "
"too many hosts definitions", []),
@@ -296,9 +324,12 @@ search_hosts(Term, State) ->
State
end.
-add_hosts_to_option(Hosts, State) ->
+set_hosts_in_options(Hosts, State) ->
PrepHosts = normalize_hosts(Hosts),
- set_option({hosts, global}, PrepHosts, State#state{hosts = PrepHosts}).
+ NewOpts = lists:filter(fun({local_config,{hosts,global},_}) -> false;
+ (_) -> true
+ end, State#state.opts),
+ set_option({hosts, global}, PrepHosts, State#state{hosts = PrepHosts, opts = NewOpts}).
normalize_hosts(Hosts) ->
normalize_hosts(Hosts,[]).
@@ -408,7 +439,7 @@ maps_to_lists(IMap) ->
end, [], IMap).
merge_configs(Terms, ResMap) ->
- lists:foldl(fun({Name, Val}, Map) when is_list(Val) ->
+ lists:foldl(fun({Name, Val}, Map) when is_list(Val), Name =/= auth_method ->
Old = maps:get(Name, Map, #{}),
New = lists:foldl(fun(SVal, OMap) ->
NVal = if Name == host_config orelse Name == append_host_config ->
@@ -620,14 +651,27 @@ process_host_term(Term, Host, State, Action) ->
{hosts, _} ->
State;
{Opt, Val} when Action == set ->
- set_option({Opt, Host}, Val, State);
+ set_option({rename_option(Opt), Host}, Val, State);
{Opt, Val} when Action == append ->
- append_option({Opt, Host}, Val, State);
+ append_option({rename_option(Opt), Host}, Val, State);
Opt ->
?WARNING_MSG("Ignore invalid (outdated?) option ~p", [Opt]),
State
end.
+rename_option(Option) when is_atom(Option) ->
+ case atom_to_list(Option) of
+ "odbc_" ++ T ->
+ NewOption = list_to_atom("sql_" ++ T),
+ ?WARNING_MSG("Option '~s' is obsoleted, use '~s' instead",
+ [Option, NewOption]),
+ NewOption;
+ _ ->
+ Option
+ end;
+rename_option(Option) ->
+ Option.
+
set_option(Opt, Val, State) ->
State#state{opts = [#local_config{key = Opt, value = Val} |
State#state.opts]}.
@@ -836,20 +880,20 @@ get_mylang() ->
fun iolist_to_binary/1,
<<"en">>).
-replace_module(mod_announce_odbc) -> {mod_announce, odbc};
-replace_module(mod_blocking_odbc) -> {mod_blocking, odbc};
-replace_module(mod_caps_odbc) -> {mod_caps, odbc};
-replace_module(mod_irc_odbc) -> {mod_irc, odbc};
-replace_module(mod_last_odbc) -> {mod_last, odbc};
-replace_module(mod_muc_odbc) -> {mod_muc, odbc};
-replace_module(mod_offline_odbc) -> {mod_offline, odbc};
-replace_module(mod_privacy_odbc) -> {mod_privacy, odbc};
-replace_module(mod_private_odbc) -> {mod_private, odbc};
-replace_module(mod_roster_odbc) -> {mod_roster, odbc};
-replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
-replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
-replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
-replace_module(mod_pubsub_odbc) -> {mod_pubsub, odbc};
+replace_module(mod_announce_odbc) -> {mod_announce, sql};
+replace_module(mod_blocking_odbc) -> {mod_blocking, sql};
+replace_module(mod_caps_odbc) -> {mod_caps, sql};
+replace_module(mod_irc_odbc) -> {mod_irc, sql};
+replace_module(mod_last_odbc) -> {mod_last, sql};
+replace_module(mod_muc_odbc) -> {mod_muc, sql};
+replace_module(mod_offline_odbc) -> {mod_offline, sql};
+replace_module(mod_privacy_odbc) -> {mod_privacy, sql};
+replace_module(mod_private_odbc) -> {mod_private, sql};
+replace_module(mod_roster_odbc) -> {mod_roster, sql};
+replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql};
+replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
+replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql};
+replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql};
replace_module(Module) ->
case is_elixir_module(Module) of
true -> expand_elixir_module(Module);
@@ -954,7 +998,7 @@ transform_terms(Terms) ->
mod_last,
ejabberd_s2s,
ejabberd_listener,
- ejabberd_odbc_sup,
+ ejabberd_sql_sup,
shaper,
ejabberd_s2s_out,
acl,
diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl
index 2bf20c2e7..bb16e91e8 100644
--- a/src/ejabberd_ctl.erl
+++ b/src/ejabberd_ctl.erl
@@ -57,6 +57,8 @@
-include("ejabberd.hrl").
-include("logger.hrl").
+-define(DEFAULT_VERSION, 1000000).
+
%%-----------------------------
%% Module
@@ -69,7 +71,7 @@ start() ->
[SNode3 | Args3] ->
[SNode3, 60000, Args3];
_ ->
- print_usage(),
+ print_usage(?DEFAULT_VERSION),
halt(?STATUS_USAGE)
end,
SNode1 = case string:tokens(SNode, "@") of
@@ -93,6 +95,9 @@ start() ->
[Node, Reason]),
%% TODO: show minimal start help
?STATUS_BADRPC;
+ {invalid_version, V} ->
+ print("Invalid API version number: ~p~n", [V]),
+ ?STATUS_ERROR;
S ->
S
end,
@@ -126,11 +131,17 @@ unregister_commands(CmdDescs, Module, Function) ->
%% Process
%%-----------------------------
+
-spec process([string()]) -> non_neg_integer().
+process(Args) ->
+ process(Args, ?DEFAULT_VERSION).
+
+
+-spec process([string()], non_neg_integer()) -> non_neg_integer().
%% The commands status, stop and restart are defined here to ensure
%% they are usable even if ejabberd is completely stopped.
-process(["status"]) ->
+process(["status"], _Version) ->
{InternalStatus, ProvidedStatus} = init:get_status(),
print("The node ~p is ~p with status: ~p~n",
[node(), InternalStatus, ProvidedStatus]),
@@ -146,24 +157,24 @@ process(["status"]) ->
?STATUS_SUCCESS
end;
-process(["stop"]) ->
+process(["stop"], _Version) ->
%%ejabberd_cover:stop(),
init:stop(),
?STATUS_SUCCESS;
-process(["restart"]) ->
+process(["restart"], _Version) ->
init:restart(),
?STATUS_SUCCESS;
-process(["mnesia"]) ->
+process(["mnesia"], _Version) ->
print("~p~n", [mnesia:system_info(all)]),
?STATUS_SUCCESS;
-process(["mnesia", "info"]) ->
+process(["mnesia", "info"], _Version) ->
mnesia:info(),
?STATUS_SUCCESS;
-process(["mnesia", Arg]) ->
+process(["mnesia", Arg], _Version) ->
case catch mnesia:system_info(list_to_atom(Arg)) of
{'EXIT', Error} -> print("Error: ~p~n", [Error]);
Return -> print("~p~n", [Return])
@@ -172,23 +183,23 @@ process(["mnesia", Arg]) ->
%% The arguments --long and --dual are not documented because they are
%% automatically selected depending in the number of columns of the shell
-process(["help" | Mode]) ->
+process(["help" | Mode], Version) ->
{MaxC, ShCode} = get_shell_info(),
case Mode of
[] ->
- print_usage(dual, MaxC, ShCode),
+ print_usage(dual, MaxC, ShCode, Version),
?STATUS_USAGE;
["--dual"] ->
- print_usage(dual, MaxC, ShCode),
+ print_usage(dual, MaxC, ShCode, Version),
?STATUS_USAGE;
["--long"] ->
- print_usage(long, MaxC, ShCode),
+ print_usage(long, MaxC, ShCode, Version),
?STATUS_USAGE;
["--tags"] ->
- print_usage_tags(MaxC, ShCode),
+ print_usage_tags(MaxC, ShCode, Version),
?STATUS_SUCCESS;
["--tags", Tag] ->
- print_usage_tags(Tag, MaxC, ShCode),
+ print_usage_tags(Tag, MaxC, ShCode, Version),
?STATUS_SUCCESS;
["help"] ->
print_usage_help(MaxC, ShCode),
@@ -196,13 +207,22 @@ process(["help" | Mode]) ->
[CmdString | _] ->
CmdStringU = ejabberd_regexp:greplace(
list_to_binary(CmdString), <<"-">>, <<"_">>),
- print_usage_commands(binary_to_list(CmdStringU), MaxC, ShCode),
+ print_usage_commands2(binary_to_list(CmdStringU), MaxC, ShCode, Version),
?STATUS_SUCCESS
end;
-process(Args) ->
+process(["--version", Arg | Args], _) ->
+ Version =
+ try
+ list_to_integer(Arg)
+ catch _:_ ->
+ throw({invalid_version, Arg})
+ end,
+ process(Args, Version);
+
+process(Args, Version) ->
AccessCommands = get_accesscommands(),
- {String, Code} = process2(Args, AccessCommands),
+ {String, Code} = process2(Args, AccessCommands, Version),
case String of
[] -> ok;
_ ->
@@ -211,18 +231,25 @@ process(Args) ->
Code.
%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
-process2(["--auth", User, Server, Pass | Args], AccessCommands) ->
- process2(Args, {list_to_binary(User), list_to_binary(Server), list_to_binary(Pass), true}, AccessCommands);
process2(Args, AccessCommands) ->
- process2(Args, admin, AccessCommands).
+ process2(Args, AccessCommands, ?DEFAULT_VERSION).
+
+%% @spec (Args::[string()], AccessCommands, Version) -> {String::string(), Code::integer()}
+process2(["--auth", User, Server, Pass | Args], AccessCommands, Version) ->
+ process2(Args, AccessCommands, {list_to_binary(User), list_to_binary(Server),
+ list_to_binary(Pass), true}, Version);
+process2(Args, AccessCommands, Version) ->
+ process2(Args, AccessCommands, admin, Version).
+
+
-process2(Args, Auth, AccessCommands) ->
- case try_run_ctp(Args, Auth, AccessCommands) of
+process2(Args, AccessCommands, Auth, Version) ->
+ case try_run_ctp(Args, Auth, AccessCommands, Version) of
{String, wrong_command_arguments}
when is_list(String) ->
io:format(lists:flatten(["\n" | String]++["\n"])),
[CommandString | _] = Args,
- process(["help" | [CommandString]]),
+ process(["help" | [CommandString]], Version),
{lists:flatten(String), ?STATUS_ERROR};
{String, Code}
when is_list(String) and is_integer(Code) ->
@@ -246,29 +273,29 @@ get_accesscommands() ->
%%-----------------------------
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
-try_run_ctp(Args, Auth, AccessCommands) ->
+try_run_ctp(Args, Auth, AccessCommands, Version) ->
try ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
false when Args /= [] ->
- try_call_command(Args, Auth, AccessCommands);
+ try_call_command(Args, Auth, AccessCommands, Version);
false ->
- print_usage(),
+ print_usage(Version),
{"", ?STATUS_USAGE};
Status ->
{"", Status}
catch
exit:Why ->
- print_usage(),
+ print_usage(Version),
{io_lib:format("Error in ejabberd ctl process: ~p", [Why]), ?STATUS_USAGE};
Error:Why ->
%% In this case probably ejabberd is not started, so let's show Status
- process(["status"]),
+ process(["status"], Version),
print("~n", []),
{io_lib:format("Error in ejabberd ctl process: '~p' ~p", [Error, Why]), ?STATUS_USAGE}
end.
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
-try_call_command(Args, Auth, AccessCommands) ->
- try call_command(Args, Auth, AccessCommands) of
+try_call_command(Args, Auth, AccessCommands, Version) ->
+ try call_command(Args, Auth, AccessCommands, Version) of
{error, command_unknown} ->
{io_lib:format("Error: command ~p not known.", [hd(Args)]), ?STATUS_ERROR};
{error, wrong_command_arguments} ->
@@ -276,24 +303,28 @@ try_call_command(Args, Auth, AccessCommands) ->
Res ->
Res
catch
+ throw:Error ->
+ {io_lib:format("~p", [Error]), ?STATUS_ERROR};
A:Why ->
Stack = erlang:get_stacktrace(),
{io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", [A, Why, Stack]), ?STATUS_ERROR}
end.
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
-call_command([CmdString | Args], Auth, AccessCommands) ->
+call_command([CmdString | Args], Auth, AccessCommands, Version) ->
CmdStringU = ejabberd_regexp:greplace(
list_to_binary(CmdString), <<"-">>, <<"_">>),
Command = list_to_atom(binary_to_list(CmdStringU)),
- case ejabberd_commands:get_command_format(Command, Auth) of
+ case ejabberd_commands:get_command_format(Command, Auth, Version) of
{error, command_unknown} ->
{error, command_unknown};
{ArgsFormat, ResultFormat} ->
case (catch format_args(Args, ArgsFormat)) of
ArgsFormatted when is_list(ArgsFormatted) ->
- Result = ejabberd_commands:execute_command(AccessCommands, Auth, Command,
- ArgsFormatted),
+ Result = ejabberd_commands:execute_command(AccessCommands,
+ Auth, Command,
+ ArgsFormatted,
+ Version),
format_result(Result, ResultFormat);
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
{NumCompa, TextCompa} =
@@ -324,7 +355,7 @@ format_args(Args, ArgsFormat) ->
format_arg(Arg, integer) ->
format_arg2(Arg, "~d");
format_arg(Arg, binary) ->
- list_to_binary(format_arg(Arg, string));
+ unicode:characters_to_binary(Arg, utf8);
format_arg("", string) ->
"";
format_arg(Arg, string) ->
@@ -349,7 +380,7 @@ format_result(Atom, {_Name, atom}) ->
format_result(Int, {_Name, integer}) ->
io_lib:format("~p", [Int]);
-format_result(String, {_Name, string}) when is_list(String) ->
+format_result([A|_]=String, {_Name, string}) when is_list(String) and is_integer(A) ->
io_lib:format("~s", [String]);
format_result(Binary, {_Name, string}) when is_binary(Binary) ->
@@ -404,8 +435,8 @@ make_status(ok) -> ?STATUS_SUCCESS;
make_status(true) -> ?STATUS_SUCCESS;
make_status(_Error) -> ?STATUS_ERROR.
-get_list_commands() ->
- try ejabberd_commands:list_commands() of
+get_list_commands(Version) ->
+ try ejabberd_commands:list_commands(Version) of
Commands ->
[tuple_command_help(Command)
|| {N,_,_}=Command <- Commands,
@@ -458,10 +489,10 @@ get_list_ctls() ->
-define(U2, "\e[24m").
-define(U(S), case ShCode of true -> [?U1, S, ?U2]; false -> S end).
-print_usage() ->
+print_usage(Version) ->
{MaxC, ShCode} = get_shell_info(),
- print_usage(dual, MaxC, ShCode).
-print_usage(HelpMode, MaxC, ShCode) ->
+ print_usage(dual, MaxC, ShCode, Version).
+print_usage(HelpMode, MaxC, ShCode, Version) ->
AllCommands =
[
{"status", [], "Get ejabberd status"},
@@ -469,12 +500,11 @@ print_usage(HelpMode, MaxC, ShCode) ->
{"restart", [], "Restart ejabberd"},
{"help", ["[--tags [tag] | com?*]"], "Show help (try: ejabberdctl help help)"},
{"mnesia", ["[info]"], "show information of Mnesia system"}] ++
- get_list_commands() ++
+ get_list_commands(Version) ++
get_list_ctls(),
print(
- ["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--auth ",
- ?U("user"), " ", ?U("host"), " ", ?U("password"), "] ",
+ ["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--version ", ?U("api_version"), "] ",
?U("command"), " [", ?U("options"), "]\n"
"\n"
"Available commands in this ejabberd node:\n"], []),
@@ -598,9 +628,9 @@ format_command_lines(CALD, _MaxCmdLen, MaxC, ShCode, long) ->
%% Print Tags
%%-----------------------------
-print_usage_tags(MaxC, ShCode) ->
+print_usage_tags(MaxC, ShCode, Version) ->
print("Available tags and commands:", []),
- TagsCommands = ejabberd_commands:get_tags_commands(),
+ TagsCommands = ejabberd_commands:get_tags_commands(Version),
lists:foreach(
fun({Tag, Commands} = _TagCommands) ->
print(["\n\n ", ?B(Tag), "\n "], []),
@@ -611,10 +641,10 @@ print_usage_tags(MaxC, ShCode) ->
TagsCommands),
print("\n\n", []).
-print_usage_tags(Tag, MaxC, ShCode) ->
+print_usage_tags(Tag, MaxC, ShCode, Version) ->
print(["Available commands with tag ", ?B(Tag), ":", "\n"], []),
HelpMode = long,
- TagsCommands = ejabberd_commands:get_tags_commands(),
+ TagsCommands = ejabberd_commands:get_tags_commands(Version),
CommandsNames = case lists:keysearch(Tag, 1, TagsCommands) of
{value, {Tag, CNs}} -> CNs;
false -> []
@@ -622,7 +652,7 @@ print_usage_tags(Tag, MaxC, ShCode) ->
CommandsList = lists:map(
fun(NameString) ->
C = ejabberd_commands:get_command_definition(
- list_to_atom(NameString)),
+ list_to_atom(NameString), Version),
#ejabberd_commands{name = Name,
args = Args,
desc = Desc} = C,
@@ -673,20 +703,20 @@ print_usage_help(MaxC, ShCode) ->
%%-----------------------------
%% @spec (CmdSubString::string(), MaxC::integer(), ShCode::boolean()) -> ok
-print_usage_commands(CmdSubString, MaxC, ShCode) ->
+print_usage_commands2(CmdSubString, MaxC, ShCode, Version) ->
%% Get which command names match this substring
- AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands()],
+ AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands(Version)],
Cmds = filter_commands(AllCommandsNames, CmdSubString),
case Cmds of
- [] -> io:format("Error: not command found that match: ~p~n", [CmdSubString]);
- _ -> print_usage_commands2(lists:sort(Cmds), MaxC, ShCode)
+ [] -> io:format("Error: no command found that match: ~p~n", [CmdSubString]);
+ _ -> print_usage_commands3(lists:sort(Cmds), MaxC, ShCode, Version)
end.
-print_usage_commands2(Cmds, MaxC, ShCode) ->
+print_usage_commands3(Cmds, MaxC, ShCode, Version) ->
%% Then for each one print it
lists:mapfoldl(
fun(Cmd, Remaining) ->
- print_usage_command(Cmd, MaxC, ShCode),
+ print_usage_command(Cmd, MaxC, ShCode, Version),
case Remaining > 1 of
true -> print([" ", lists:duplicate(MaxC, 126), " \n"], []);
false -> ok
@@ -716,16 +746,16 @@ filter_commands_regexp(All, Glob) ->
All).
%% @spec (Cmd::string(), MaxC::integer(), ShCode::boolean()) -> ok
-print_usage_command(Cmd, MaxC, ShCode) ->
+print_usage_command(Cmd, MaxC, ShCode, Version) ->
Name = list_to_atom(Cmd),
- case ejabberd_commands:get_command_definition(Name) of
+ case ejabberd_commands:get_command_definition(Name, Version) of
command_not_found ->
io:format("Error: command ~p not known.~n", [Cmd]);
C ->
- print_usage_command(Cmd, C, MaxC, ShCode)
+ print_usage_command2(Cmd, C, MaxC, ShCode)
end.
-print_usage_command(Cmd, C, MaxC, ShCode) ->
+print_usage_command2(Cmd, C, MaxC, ShCode) ->
#ejabberd_commands{
tags = TagsAtoms,
desc = Desc,
diff --git a/src/ejabberd_frontend_socket.erl b/src/ejabberd_frontend_socket.erl
index 09eeded9c..b8e706f23 100644
--- a/src/ejabberd_frontend_socket.erl
+++ b/src/ejabberd_frontend_socket.erl
@@ -148,20 +148,20 @@ init([Module, SockMod, Socket, Opts, Receiver]) ->
receiver = Receiver}}.
handle_call({starttls, TLSOpts}, _From, State) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(State#state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(State#state.socket, TLSOpts),
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
Reply = ok,
- {reply, Reply, State#state{socket = TLSSocket, sockmod = p1_tls},
+ {reply, Reply, State#state{socket = TLSSocket, sockmod = fast_tls},
?HIBERNATE_TIMEOUT};
handle_call({starttls, TLSOpts, Data}, _From, State) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(State#state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(State#state.socket, TLSOpts),
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
catch (State#state.sockmod):send(
State#state.socket, Data),
Reply = ok,
{reply, Reply,
- State#state{socket = TLSSocket, sockmod = p1_tls},
+ State#state{socket = TLSSocket, sockmod = fast_tls},
?HIBERNATE_TIMEOUT};
handle_call({compress, Data}, _From, State) ->
{ok, ZlibSocket} =
@@ -187,10 +187,10 @@ handle_call(get_sockmod, _From, State) ->
Reply = State#state.sockmod,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_peer_certificate, _From, State) ->
- Reply = p1_tls:get_peer_certificate(State#state.socket),
+ Reply = fast_tls:get_peer_certificate(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_verify_result, _From, State) ->
- Reply = p1_tls:get_verify_result(State#state.socket),
+ Reply = fast_tls:get_verify_result(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(close, _From, State) ->
ejabberd_receiver:close(State#state.receiver),
@@ -236,9 +236,9 @@ check_starttls(SockMod, Socket, Receiver, Opts) ->
(_) -> false
end, Opts),
if TLSEnabled ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(Socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(Socket, TLSOpts),
ejabberd_receiver:starttls(Receiver, TLSSocket),
- {p1_tls, TLSSocket};
+ {fast_tls, TLSSocket};
true ->
{SockMod, Socket}
end.
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl
index b95f98207..6b53f46c6 100644
--- a/src/ejabberd_http.erl
+++ b/src/ejabberd_http.erl
@@ -117,9 +117,9 @@ init({SockMod, Socket}, Opts) ->
TLSOpts = [verify_none | TLSOpts3],
{SockMod1, Socket1} = if TLSEnabled ->
inet:setopts(Socket, [{recbuf, 8192}]),
- {ok, TLSSocket} = p1_tls:tcp_to_tls(Socket,
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(Socket,
TLSOpts),
- {p1_tls, TLSSocket};
+ {fast_tls, TLSSocket};
true -> {SockMod, Socket}
end,
Captcha = case proplists:get_bool(captcha, Opts) of
@@ -328,8 +328,8 @@ get_transfer_protocol(SockMod, HostPort) ->
{gen_tcp, []} -> {Host, 80, http};
{gen_tcp, [Port]} ->
{Host, jlib:binary_to_integer(Port), http};
- {p1_tls, []} -> {Host, 443, https};
- {p1_tls, [Port]} ->
+ {fast_tls, []} -> {Host, 443, https};
+ {fast_tls, [Port]} ->
{Host, jlib:binary_to_integer(Port), https}
end.
@@ -532,10 +532,10 @@ make_xhtml_output(State, Status, Headers, XHTML) ->
Data = case lists:member(html, Headers) of
true ->
iolist_to_binary([?HTML_DOCTYPE,
- xml:element_to_binary(XHTML)]);
+ fxml:element_to_binary(XHTML)]);
_ ->
iolist_to_binary([?XHTML_DOCTYPE,
- xml:element_to_binary(XHTML)])
+ fxml:element_to_binary(XHTML)])
end,
Headers1 = case lists:keysearch(<<"Content-Type">>, 1,
Headers)
@@ -753,6 +753,7 @@ code_to_phrase(503) -> <<"Service Unavailable">>;
code_to_phrase(504) -> <<"Gateway Timeout">>;
code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
+-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
parse_auth(<<"Basic ", Auth64/binary>>) ->
Auth = jlib:decode_base64(Auth64),
%% Auth should be a string with the format: user@server:password
diff --git a/src/ejabberd_http_bind.erl b/src/ejabberd_http_bind.erl
index c4cfdddd9..ea8cd792f 100644
--- a/src/ejabberd_http_bind.erl
+++ b/src/ejabberd_http_bind.erl
@@ -224,7 +224,7 @@ process_request(Data, IP, HOpts) ->
of
%% No existing session:
{ok, {<<"">>, Rid, Attrs, Payload}} ->
- case xml:get_attr_s(<<"to">>, Attrs) of
+ case fxml:get_attr_s(<<"to">>, Attrs) of
<<"">> ->
?DEBUG("Session not created (Improper addressing)", []),
{200, ?HEADER,
@@ -248,13 +248,13 @@ process_request(Data, IP, HOpts) ->
end;
%% Existing session
{ok, {Sid, Rid, Attrs, Payload1}} ->
- StreamStart = case xml:get_attr_s(<<"xmpp:restart">>,
+ StreamStart = case fxml:get_attr_s(<<"xmpp:restart">>,
Attrs)
of
<<"true">> -> true;
_ -> false
end,
- Payload2 = case xml:get_attr_s(<<"type">>, Attrs) of
+ Payload2 = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"terminate">> ->
Payload1 ++ [{xmlstreamend, <<"stream:stream">>}];
_ -> Payload1
@@ -280,7 +280,7 @@ process_request(Data, IP, HOpts) ->
handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
Payload, PayloadSize, IP) ->
?DEBUG("got pid: ~p", [Pid]),
- Wait = case str:to_integer(xml:get_attr_s(<<"wait">>,
+ Wait = case str:to_integer(fxml:get_attr_s(<<"wait">>,
Attrs))
of
{error, _} -> ?MAX_WAIT;
@@ -289,7 +289,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
true -> CWait
end
end,
- Hold = case str:to_integer(xml:get_attr_s(<<"hold">>,
+ Hold = case str:to_integer(fxml:get_attr_s(<<"hold">>,
Attrs))
of
{error, _} -> (?MAX_REQUESTS) - 1;
@@ -299,7 +299,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
end
end,
Pdelay = case
- str:to_integer(xml:get_attr_s(<<"process-delay">>,
+ str:to_integer(fxml:get_attr_s(<<"process-delay">>,
Attrs))
of
{error, _} -> ?PROCESS_DELAY_DEFAULT;
@@ -312,12 +312,12 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
?PROCESS_DELAY_MIN])
end,
Version = case catch
- list_to_float(binary_to_list(xml:get_attr_s(<<"ver">>, Attrs)))
+ list_to_float(binary_to_list(fxml:get_attr_s(<<"ver">>, Attrs)))
of
{'EXIT', _} -> 0.0;
V -> V
end,
- XmppVersion = xml:get_attr_s(<<"xmpp:version">>, Attrs),
+ XmppVersion = fxml:get_attr_s(<<"xmpp:version">>, Attrs),
?DEBUG("Create session: ~p", [Sid]),
mnesia:dirty_write(
#http_bind{id = Sid,
@@ -589,8 +589,8 @@ process_http_put(#http_put{rid = Rid, attrs = Attrs,
Request,
StateName, StateData, RidAllow) ->
?DEBUG("Actually processing request: ~p", [Request]),
- Key = xml:get_attr_s(<<"key">>, Attrs),
- NewKey = xml:get_attr_s(<<"newkey">>, Attrs),
+ Key = fxml:get_attr_s(<<"key">>, Attrs),
+ NewKey = fxml:get_attr_s(<<"newkey">>, Attrs),
KeyAllow = case RidAllow of
repeat -> true;
false -> false;
@@ -801,7 +801,7 @@ handle_http_put_error(Reason,
case Reason of
not_exists ->
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"type">>, <<"terminate">>},
@@ -810,7 +810,7 @@ handle_http_put_error(Reason,
children = []})};
bad_key ->
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"type">>, <<"terminate">>},
@@ -819,7 +819,7 @@ handle_http_put_error(Reason,
children = []})};
polling_too_frequently ->
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"type">>, <<"terminate">>},
@@ -855,7 +855,7 @@ rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) ->
%% We did not miss any packet, we can process it immediately:
NewRid == OldRid + 1 ->
case catch
- jlib:binary_to_integer(xml:get_attr_s(<<"pause">>,
+ jlib:binary_to_integer(fxml:get_attr_s(<<"pause">>,
Attrs))
of
{'EXIT', _} -> {true, 0};
@@ -929,9 +929,9 @@ prepare_outpacket_response(#http_bind{id = Sid,
_Rid, OutPacket, true) ->
case OutPacket of
[{xmlstreamstart, _, OutAttrs} | Els] ->
- AuthID = xml:get_attr_s(<<"id">>, OutAttrs),
- From = xml:get_attr_s(<<"from">>, OutAttrs),
- Version = xml:get_attr_s(<<"version">>, OutAttrs),
+ AuthID = fxml:get_attr_s(<<"id">>, OutAttrs),
+ From = fxml:get_attr_s(<<"from">>, OutAttrs),
+ Version = fxml:get_attr_s(<<"version">>, OutAttrs),
OutEls = case Els of
[] -> [];
[{xmlstreamelement,
@@ -968,7 +968,7 @@ prepare_outpacket_response(#http_bind{id = Sid,
MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY),
MaxPause = get_max_pause(To),
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"sid">>, Sid},
@@ -1032,7 +1032,7 @@ send_outpacket(#http_bind{pid = FsmRef}, OutPacket) ->
TypedEls = lists:foldl(fun ({xmlstreamelement, El},
Acc) ->
Acc ++
- [xml:element_to_binary(check_default_xmlns(El))];
+ [fxml:element_to_binary(check_default_xmlns(El))];
({xmlstreamraw, R}, Acc) ->
Acc ++ [R]
end,
@@ -1067,7 +1067,7 @@ send_outpacket(#http_bind{pid = FsmRef}, OutPacket) ->
|| {xmlstreamelement, OEl} <- StreamTail]
end,
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>,
?NS_HTTP_BIND}],
@@ -1114,14 +1114,14 @@ parse_request(Data, PayloadSize, MaxStanzaSize) ->
?DEBUG("--- incoming data --- ~n~s~n --- END "
"--- ",
[Data]),
- case xml_stream:parse_element(Data) of
+ case fxml_stream:parse_element(Data) of
#xmlel{name = <<"body">>, attrs = Attrs,
children = Els} ->
- Xmlns = xml:get_attr_s(<<"xmlns">>, Attrs),
+ Xmlns = fxml:get_attr_s(<<"xmlns">>, Attrs),
if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request};
true ->
case catch
- jlib:binary_to_integer(xml:get_attr_s(<<"rid">>,
+ jlib:binary_to_integer(fxml:get_attr_s(<<"rid">>,
Attrs))
of
{'EXIT', _} -> {error, bad_request};
@@ -1133,7 +1133,7 @@ parse_request(Data, PayloadSize, MaxStanzaSize) ->
end
end,
Els),
- Sid = xml:get_attr_s(<<"sid">>, Attrs),
+ Sid = fxml:get_attr_s(<<"sid">>, Attrs),
if PayloadSize =< MaxStanzaSize ->
{ok, {Sid, Rid, Attrs, FixedEls}};
true -> {size_limit, Sid}
@@ -1165,7 +1165,7 @@ set_inactivity_timer(_Pause, MaxInactivity) ->
elements_to_string([]) -> [];
elements_to_string([El | Els]) ->
- [xml:element_to_binary(El) | elements_to_string(Els)].
+ [fxml:element_to_binary(El) | elements_to_string(Els)].
%% @spec (To, Default::integer()) -> integer()
%% where To = [] | {Host::string(), Version::string()}
@@ -1188,7 +1188,7 @@ get_max_pause(_) -> ?MAX_PAUSE.
check_default_xmlns(#xmlel{name = Name, attrs = Attrs,
children = Els} =
El) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
<<"">> ->
#xmlel{name = Name,
attrs = [{<<"xmlns">>, ?NS_CLIENT} | Attrs],
diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl
index 3b50e44a3..e66cf33a5 100644
--- a/src/ejabberd_http_ws.erl
+++ b/src/ejabberd_http_ws.erl
@@ -171,11 +171,11 @@ handle_sync_event({send_xml, Packet}, _From, StateName,
{true, {xmlstreamelement, #xmlel{name=Name2} = El2}} ->
El3 = case Name2 of
<<"stream:", _/binary>> ->
- xml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
+ fxml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
_ ->
- case xml:get_tag_attr_s(<<"xmlns">>, El2) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El2) of
<<"">> ->
- xml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
+ fxml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
_ ->
El2
end
@@ -186,12 +186,12 @@ handle_sync_event({send_xml, Packet}, _From, StateName,
end,
case Packet2 of
{xmlstreamstart, Name, Attrs3} ->
- B = xml:element_to_binary(#xmlel{name = Name, attrs = Attrs3}),
+ B = fxml:element_to_binary(#xmlel{name = Name, attrs = Attrs3}),
WsPid ! {send, <<(binary:part(B, 0, byte_size(B)-2))/binary, ">">>};
{xmlstreamend, Name} ->
WsPid ! {send, <<"</", Name/binary, ">">>};
{xmlstreamelement, El} ->
- WsPid ! {send, xml:element_to_binary(El)};
+ WsPid ! {send, fxml:element_to_binary(El)};
{xmlstreamraw, Bin} ->
WsPid ! {send, Bin};
{xmlstreamcdata, Bin2} ->
@@ -210,7 +210,7 @@ handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}, rfc_compilant
when StateName /= stream_end_sent ->
Close = #xmlel{name = <<"close">>,
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]},
- WsPid ! {send, xml:element_to_binary(Close)},
+ WsPid ! {send, fxml:element_to_binary(Close)},
{stop, normal, StateData};
handle_sync_event(close, _From, _StateName, StateData) ->
{stop, normal, StateData}.
@@ -316,9 +316,9 @@ get_human_html_xmlel() ->
parse(#state{rfc_compilant = C} = State, Data) ->
case C of
undefined ->
- P = xml_stream:new(self()),
- P2 = xml_stream:parse(P, Data),
- xml_stream:close(P2),
+ P = fxml_stream:new(self()),
+ P2 = fxml_stream:parse(P, Data),
+ fxml_stream:close(P2),
case parsed_items([]) of
error ->
{State#state{rfc_compilant = true}, <<"parse error">>};
@@ -330,7 +330,7 @@ parse(#state{rfc_compilant = C} = State, Data) ->
parse(State#state{rfc_compilant = false}, Data)
end;
true ->
- El = xml_stream:parse_element(Data),
+ El = fxml_stream:parse_element(Data),
case El of
#xmlel{name = <<"open">>, attrs = Attrs} ->
Attrs2 = [{<<"xmlns:stream">>, ?NS_STREAM}, {<<"xmlns">>, <<"jabber:client">>} |
diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl
index 66f530c8f..5566073e9 100644
--- a/src/ejabberd_local.erl
+++ b/src/ejabberd_local.erl
@@ -32,7 +32,7 @@
%% API
-export([start_link/0]).
--export([route/3, route_iq/4, route_iq/5,
+-export([route/3, route_iq/4, route_iq/5, process_iq/3,
process_iq_reply/3, register_iq_handler/4,
register_iq_handler/5, register_iq_response_handler/4,
register_iq_response_handler/5, unregister_iq_handler/2,
@@ -74,7 +74,7 @@ start_link() ->
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
- #iq{xmlns = XMLNS} ->
+ #iq{xmlns = XMLNS, lang = Lang} ->
Host = To#jid.lserver,
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function}] ->
@@ -87,8 +87,10 @@ process_iq(From, To, Packet) ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
[] ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
+ 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 ->
@@ -166,8 +168,10 @@ refresh_iq_handlers() ->
ejabberd_local ! refresh_iq_handlers.
bounce_resource_packet(From, To, Packet) ->
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"No available resource found">>,
Err = jlib:make_error_reply(Packet,
- ?ERR_ITEM_NOT_FOUND),
+ ?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
ejabberd_router:route(To, From, Err),
stop.
@@ -178,6 +182,7 @@ bounce_resource_packet(From, To, Packet) ->
init([]) ->
lists:foreach(fun (Host) ->
ejabberd_router:register_route(Host,
+ Host,
{apply, ?MODULE,
route}),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
@@ -272,7 +277,7 @@ do_route(From, To, Packet) ->
end;
true ->
#xmlel{attrs = Attrs} = Packet,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl
index 05499b45c..795d4f390 100644
--- a/src/ejabberd_logger.erl
+++ b/src/ejabberd_logger.erl
@@ -50,6 +50,7 @@
%% "ejabberd.log" in current directory.
%% Note: If the directory where to place the ejabberd log file to not exist,
%% it is not created and no log file will be generated.
+%% @spec () -> string()
get_log_path() ->
case ejabberd_config:env_binary_to_list(ejabberd, log_path) of
{ok, Path} ->
@@ -99,7 +100,33 @@ get_string_env(Name, Default) ->
Default
end.
+%% @spec () -> ok
start() ->
+ StartedApps = application:which_applications(5000),
+ case lists:keyfind(logger, 1, StartedApps) of
+ %% Elixir logger is started. We assume everything is in place
+ %% to use lager to Elixir logger bridge.
+ {logger, _, _} ->
+ error_logger:info_msg("Ignoring ejabberd logger options, using Elixir Logger.", []),
+ %% Do not start lager, we rely on Elixir Logger
+ do_start_for_logger();
+ _ ->
+ do_start()
+ end.
+
+do_start_for_logger() ->
+ application:load(sasl),
+ application:set_env(sasl, sasl_error_logger, false),
+ application:load(lager),
+ application:set_env(lager, error_logger_redirect, false),
+ application:set_env(lager, error_logger_whitelist, ['Elixir.Logger.ErrorHandler']),
+ application:set_env(lager, crash_log, false),
+ application:set_env(lager, handlers, [{elixir_logger_backend, [{level, info}]}]),
+ ejabberd:start_app(lager),
+ ok.
+
+%% Start lager
+do_start() ->
application:load(sasl),
application:set_env(sasl, sasl_error_logger, false),
application:load(lager),
@@ -126,10 +153,12 @@ start() ->
ejabberd:start_app(lager),
ok.
+%% @spec () -> ok
reopen_log() ->
%% Lager detects external log rotation automatically.
ok.
+%% @spec () -> ok
rotate_log() ->
lager_crash_log ! rotate,
lists:foreach(
@@ -139,8 +168,9 @@ rotate_log() ->
ok
end, gen_event:which_handlers(lager_event)).
+%% @spec () -> {loglevel(), atom(), string()}
get() ->
- case lager:get_loglevel(lager_console_backend) of
+ case get_lager_loglevel() of
none -> {0, no_log, "No log"};
emergency -> {1, critical, "Critical"};
alert -> {1, critical, "Critical"};
@@ -152,6 +182,7 @@ get() ->
debug -> {5, debug, "Debug"}
end.
+%% @spec (loglevel() | {loglevel(), list()}) -> {module, module()}
set(LogLevel) when is_integer(LogLevel) ->
LagerLogLevel = case LogLevel of
0 -> none;
@@ -162,7 +193,7 @@ set(LogLevel) when is_integer(LogLevel) ->
5 -> debug;
E -> throw({wrong_loglevel, E})
end,
- case lager:get_loglevel(lager_console_backend) of
+ case get_lager_loglevel() of
LagerLogLevel ->
ok;
_ ->
@@ -172,6 +203,8 @@ set(LogLevel) when is_integer(LogLevel) ->
lager:set_loglevel(H, LagerLogLevel);
(lager_console_backend = H) ->
lager:set_loglevel(H, LagerLogLevel);
+ (elixir_logger_backend = H) ->
+ lager:set_loglevel(H, LagerLogLevel);
(_) ->
ok
end, gen_event:which_handlers(lager_event))
@@ -180,3 +213,22 @@ set(LogLevel) when is_integer(LogLevel) ->
set({_LogLevel, _}) ->
error_logger:error_msg("custom loglevels are not supported for 'lager'"),
{module, lager}.
+
+get_lager_loglevel() ->
+ Handlers = get_lager_handlers(),
+ lists:foldl(fun(lager_console_backend, _Acc) ->
+ lager:get_loglevel(lager_console_backend);
+ (elixir_logger_backend, _Acc) ->
+ lager:get_loglevel(elixir_logger_backend);
+ (_, Acc) ->
+ Acc
+ end,
+ none, Handlers).
+
+get_lager_handlers() ->
+ case catch gen_event:which_handlers(lager_event) of
+ {'EXIT',noproc} ->
+ [];
+ Result ->
+ Result
+ end.
diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl
index a688bb39e..d8e0a435d 100644
--- a/src/ejabberd_oauth.erl
+++ b/src/ejabberd_oauth.erl
@@ -134,7 +134,7 @@ authenticate_user({User, Server}, {password, Password} = Ctx) ->
none),
case acl:match_rule(JID#jid.lserver, Access, JID) of
allow ->
- case ejabberd_auth:check_password(User, Server, Password) of
+ case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
true ->
{ok, {Ctx, {user, User, Server}}};
false ->
@@ -149,7 +149,7 @@ authenticate_user({User, Server}, {password, Password} = Ctx) ->
authenticate_client(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
-verify_resowner_scope({user, User, Server}, Scope, Ctx) ->
+verify_resowner_scope({user, _User, _Server}, Scope, Ctx) ->
Cmds = ejabberd_commands:get_commands(),
Cmds1 = [sasl_auth | Cmds],
RegisteredScope = [atom_to_binary(C, utf8) || C <- Cmds1],
@@ -164,7 +164,7 @@ verify_resowner_scope(_, _, _) ->
{error, badscope}.
-associate_access_code(AccessCode, Context, AppContext) ->
+associate_access_code(_AccessCode, _Context, AppContext) ->
%put(?ACCESS_CODE_TABLE, AccessCode, Context),
{ok, AppContext}.
@@ -184,7 +184,7 @@ associate_access_token(AccessToken, Context, AppContext) ->
mnesia:dirty_write(R),
{ok, AppContext}.
-associate_refresh_token(RefreshToken, Context, AppContext) ->
+associate_refresh_token(_RefreshToken, _Context, AppContext) ->
%put(?REFRESH_TOKEN_TABLE, RefreshToken, Context),
{ok, AppContext}.
@@ -303,7 +303,7 @@ process(_Handlers,
process(_Handlers,
#request{method = 'POST', q = Q, lang = _Lang,
path = [_, <<"authorization_token">>]}) ->
- ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>),
+ _ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>),
ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>),
RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>),
SScope = proplists:get_value(<<"scope">>, Q, <<"">>),
diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl
index 50c8e323c..758001239 100644
--- a/src/ejabberd_piefxis.erl
+++ b/src/ejabberd_piefxis.erl
@@ -28,8 +28,8 @@
%%% Not implemented:
%%% - write mod_piefxis with ejabberdctl commands
-%%% - Export from mod_offline_odbc.erl
-%%% - Export from mod_private_odbc.erl
+%%% - Export from mod_offline_sql.erl
+%%% - Export from mod_private_sql.erl
%%% - XEP-227: 6. Security Considerations
%%% - Other schemas of XInclude are not tested, and may not be imported correctly.
%%% - If a host has many users, split that host in XML files with 50 users each.
@@ -64,7 +64,7 @@
-define(NS_PIEFXIS, <<"http://www.xmpp.org/extensions/xep-0227.html#ns">>).
-define(NS_XI, <<"http://www.w3.org/2001/XInclude">>).
--record(state, {xml_stream_state :: xml_stream:xml_stream_state(),
+-record(state, {xml_stream_state :: fxml_stream:xml_stream_state(),
user = <<"">> :: binary(),
server = <<"">> :: binary(),
fd :: file:io_device(),
@@ -80,12 +80,11 @@ import_file(FileName) ->
import_file(FileName, #state{}).
-spec import_file(binary(), state()) -> ok | {error, atom()}.
-
import_file(FileName, State) ->
case file:open(FileName, [read, binary]) of
{ok, Fd} ->
Dir = filename:dirname(FileName),
- XMLStreamState = xml_stream:new(self(), infinity),
+ XMLStreamState = fxml_stream:new(self(), infinity),
Res = process(State#state{xml_stream_state = XMLStreamState,
fd = Fd,
dir = Dir}),
@@ -97,72 +96,14 @@ import_file(FileName, State) ->
{error, Reason}
end.
-%%%==================================
-%%%% Process Elements
-%%%==================================
-%%%% Process Element
-%%%==================================
-%%%% Add user
-%% @spec (El::xmlel(), Domain::string(), User::binary(), Password::binary() | none)
-%% -> ok | {error, ErrorText::string()}
-%% @doc Add a new user to the database.
-%% If user already exists, it will be only updated.
-spec export_server(binary()) -> any().
-
-%% @spec (User::string(), Password::string(), Domain::string())
-%% -> ok | {atomic, exists} | {error, not_allowed}
-%% @doc Create a new user
export_server(Dir) ->
export_hosts(?MYHOSTS, Dir).
-%%%==================================
-%%%% Populate user
-%% @spec (User::string(), Domain::string(), El::xml())
-%% -> ok | {error, not_found}
-%%
-%% @doc Add a new user from a XML file with a roster list.
-%%
-%% Example of a file:
-%% ```
-%% <?xml version='1.0' encoding='UTF-8'?>
-%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
-%% <host jid='localhost'>
-%% <user name='juliet' password='s3crEt'>
-%% <query xmlns='jabber:iq:roster'>
-%% <item jid='romeo@montague.net'
-%% name='Romeo'
-%% subscription='both'>
-%% <group>Friends</group>
-%% </item>
-%% </query>
-%% </user>
-%% </host>
-%% </server-data>
-%% '''
-spec export_host(binary(), binary()) -> any().
-
export_host(Dir, Host) ->
export_hosts([Host], Dir).
-%% @spec User = String with the user name
-%% Domain = String with a domain name
-%% El = Sub XML element with vCard tags values
-%% @ret ok | {error, not_found}
-%% @doc Read vcards from the XML and send it to the server
-%%
-%% Example:
-%% ```
-%% <?xml version='1.0' encoding='UTF-8'?>
-%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
-%% <host jid='localhost'>
-%% <user name='admin' password='s3crEt'>
-%% <vCard xmlns='vcard-temp'>
-%% <FN>Admin</FN>
-%% </vCard>
-%% </user>
-%% </host>
-%% </server-data>
-%% '''
%%%===================================================================
%%% Internal functions
%%%===================================================================
@@ -194,11 +135,6 @@ export_hosts(Hosts, Dir) ->
{error, Reason}
end.
-%% @spec User = String with the user name
-%% Domain = String with a domain name
-%% El = Sub XML element with offline messages values
-%% @ret ok | {error, not_found}
-%% @doc Read off-line message from the XML and send it to the server
export_host(Dir, FnH, Host) ->
DFn = make_host_basefilename(Dir, FnH),
case file:open(DFn, [raw, write]) of
@@ -223,11 +159,6 @@ export_host(Dir, FnH, Host) ->
{error, Reason}
end.
-%% @spec User = String with the user name
-%% Domain = String with a domain name
-%% El = Sub XML element with private storage values
-%% @ret ok | {error, not_found}
-%% @doc Private storage parsing
export_users([{User, _S}|Users], Server, Fd) ->
case export_user(User, Server, Fd) of
ok ->
@@ -238,8 +169,6 @@ export_users([{User, _S}|Users], Server, Fd) ->
export_users([], _Server, _Fd) ->
ok.
-%%%==================================
-%%%% Utilities
export_user(User, Server, Fd) ->
Password = ejabberd_auth:get_password_s(User, Server),
LServer = jid:nameprep(Server),
@@ -257,7 +186,7 @@ export_user(User, Server, Fd) ->
get_privacy(User, Server) ++
get_roster(User, Server) ++
get_private(User, Server),
- print(Fd, xml:element_to_binary(
+ print(Fd, fxml:element_to_binary(
#xmlel{name = <<"user">>,
attrs = [{<<"name">>, User},
{<<"password">>, Pass}],
@@ -289,7 +218,6 @@ get_vcard(User, Server) ->
[]
end.
-%%%==================================
get_offline(User, Server) ->
case mod_offline:get_offline_els(User, Server) of
[] ->
@@ -306,7 +234,6 @@ get_offline(User, Server) ->
[#xmlel{name = <<"offline-messages">>, children = NewEls}]
end.
-%%%% Export hosts
get_privacy(User, Server) ->
case mod_privacy:get_user_lists(User, Server) of
{ok, #privacy{default = Default,
@@ -333,7 +260,6 @@ get_privacy(User, Server) ->
[]
end.
-%% @spec (Dir::string(), Hosts::[string()]) -> ok
get_roster(User, Server) ->
JID = jid:make(User, Server, <<>>),
case mod_roster:get_roster(User, Server) of
@@ -387,17 +313,17 @@ get_private(User, Server) ->
process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
case file:read(Fd, ?CHUNK_SIZE) of
{ok, Data} ->
- NewXMLStreamState = xml_stream:parse(XMLStreamState, Data),
+ NewXMLStreamState = fxml_stream:parse(XMLStreamState, Data),
case process_els(State#state{xml_stream_state =
NewXMLStreamState}) of
{ok, NewState} ->
process(NewState);
Err ->
- xml_stream:close(NewXMLStreamState),
+ fxml_stream:close(NewXMLStreamState),
Err
end;
eof ->
- xml_stream:close(XMLStreamState),
+ fxml_stream:close(XMLStreamState),
ok
end.
@@ -415,7 +341,7 @@ process_els(State) ->
end.
process_el({xmlstreamstart, <<"server-data">>, Attrs}, State) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_PIEFXIS ->
{ok, State};
?NS_PIE ->
@@ -430,7 +356,7 @@ process_el({xmlstreamcdata, _}, State) ->
process_el({xmlstreamelement, #xmlel{name = <<"xi:include">>,
attrs = Attrs}},
#state{dir = Dir, user = <<"">>} = State) ->
- FileName = xml:get_attr_s(<<"href">>, Attrs),
+ FileName = fxml:get_attr_s(<<"href">>, Attrs),
case import_file(filename:join([Dir, FileName]), State) of
ok ->
{ok, State};
@@ -443,7 +369,7 @@ process_el({xmlstreamstart, <<"host">>, Attrs}, State) ->
process_el({xmlstreamelement, #xmlel{name = <<"host">>,
attrs = Attrs,
children = Els}}, State) ->
- JIDS = xml:get_attr_s(<<"jid">>, Attrs),
+ JIDS = fxml:get_attr_s(<<"jid">>, Attrs),
case jid:from_string(JIDS) of
#jid{lserver = S} ->
case lists:member(S, ?MYHOSTS) of
@@ -486,8 +412,8 @@ process_users([], State) ->
process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
#state{server = LServer} = State) ->
- Name = xml:get_attr_s(<<"name">>, Attrs),
- Password = xml:get_attr_s(<<"password">>, Attrs),
+ Name = fxml:get_attr_s(<<"name">>, Attrs),
+ Password = fxml:get_attr_s(<<"password">>, Attrs),
PasswordFormat = ejabberd_config:get_option({auth_password_format, LServer}, fun(X) -> X end, plain),
Pass = case PasswordFormat of
scram ->
@@ -525,7 +451,7 @@ process_user_els([], State) ->
process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El,
State) ->
- case {Name, xml:get_attr_s(<<"xmlns">>, Attrs)} of
+ case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
{<<"query">>, ?NS_ROSTER} ->
process_roster(El, State);
{<<"query">>, ?NS_PRIVACY} ->
@@ -576,15 +502,13 @@ process_roster(El, State = #state{user = U, server = S}) ->
stop("Failed to write roster: ~p", [Err])
end.
-%%%==================================
-%%%% Export server
process_privacy(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S, <<"">>),
case mod_privacy:process_iq_set(
[], JID, JID, #iq{type = set, sub_el = El}) of
{error, Error} = Err ->
#xmlel{children = Els} = El,
- Name = case xml:remove_cdata(Els) of
+ Name = case fxml:remove_cdata(Els) of
[#xmlel{name = N}] -> N;
_ -> undefined
end,
@@ -603,7 +527,6 @@ process_privacy(El, State = #state{user = U, server = S}) ->
{ok, State}
end.
-%% @spec (Dir::string()) -> ok
process_private(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S, <<"">>),
case mod_private:process_sm_iq(
@@ -614,8 +537,6 @@ process_private(El, State = #state{user = U, server = S}) ->
stop("Failed to write private: ~p", [Err])
end.
-%%%==================================
-%%%% Export host
process_vcard(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S, <<"">>),
case mod_vcard:process_sm_iq(
@@ -626,9 +547,8 @@ process_vcard(El, State = #state{user = U, server = S}) ->
stop("Failed to write vcard: ~p", [Err])
end.
-%% @spec (Dir::string(), Host::string()) -> ok
process_offline_msg(El, State = #state{user = U, server = S}) ->
- FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs),
+ FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
case jid:from_string(FromS) of
#jid{} = From ->
To = jid:make(U, S, <<>>),
@@ -643,9 +563,8 @@ process_offline_msg(El, State = #state{user = U, server = S}) ->
stop("Invalid 'from' = ~s", [FromS])
end.
-%% @spec (Dir::string(), Fn::string(), Host::string()) -> ok
process_presence(El, #state{user = U, server = S} = State) ->
- FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs),
+ FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
case jid:from_string(FromS) of
#jid{} = From ->
To = jid:make(U, S, <<>>),
diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl
index ae405db0a..5224b035b 100644
--- a/src/ejabberd_rdbms.erl
+++ b/src/ejabberd_rdbms.erl
@@ -35,10 +35,10 @@
-include("logger.hrl").
start() ->
- file:delete(ejabberd_odbc:freetds_config()),
- file:delete(ejabberd_odbc:odbc_config()),
- file:delete(ejabberd_odbc:odbcinst_config()),
- case lists:any(fun(H) -> needs_odbc(H) /= false end,
+ file:delete(ejabberd_sql:freetds_config()),
+ file:delete(ejabberd_sql:odbc_config()),
+ file:delete(ejabberd_sql:odbcinst_config()),
+ case lists:any(fun(H) -> needs_sql(H) /= false end,
?MYHOSTS) of
true ->
start_hosts();
@@ -49,34 +49,34 @@ start() ->
%% Start relationnal DB module on the nodes where it is needed
start_hosts() ->
lists:foreach(fun (Host) ->
- case needs_odbc(Host) of
- {true, App} -> start_odbc(Host, App);
+ case needs_sql(Host) of
+ {true, App} -> start_sql(Host, App);
false -> ok
end
end,
?MYHOSTS).
-%% Start the ODBC module on the given host
-start_odbc(Host, App) ->
+%% Start the SQL module on the given host
+start_sql(Host, App) ->
ejabberd:start_app(App),
Supervisor_name = gen_mod:get_module_proc(Host,
- ejabberd_odbc_sup),
+ ejabberd_sql_sup),
ChildSpec = {Supervisor_name,
- {ejabberd_odbc_sup, start_link, [Host]}, transient,
- infinity, supervisor, [ejabberd_odbc_sup]},
+ {ejabberd_sql_sup, start_link, [Host]}, transient,
+ infinity, supervisor, [ejabberd_sql_sup]},
case supervisor:start_child(ejabberd_sup, ChildSpec) of
{ok, _PID} -> ok;
_Error ->
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying."
"..~n",
[Supervisor_name, _Error]),
- start_odbc(Host, App)
+ start_sql(Host, App)
end.
-%% Returns {true, App} if we have configured odbc for the given host
-needs_odbc(Host) ->
+%% Returns {true, App} if we have configured sql for the given host
+needs_sql(Host) ->
LHost = jid:nameprep(Host),
- case ejabberd_config:get_option({odbc_type, LHost},
+ case ejabberd_config:get_option({sql_type, LHost},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
@@ -91,11 +91,11 @@ needs_odbc(Host) ->
undefined -> false
end.
-opt_type(odbc_type) ->
+opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end;
-opt_type(_) -> [odbc_type].
+opt_type(_) -> [sql_type].
diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl
index 785855294..0a33e30ec 100644
--- a/src/ejabberd_receiver.erl
+++ b/src/ejabberd_receiver.erl
@@ -48,15 +48,16 @@
-include("logger.hrl").
-record(state,
- {socket :: inet:socket() | p1_tls:tls_socket() | ezlib:zlib_socket(),
- sock_mod = gen_tcp :: gen_tcp | p1_tls | ezlib,
+ {socket :: inet:socket() | fast_tls:tls_socket() | ezlib:zlib_socket(),
+ sock_mod = gen_tcp :: gen_tcp | fast_tls | ezlib,
shaper_state = none :: shaper:shaper(),
c2s_pid :: pid(),
max_stanza_size = infinity :: non_neg_integer() | infinity,
- xml_stream_state :: xml_stream:xml_stream_state(),
+ xml_stream_state :: fxml_stream:xml_stream_state(),
timeout = infinity:: timeout()}).
--define(HIBERNATE_TIMEOUT, 90000).
+-define(HIBERNATE_TIMEOUT, ejabberd_config:get_option(receiver_hibernate, fun(X) when is_integer(X); X == hibernate-> X end, 90000)).
+
-spec start_link(inet:socket(), atom(), shaper:shaper(),
non_neg_integer() | infinity) -> ignore |
@@ -89,7 +90,7 @@ change_shaper(Pid, Shaper) ->
reset_stream(Pid) -> do_call(Pid, reset_stream).
--spec starttls(pid(), p1_tls:tls_socket()) -> ok.
+-spec starttls(pid(), fast_tls:tls_socket()) -> ok.
starttls(Pid, TLSSocket) ->
do_call(Pid, {starttls, TLSSocket}).
@@ -129,8 +130,8 @@ init([Socket, SockMod, Shaper, MaxStanzaSize]) ->
handle_call({starttls, TLSSocket}, _From, State) ->
State1 = reset_parser(State),
NewState = State1#state{socket = TLSSocket,
- sock_mod = p1_tls},
- case p1_tls:recv_data(TLSSocket, <<"">>) of
+ sock_mod = fast_tls},
+ case fast_tls:recv_data(TLSSocket, <<"">>) of
{ok, TLSData} ->
{reply, ok,
process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
@@ -140,6 +141,7 @@ handle_call({starttls, TLSSocket}, _From, State) ->
handle_call({compress, Data}, _From,
#state{socket = Socket, sock_mod = SockMod} =
State) ->
+ ejabberd:start_app(ezlib),
{ok, ZlibSocket} = ezlib:enable_zlib(SockMod,
Socket),
if Data /= undefined -> do_send(State, Data);
@@ -160,7 +162,7 @@ handle_call(reset_stream, _From, State) ->
Reply = ok,
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
handle_call({become_controller, C2SPid}, _From, State) ->
- XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
+ XMLStreamState = fxml_stream:new(C2SPid, State#state.max_stanza_size),
NewState = State#state{c2s_pid = C2SPid,
xml_stream_state = XMLStreamState},
activate_socket(NewState),
@@ -182,8 +184,8 @@ handle_info({Tag, _TCPSocket, Data},
when (Tag == tcp) or (Tag == ssl) or
(Tag == ejabberd_xml) ->
case SockMod of
- p1_tls ->
- case p1_tls:recv_data(Socket, Data) of
+ fast_tls ->
+ case fast_tls:recv_data(Socket, Data) of
{ok, TLSData} ->
{noreply, process_data(TLSData, State),
?HIBERNATE_TIMEOUT};
@@ -284,7 +286,7 @@ process_data(Data,
undefined ->
XMLStreamState;
_ ->
- xml_stream:parse(XMLStreamState, Data)
+ fxml_stream:parse(XMLStreamState, Data)
end,
{NewShaperState, Pause} = shaper:update(ShaperState, byte_size(Data)),
if
@@ -309,7 +311,7 @@ element_wrapper(Element) -> Element.
close_stream(undefined) -> ok;
close_stream(XMLStreamState) ->
- xml_stream:close(XMLStreamState).
+ fxml_stream:close(XMLStreamState).
reset_parser(#state{xml_stream_state = undefined} = State) ->
State;
@@ -317,14 +319,14 @@ reset_parser(#state{c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize,
xml_stream_state = XMLStreamState}
= State) ->
- NewStreamState = try xml_stream:reset(XMLStreamState)
+ NewStreamState = try fxml_stream:reset(XMLStreamState)
catch error:_ ->
close_stream(XMLStreamState),
case C2SPid of
undefined ->
undefined;
_ ->
- xml_stream:new(C2SPid, MaxStanzaSize)
+ fxml_stream:new(C2SPid, MaxStanzaSize)
end
end,
State#state{xml_stream_state = NewStreamState}.
diff --git a/src/ejabberd_riak.erl b/src/ejabberd_riak.erl
index 22da9d2e9..575810acc 100644
--- a/src/ejabberd_riak.erl
+++ b/src/ejabberd_riak.erl
@@ -28,7 +28,7 @@
-behaviour(gen_server).
%% API
--export([start_link/4, get_proc/1, make_bucket/1, put/2, put/3,
+-export([start_link/5, get_proc/1, make_bucket/1, put/2, put/3,
get/2, get/3, get_by_index/4, delete/1, delete/2,
count_by_index/3, get_by_index_range/5,
get_keys/1, get_keys_by_index/3, is_connected/0,
@@ -68,12 +68,20 @@
%%% API
%%%===================================================================
%% @private
-start_link(Num, Server, Port, _StartInterval) ->
- gen_server:start_link({local, get_proc(Num)}, ?MODULE, [Server, Port], []).
+start_link(Num, Server, Port, _StartInterval, Options) ->
+ gen_server:start_link({local, get_proc(Num)}, ?MODULE, [Server, Port, Options], []).
%% @private
is_connected() ->
- catch riakc_pb_socket:is_connected(get_random_pid()).
+ lists:all(
+ fun({_Id, Pid, _Type, _Modules}) when is_pid(Pid) ->
+ case catch riakc_pb_socket:is_connected(get_riak_pid(Pid)) of
+ true -> true;
+ _ -> false
+ end;
+ (_) ->
+ false
+ end, supervisor:which_children(ejabberd_riak_sup)).
%% @private
get_proc(I) ->
@@ -429,10 +437,8 @@ map_key(Obj, _, _) ->
%%% gen_server API
%%%===================================================================
%% @private
-init([Server, Port]) ->
- case riakc_pb_socket:start(
- Server, Port,
- [auto_reconnect]) of
+init([Server, Port, Options]) ->
+ case riakc_pb_socket:start(Server, Port, Options) of
{ok, Pid} ->
erlang:monitor(process, Pid),
{ok, #state{pid = Pid}};
@@ -517,6 +523,9 @@ make_invalid_object(Val) ->
get_random_pid() ->
PoolPid = ejabberd_riak_sup:get_random_pid(),
+ get_riak_pid(PoolPid).
+
+get_riak_pid(PoolPid) ->
case catch gen_server:call(PoolPid, get_pid) of
{ok, Pid} ->
Pid;
diff --git a/src/ejabberd_riak_sup.erl b/src/ejabberd_riak_sup.erl
index 319d1707d..af811441b 100644
--- a/src/ejabberd_riak_sup.erl
+++ b/src/ejabberd_riak_sup.erl
@@ -103,12 +103,27 @@ init([]) ->
StartInterval = get_start_interval(),
Server = get_riak_server(),
Port = get_riak_port(),
+ CACertFile = get_riak_cacertfile(),
+ Username = get_riak_username(),
+ Password = get_riak_password(),
+ Options = lists:filter(
+ fun(X) -> X /= nil end,
+ [auto_reconnect,
+ {keepalive, true},
+ if CACertFile /= nil -> {cacertfile ,CACertFile};
+ true -> nil
+ end,
+ if (Username /= nil) and (Password /= nil) ->
+ {credentials, Username, Password};
+ true -> nil
+ end
+ ]),
{ok, {{one_for_one, PoolSize*10, 1},
lists:map(
fun(I) ->
{ejabberd_riak:get_proc(I),
{ejabberd_riak, start_link,
- [I, Server, Port, StartInterval*1000]},
+ [I, Server, Port, StartInterval*1000, Options]},
transient, 2000, worker, [?MODULE]}
end, lists:seq(1, PoolSize))}}.
@@ -131,6 +146,27 @@ get_riak_server() ->
binary_to_list(iolist_to_binary(S))
end, ?DEFAULT_RIAK_HOST).
+get_riak_cacertfile() ->
+ ejabberd_config:get_option(
+ riak_cacertfile,
+ fun(S) ->
+ binary_to_list(iolist_to_binary(S))
+ end, nil).
+
+get_riak_username() ->
+ ejabberd_config:get_option(
+ riak_username,
+ fun(S) ->
+ binary_to_list(iolist_to_binary(S))
+ end, nil).
+
+get_riak_password() ->
+ ejabberd_config:get_option(
+ riak_password,
+ fun(S) ->
+ binary_to_list(iolist_to_binary(S))
+ end, nil).
+
get_riak_port() ->
ejabberd_config:get_option(
riak_port,
@@ -162,6 +198,9 @@ opt_type(riak_port) -> fun (_) -> true end;
opt_type(riak_server) -> fun (_) -> true end;
opt_type(riak_start_interval) ->
fun (N) when is_integer(N), N >= 1 -> N end;
+opt_type(riak_cacertfile) -> fun iolist_to_binary/1;
+opt_type(riak_username) -> fun iolist_to_binary/1;
+opt_type(riak_password) -> fun iolist_to_binary/1;
opt_type(_) ->
[modules, riak_pool_size, riak_port, riak_server,
- riak_start_interval].
+ riak_start_interval, riak_cacertfile, riak_username, riak_password].
diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl
index 5ca1262e6..e29d6acfb 100644
--- a/src/ejabberd_router.erl
+++ b/src/ejabberd_router.erl
@@ -36,7 +36,9 @@
route_error/4,
register_route/1,
register_route/2,
+ register_route/3,
register_routes/1,
+ host_of_route/1,
unregister_route/1,
unregister_routes/1,
dirty_get_all_routes/0,
@@ -55,7 +57,7 @@
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
--record(route, {domain, pid, local_hint}).
+-record(route, {domain, server_host, pid, local_hint}).
-record(state, {}).
@@ -86,7 +88,7 @@ route(From, To, Packet) ->
route_error(From, To, ErrPacket, OrigPacket) ->
#xmlel{attrs = Attrs} = OrigPacket,
- case <<"error">> == xml:get_attr_s(<<"type">>, Attrs) of
+ case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
false -> route(From, To, ErrPacket);
true -> ok
end.
@@ -94,19 +96,29 @@ route_error(From, To, ErrPacket, OrigPacket) ->
-spec register_route(binary()) -> term().
register_route(Domain) ->
- register_route(Domain, undefined).
+ ?WARNING_MSG("~s:register_route/1 is deprected, "
+ "use ~s:register_route/2 instead",
+ [?MODULE, ?MODULE]),
+ register_route(Domain, ?MYNAME).
--spec register_route(binary(), local_hint()) -> term().
+-spec register_route(binary(), binary()) -> term().
-register_route(Domain, LocalHint) ->
- case jid:nameprep(Domain) of
- error -> erlang:error({invalid_domain, Domain});
- LDomain ->
+register_route(Domain, ServerHost) ->
+ register_route(Domain, ServerHost, undefined).
+
+-spec register_route(binary(), binary(), local_hint()) -> term().
+
+register_route(Domain, ServerHost, LocalHint) ->
+ case {jid:nameprep(Domain), jid:nameprep(ServerHost)} of
+ {error, _} -> erlang:error({invalid_domain, Domain});
+ {_, error} -> erlang:error({invalid_domain, ServerHost});
+ {LDomain, LServerHost} ->
Pid = self(),
case get_component_number(LDomain) of
undefined ->
F = fun () ->
mnesia:write(#route{domain = LDomain, pid = Pid,
+ server_host = LServerHost,
local_hint = LocalHint})
end,
mnesia:transaction(F);
@@ -115,46 +127,42 @@ register_route(Domain, LocalHint) ->
case mnesia:wread({route, LDomain}) of
[] ->
mnesia:write(#route{domain = LDomain,
+ server_host = LServerHost,
pid = Pid,
local_hint = 1}),
- lists:foreach(fun (I) ->
- mnesia:write(#route{domain
- =
- LDomain,
- pid
- =
- undefined,
- local_hint
- =
- I})
- end,
- lists:seq(2, N));
+ lists:foreach(
+ fun (I) ->
+ mnesia:write(
+ #route{domain = LDomain,
+ pid = undefined,
+ server_host = LServerHost,
+ local_hint = I})
+ end,
+ lists:seq(2, N));
Rs ->
- lists:any(fun (#route{pid = undefined,
- local_hint = I} =
- R) ->
- mnesia:write(#route{domain =
- LDomain,
- pid =
- Pid,
- local_hint
- =
- I}),
- mnesia:delete_object(R),
- true;
- (_) -> false
- end,
- Rs)
+ lists:any(
+ fun (#route{pid = undefined,
+ local_hint = I} = R) ->
+ mnesia:write(
+ #route{domain = LDomain,
+ pid = Pid,
+ server_host = LServerHost,
+ local_hint = I}),
+ mnesia:delete_object(R),
+ true;
+ (_) -> false
+ end,
+ Rs)
end
end,
mnesia:transaction(F)
end
end.
--spec register_routes([binary()]) -> ok.
+-spec register_routes([{binary(), binary()}]) -> ok.
register_routes(Domains) ->
- lists:foreach(fun (Domain) -> register_route(Domain)
+ lists:foreach(fun ({Domain, ServerHost}) -> register_route(Domain, ServerHost)
end,
Domains).
@@ -183,7 +191,9 @@ unregister_route(Domain) ->
of
[R] ->
I = R#route.local_hint,
+ ServerHost = R#route.server_host,
mnesia:write(#route{domain = LDomain,
+ server_host = ServerHost,
pid = undefined,
local_hint = I}),
mnesia:delete_object(R);
@@ -211,6 +221,20 @@ dirty_get_all_routes() ->
dirty_get_all_domains() ->
lists:usort(mnesia:dirty_all_keys(route)).
+-spec host_of_route(binary()) -> binary().
+
+host_of_route(Domain) ->
+ case jid:nameprep(Domain) of
+ error ->
+ erlang:error({invalid_domain, Domain});
+ LDomain ->
+ case mnesia:dirty_read(route, LDomain) of
+ [#route{server_host = ServerHost}|_] ->
+ ServerHost;
+ [] ->
+ erlang:error({unregistered_route, Domain})
+ end
+ end.
%%====================================================================
%% gen_server callbacks
@@ -283,8 +307,11 @@ handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
if is_integer(E#route.local_hint) ->
LDomain = E#route.domain,
I = E#route.local_hint,
+ ServerHost = E#route.server_host,
mnesia:write(#route{domain =
LDomain,
+ server_host =
+ ServerHost,
pid =
undefined,
local_hint =
@@ -394,12 +421,10 @@ get_component_number(LDomain) ->
undefined).
update_tables() ->
- case catch mnesia:table_info(route, attributes) of
- [domain, node, pid] -> mnesia:delete_table(route);
- [domain, pid] -> mnesia:delete_table(route);
- [domain, pid, local_hint] -> ok;
- [domain, pid, local_hint|_] -> mnesia:delete_table(route);
- {'EXIT', _} -> ok
+ try
+ mnesia:transform_table(route, ignore, record_info(fields, route))
+ catch exit:{aborted, {no_exists, _}} ->
+ ok
end,
case lists:member(local_route,
mnesia:system_info(tables))
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index 2f026b32a..be1ee4659 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -214,7 +214,7 @@ check_peer_certificate(SockMod, Sock, Peer) ->
end
end;
VerifyRes ->
- {error, p1_tls:get_cert_verify_string(VerifyRes, Cert)}
+ {error, fast_tls:get_cert_verify_string(VerifyRes, Cert)}
end;
{error, _Reason} ->
{error, <<"Cannot get peer certificate">>};
@@ -308,12 +308,14 @@ do_route(From, To, Packet) ->
#xmlel{name = Name, attrs = NewAttrs, children = Els}),
ok;
{aborted, _Reason} ->
- case xml:get_tag_attr_s(<<"type">>, Packet) of
+ case fxml:get_tag_attr_s(<<"type">>, Packet) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ 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,
false
diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl
index 29dd5e9df..d8d0a400a 100644
--- a/src/ejabberd_s2s_in.erl
+++ b/src/ejabberd_s2s_in.erl
@@ -85,16 +85,16 @@
-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_NAMESPACE_ERR,
- xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
+ fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
- xml:element_to_binary(?SERR_HOST_UNKNOWN)).
+ fxml:element_to_binary(?SERR_HOST_UNKNOWN)).
-define(INVALID_FROM_ERR,
- xml:element_to_binary(?SERR_INVALID_FROM)).
+ fxml:element_to_binary(?SERR_INVALID_FROM)).
-define(INVALID_XML_ERR,
- xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
+ fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
start(SockData, Opts) ->
supervisor:start_child(ejabberd_s2s_in_sup,
@@ -188,10 +188,10 @@ init([{SockMod, Socket}, Opts]) ->
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
- case {xml:get_attr_s(<<"xmlns">>, Attrs),
- xml:get_attr_s(<<"xmlns:db">>, Attrs),
- xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs),
+ fxml:get_attr_s(<<"xmlns:db">>, Attrs),
+ fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
of
{<<"jabber:server">>, _, Server, true}
when StateData#state.tls and
@@ -199,7 +199,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
send_text(StateData,
?STREAM_HEADER(<<" version='1.0'">>)),
Auth = if StateData#state.tls_enabled ->
- case jid:nameprep(xml:get_attr_s(<<"from">>, Attrs)) of
+ case jid:nameprep(fxml:get_attr_s(<<"from">>, Attrs)) of
From when From /= <<"">>, From /= error ->
{Result, Message} =
ejabberd_s2s:check_peer_certificate(StateData#state.sockmod,
@@ -234,7 +234,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
[StateData#state.server, RemoteServer, CertError]),
send_text(StateData,
- <<(xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
+ <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
CertError)))/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
@@ -306,7 +306,7 @@ wait_for_feature_request({xmlstreamelement, El},
TLSEnabled = StateData#state.tls_enabled,
SockMod =
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
- case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
{?NS_TLS, <<"starttls">>}
when TLS == true, TLSEnabled == false,
SockMod == gen_tcp ->
@@ -325,13 +325,13 @@ wait_for_feature_request({xmlstreamelement, El},
{s2s_tls_compression, StateData#state.server},
fun(true) -> true;
(false) -> false
- end, true) of
+ end, false) of
true -> lists:delete(compression_none, TLSOpts1);
false -> [compression_none | TLSOpts1]
end,
TLSSocket = (StateData#state.sockmod):starttls(Socket,
TLSOpts,
- xml:element_to_binary(#xmlel{name
+ fxml:element_to_binary(#xmlel{name
=
<<"proceed">>,
attrs
@@ -345,7 +345,7 @@ wait_for_feature_request({xmlstreamelement, El},
StateData#state{socket = TLSSocket, streamid = new_id(),
tls_enabled = true, tls_options = TLSOpts}};
{?NS_SASL, <<"auth">>} when TLSEnabled ->
- Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
+ Mech = fxml:get_attr_s(<<"mechanism">>, Attrs),
case Mech of
<<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
AuthDomain = StateData#state.auth_domain,
@@ -447,9 +447,9 @@ stream_established({xmlstreamelement, El}, StateData) ->
_ ->
NewEl = jlib:remove_attr(<<"xmlns">>, El),
#xmlel{name = Name, attrs = Attrs} = NewEl,
- From_s = xml:get_attr_s(<<"from">>, Attrs),
+ From_s = fxml:get_attr_s(<<"from">>, Attrs),
From = jid:from_string(From_s),
- To_s = xml:get_attr_s(<<"to">>, Attrs),
+ To_s = fxml:get_attr_s(<<"to">>, Attrs),
To = jid:from_string(To_s),
if (To /= error) and (From /= error) ->
LFrom = From#jid.lserver,
@@ -626,7 +626,7 @@ send_text(StateData, Text) ->
Text).
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
change_shaper(StateData, Host, JID) ->
Shaper = acl:match_rule(Host, StateData#state.shaper,
@@ -643,15 +643,15 @@ cancel_timer(Timer) ->
is_key_packet(#xmlel{name = Name, attrs = Attrs,
children = Els})
when Name == <<"db:result">> ->
- {key, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)};
+ {key, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)};
is_key_packet(#xmlel{name = Name, attrs = Attrs,
children = Els})
when Name == <<"db:verify">> ->
- {verify, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)};
+ {verify, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)};
is_key_packet(_) -> false.
fsm_limit_opts(Opts) ->
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index 696185739..a30f2f438 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -105,13 +105,13 @@
-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_NAMESPACE_ERR,
- xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
+ fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
- xml:element_to_binary(?SERR_HOST_UNKNOWN)).
+ fxml:element_to_binary(?SERR_HOST_UNKNOWN)).
-define(INVALID_XML_ERR,
- xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
+ fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
@@ -192,7 +192,7 @@ init([From, Server, Type]) ->
{s2s_tls_compression, From},
fun(true) -> true;
(false) -> false
- end, true) of
+ end, false) of
false -> [compression_none | TLSOpts4];
true -> TLSOpts4
end,
@@ -323,15 +323,15 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
true ->
{no_verify, <<"Not verified">>, StateData}
end,
- RemoteStreamID = xml:get_attr_s(<<"id">>, Attrs),
+ RemoteStreamID = fxml:get_attr_s(<<"id">>, Attrs),
NewStateData = StateData0#state{remote_streamid = RemoteStreamID},
- case {xml:get_attr_s(<<"xmlns">>, Attrs),
- xml:get_attr_s(<<"xmlns:db">>, Attrs),
- xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs),
+ fxml:get_attr_s(<<"xmlns:db">>, Attrs),
+ fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
of
_ when CertCheckRes == error ->
send_text(NewStateData,
- <<(xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
+ <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
CertCheckMsg)))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)",
@@ -495,7 +495,7 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
STLSReq} =
Acc) ->
case
- xml:get_attr_s(<<"xmlns">>,
+ fxml:get_attr_s(<<"xmlns">>,
Attrs1)
of
?NS_SASL ->
@@ -508,7 +508,7 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
=
Els2}) ->
case
- xml:get_cdata(Els2)
+ fxml:get_cdata(Els2)
of
<<"EXTERNAL">> ->
true;
@@ -533,13 +533,13 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
_STLSReq} =
Acc) ->
case
- xml:get_attr_s(<<"xmlns">>,
+ fxml:get_attr_s(<<"xmlns">>,
Attrs1)
of
?NS_TLS ->
Req =
case
- xml:get_subtag(El1,
+ fxml:get_subtag(El1,
<<"required">>)
of
#xmlel{} ->
@@ -610,7 +610,7 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
end;
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -637,7 +637,7 @@ wait_for_auth_result({xmlstreamelement, El},
StateData) ->
case El of
#xmlel{name = <<"success">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_SASL ->
?DEBUG("auth: ~p",
[{StateData#state.myname, StateData#state.server}]),
@@ -653,7 +653,7 @@ wait_for_auth_result({xmlstreamelement, El},
?FSMTIMEOUT};
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -661,7 +661,7 @@ wait_for_auth_result({xmlstreamelement, El},
{stop, normal, StateData}
end;
#xmlel{name = <<"failure">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_SASL ->
?DEBUG("restarted: ~p",
[{StateData#state.myname, StateData#state.server}]),
@@ -670,7 +670,7 @@ wait_for_auth_result({xmlstreamelement, El},
StateData#state{socket = undefined}, ?FSMTIMEOUT};
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -679,7 +679,7 @@ wait_for_auth_result({xmlstreamelement, El},
end;
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -707,7 +707,7 @@ wait_for_starttls_proceed({xmlstreamelement, El},
StateData) ->
case El of
#xmlel{name = <<"proceed">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_TLS ->
?DEBUG("starttls: ~p",
[{StateData#state.myname, StateData#state.server}]),
@@ -737,7 +737,7 @@ wait_for_starttls_proceed({xmlstreamelement, El},
?FSMTIMEOUT};
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -985,7 +985,7 @@ send_text(StateData, Text) ->
ejabberd_socket:send(StateData#state.socket, Text).
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
send_queue(StateData, Q) ->
case queue:out(Q) of
@@ -997,14 +997,14 @@ send_queue(StateData, Q) ->
%% Bounce a single message (xmlelement)
bounce_element(El, Error) ->
#xmlel{attrs = Attrs} = El,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
Err = jlib:make_error_reply(El, Error),
- From = jid:from_string(xml:get_tag_attr_s(<<"from">>,
+ From = jid:from_string(fxml:get_tag_attr_s(<<"from">>,
El)),
- To = jid:from_string(xml:get_tag_attr_s(<<"to">>,
+ To = jid:from_string(fxml:get_tag_attr_s(<<"to">>,
El)),
ejabberd_router:route(To, From, Err)
end.
@@ -1070,16 +1070,16 @@ send_db_request(StateData) ->
is_verify_res(#xmlel{name = Name, attrs = Attrs})
when Name == <<"db:result">> ->
- {result, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs),
- xml:get_attr_s(<<"type">>, Attrs)};
+ {result, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs),
+ fxml:get_attr_s(<<"type">>, Attrs)};
is_verify_res(#xmlel{name = Name, attrs = Attrs})
when Name == <<"db:verify">> ->
- {verify, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs),
- xml:get_attr_s(<<"type">>, Attrs)};
+ {verify, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs),
+ fxml:get_attr_s(<<"type">>, Attrs)};
is_verify_res(_) -> false.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl
index a9b771b89..465fb587a 100644
--- a/src/ejabberd_service.erl
+++ b/src/ejabberd_service.erl
@@ -91,10 +91,10 @@
"m:error></stream:stream>">>).
-define(INVALID_XML_ERR,
- xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
+ fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-define(INVALID_NS_ERR,
- xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
+ fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
%%%----------------------------------------------------------------------
%%% API
@@ -166,9 +166,9 @@ init([{SockMod, Socket}, Opts]) ->
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"jabber:component:accept">> ->
- To = xml:get_attr_s(<<"to">>, Attrs),
+ To = fxml:get_attr_s(<<"to">>, Attrs),
Host = jid:nameprep(To),
if Host == error ->
Header = io_lib:format(?STREAM_HEADER,
@@ -180,7 +180,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
{stop, normal, StateData};
true ->
Header = io_lib:format(?STREAM_HEADER,
- [StateData#state.streamid, xml:crypt(To)]),
+ [StateData#state.streamid, fxml:crypt(To)]),
send_text(StateData, Header),
HostOpts = case dict:is_key(Host, StateData#state.host_opts) of
true ->
@@ -212,7 +212,7 @@ wait_for_stream(closed, StateData) ->
wait_for_handshake({xmlstreamelement, El}, StateData) ->
#xmlel{name = Name, children = Els} = El,
- case {Name, xml:get_cdata(Els)} of
+ case {Name, fxml:get_cdata(Els)} of
{<<"handshake">>, Digest} ->
case dict:find(StateData#state.host, StateData#state.host_opts) of
{ok, Password} ->
@@ -222,7 +222,7 @@ wait_for_handshake({xmlstreamelement, El}, StateData) ->
send_text(StateData, <<"<handshake/>">>),
lists:foreach(
fun (H) ->
- ejabberd_router:register_route(H),
+ ejabberd_router:register_route(H, ?MYNAME),
?INFO_MSG("Route registered for service ~p~n",
[H])
end, dict:fetch_keys(StateData#state.host_opts)),
@@ -250,7 +250,7 @@ wait_for_handshake(closed, StateData) ->
stream_established({xmlstreamelement, El}, StateData) ->
NewEl = jlib:remove_attr(<<"xmlns">>, El),
#xmlel{name = Name, attrs = Attrs} = NewEl,
- From = xml:get_attr_s(<<"from">>, Attrs),
+ From = fxml:get_attr_s(<<"from">>, Attrs),
FromJID = case StateData#state.check_from of
%% If the admin does not want to check the from field
%% when accept packets from any address.
@@ -269,7 +269,7 @@ stream_established({xmlstreamelement, El}, StateData) ->
_ -> error
end
end,
- To = xml:get_attr_s(<<"to">>, Attrs),
+ To = fxml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of
<<"">> -> error;
_ -> jid:from_string(To)
@@ -280,7 +280,9 @@ stream_established({xmlstreamelement, El}, StateData) ->
and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, NewEl);
true ->
- Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
+ 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
end,
@@ -356,11 +358,13 @@ handle_info({route, From, To, Packet}, StateName,
Attrs2 =
jlib:replace_from_to_attrs(jid:to_string(From),
jid:to_string(To), Attrs),
- Text = xml:element_to_binary(#xmlel{name = Name,
+ Text = fxml:element_to_binary(#xmlel{name = Name,
attrs = Attrs2, children = Els}),
send_text(StateData, Text);
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ 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)
end,
{next_state, StateName, StateData};
@@ -402,7 +406,7 @@ send_text(StateData, Text) ->
Text).
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
new_id() -> randoms:get_string().
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 3045a417b..b7fc39502 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -35,6 +35,7 @@
-export([start/0,
start_link/0,
route/3,
+ process_iq/3,
open_session/5,
open_session/6,
close_session/4,
@@ -50,6 +51,7 @@
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
get_vh_session_number/1,
+ get_vh_by_backend/1,
register_iq_handler/4,
register_iq_handler/5,
unregister_iq_handler/2,
@@ -64,7 +66,8 @@
get_max_user_sessions/2,
get_all_pids/0,
is_existing_resource/3,
- get_commands_spec/0
+ get_commands_spec/0,
+ make_sid/0
]).
-export([init/1, handle_call/3, handle_cast/2,
@@ -133,10 +136,10 @@ open_session(SID, User, Server, Resource, Info) ->
-spec close_session(sid(), binary(), binary(), binary()) -> ok.
close_session(SID, User, Server, Resource) ->
- Mod = get_sm_backend(),
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
Info = case Mod:delete_session(LUser, LServer, LResource, SID) of
{ok, #session{info = I}} -> I;
{error, notfound} -> []
@@ -157,8 +160,10 @@ check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
bounce_offline_message(From, To, Packet) ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"User session not found">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err),
stop.
@@ -172,14 +177,14 @@ disconnect_removed_user(User, Server) ->
get_user_resources(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
[element(3, S#session.usr) || S <- clean_session_list(Ss)].
-spec get_user_present_resources(binary(), binary()) -> [tuple()].
get_user_present_resources(LUser, LServer) ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
[{S#session.priority, element(3, S#session.usr)}
|| S <- clean_session_list(Ss), is_integer(S#session.priority)].
@@ -190,7 +195,7 @@ get_user_ip(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
undefined;
@@ -205,7 +210,7 @@ get_user_info(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
offline;
@@ -255,7 +260,7 @@ get_session_pid(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[#session{sid = {_, Pid}}] -> Pid;
_ -> none
@@ -264,33 +269,40 @@ get_session_pid(User, Server, Resource) ->
-spec dirty_get_sessions_list() -> [ljid()].
dirty_get_sessions_list() ->
- Mod = get_sm_backend(),
- [S#session.usr || S <- Mod:get_sessions()].
+ lists:flatmap(
+ fun(Mod) ->
+ [S#session.usr || S <- Mod:get_sessions()]
+ end, get_sm_backends()).
-spec dirty_get_my_sessions_list() -> [#session{}].
dirty_get_my_sessions_list() ->
- Mod = get_sm_backend(),
- [S || S <- Mod:get_sessions(), node(element(2, S#session.sid)) == node()].
+ lists:flatmap(
+ fun(Mod) ->
+ [S || S <- Mod:get_sessions(),
+ node(element(2, S#session.sid)) == node()]
+ end, get_sm_backends()).
-spec get_vh_session_list(binary()) -> [ljid()].
get_vh_session_list(Server) ->
LServer = jid:nameprep(Server),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
[S#session.usr || S <- Mod:get_sessions(LServer)].
-spec get_all_pids() -> [pid()].
get_all_pids() ->
- Mod = get_sm_backend(),
- [element(2, S#session.sid) || S <- Mod:get_sessions()].
+ lists:flatmap(
+ fun(Mod) ->
+ [element(2, S#session.sid) || S <- Mod:get_sessions()]
+ end, get_sm_backends()).
-spec get_vh_session_number(binary()) -> non_neg_integer().
get_vh_session_number(Server) ->
LServer = jid:nameprep(Server),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
length(Mod:get_sessions(LServer)).
register_iq_handler(Host, XMLNS, Module, Fun) ->
@@ -312,8 +324,7 @@ unregister_iq_handler(Host, XMLNS) ->
%%====================================================================
init([]) ->
- Mod = get_sm_backend(),
- Mod:init(),
+ lists:foreach(fun(Mod) -> Mod:init() end, get_sm_backends()),
ets:new(sm_iqtable, [named_table]),
lists:foreach(
fun(Host) ->
@@ -380,7 +391,7 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
LResource = jid:resourceprep(Resource),
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
Mod:set_session(#session{sid = SID, usr = USR, us = US,
priority = Priority, info = Info}).
@@ -397,7 +408,7 @@ do_route(From, To, {broadcast, _} = Packet) ->
get_user_resources(To#jid.user, To#jid.server));
_ ->
{U, S, R} = jid:tolower(To),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(S),
case Mod:get_sessions(U, S, R) of
[] ->
?DEBUG("packet dropped~n", []);
@@ -415,14 +426,15 @@ do_route(From, To, #xmlel{} = 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 xml:get_attr_s(<<"type">>, Attrs)
+ {Pass, _Subsc} = case fxml:get_attr_s(<<"type">>, Attrs)
of
<<"subscribe">> ->
- Reason = xml:get_path_s(Packet,
+ Reason = fxml:get_path_s(Packet,
[{elem,
<<"status">>},
cdata]),
@@ -483,13 +495,14 @@ do_route(From, To, #xmlel{} = Packet) ->
true -> ok
end;
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
<<"headline">> -> route_message(From, To, Packet, headline);
<<"error">> -> ok;
<<"groupchat">> ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ ErrTxt = <<"Incorrect message type">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err);
_ ->
route_message(From, To, Packet, normal)
@@ -498,28 +511,32 @@ do_route(From, To, #xmlel{} = Packet) ->
_ -> ok
end;
_ ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
case Name of
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
<<"normal">> -> route_message(From, To, Packet, normal);
<<"">> -> route_message(From, To, Packet, normal);
<<"error">> -> ok;
_ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ ErrTxt = <<"Incorrect message type">>,
+ Err = jlib:make_error_reply(
+ Packet,
+ ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err)
end;
<<"iq">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ 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", [])
@@ -565,7 +582,7 @@ route_message(From, To, Packet, Type) ->
lists:foreach(fun ({P, R}) when P == Priority;
(P >= 0) and (Type == headline) ->
LResource = jid:resourceprep(R),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer,
LResource) of
[] ->
@@ -647,11 +664,11 @@ get_resource_sessions(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
[S#session.sid || S <- Mod:get_sessions(LUser, LServer, LResource)].
check_max_sessions(LUser, LServer) ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
SIDs = [S#session.sid || S <- Mod:get_sessions(LUser, LServer)],
MaxSessions = get_max_user_sessions(LUser, LServer),
if length(SIDs) =< MaxSessions -> ok;
@@ -676,7 +693,7 @@ get_max_user_sessions(LUser, Host) ->
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
- #iq{xmlns = XMLNS} ->
+ #iq{xmlns = XMLNS, lang = Lang} ->
Host = To#jid.lserver,
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function}] ->
@@ -689,8 +706,10 @@ process_iq(From, To, Packet) ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
[] ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ 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;
@@ -703,24 +722,38 @@ process_iq(From, To, Packet) ->
-spec force_update_presence({binary(), binary()}) -> any().
force_update_presence({LUser, LServer}) ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
lists:foreach(fun (#session{sid = {_, Pid}}) ->
Pid ! {force_update_presence, LUser, LServer}
end,
Ss).
--spec get_sm_backend() -> module().
+-spec get_sm_backend(binary()) -> module().
-get_sm_backend() ->
- DBType = ejabberd_config:get_option(sm_db_type,
+get_sm_backend(Host) ->
+ DBType = ejabberd_config:get_option({sm_db_type, Host},
fun(mnesia) -> mnesia;
(internal) -> mnesia;
- (odbc) -> odbc;
+ (odbc) -> sql;
+ (sql) -> sql;
(redis) -> redis
end, mnesia),
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
+-spec get_sm_backends() -> [module()].
+
+get_sm_backends() ->
+ lists:usort([get_sm_backend(Host) || Host <- ?MYHOSTS]).
+
+-spec get_vh_by_backend(module()) -> [binary()].
+
+get_vh_by_backend(Mod) ->
+ lists:filter(
+ fun(Host) ->
+ get_sm_backend(Host) == Mod
+ end, ?MYHOSTS).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% ejabberd commands
@@ -775,10 +808,14 @@ kick_user(User, Server) ->
end, Resources),
length(Resources).
+make_sid() ->
+ {p1_time_compat:unique_timestamp(), self()}.
+
opt_type(sm_db_type) ->
fun (mnesia) -> mnesia;
(internal) -> mnesia;
- (odbc) -> odbc;
+ (sql) -> sql;
+ (odbc) -> sql;
(redis) -> redis
end;
opt_type(_) -> [sm_db_type].
diff --git a/src/ejabberd_sm_redis.erl b/src/ejabberd_sm_redis.erl
index 637e13647..bf9e0eff5 100644
--- a/src/ejabberd_sm_redis.erl
+++ b/src/ejabberd_sm_redis.erl
@@ -107,7 +107,7 @@ get_sessions() ->
lists:flatmap(
fun(LServer) ->
get_sessions(LServer)
- end, ?MYHOSTS).
+ end, ejabberd_sm:get_vh_by_backend(?MODULE)).
-spec get_sessions(binary()) -> [#session{}].
get_sessions(LServer) ->
@@ -204,7 +204,7 @@ clean_table() ->
?ERROR_MSG("failed to clean redis table for "
"server ~s: ~p", [LServer, Err])
end
- end, ?MYHOSTS).
+ end, ejabberd_sm:get_vh_by_backend(?MODULE)).
opt_type(redis_connect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
diff --git a/src/ejabberd_sm_odbc.erl b/src/ejabberd_sm_sql.erl
index debeafe99..3d4e224d4 100644
--- a/src/ejabberd_sm_odbc.erl
+++ b/src/ejabberd_sm_sql.erl
@@ -6,7 +6,7 @@
%%% @end
%%% Created : 9 Mar 2015 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
--module(ejabberd_sm_odbc).
+-module(ejabberd_sm_sql).
-behaviour(ejabberd_sm).
@@ -29,11 +29,11 @@
%%%===================================================================
-spec init() -> ok | {error, any()}.
init() ->
- Node = ejabberd_odbc:escape(jlib:atom_to_binary(node())),
+ Node = ejabberd_sql:escape(jlib:atom_to_binary(node())),
?INFO_MSG("Cleaning SQL SM table...", []),
lists:foldl(
fun(Host, ok) ->
- case ejabberd_odbc:sql_query(
+ case ejabberd_sql:sql_query(
Host, [<<"delete from sm where node='">>, Node, <<"'">>]) of
{updated, _} ->
ok;
@@ -43,18 +43,18 @@ init() ->
end;
(_, Err) ->
Err
- end, ok, ?MYHOSTS).
+ end, ok, ejabberd_sm:get_vh_by_backend(?MODULE)).
set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
priority = Priority, info = Info}) ->
- Username = ejabberd_odbc:escape(U),
- Resource = ejabberd_odbc:escape(R),
- InfoS = ejabberd_odbc:encode_term(Info),
+ Username = ejabberd_sql:escape(U),
+ Resource = ejabberd_sql:escape(R),
+ InfoS = ejabberd_sql:encode_term(Info),
PrioS = enc_priority(Priority),
TS = now_to_timestamp(Now),
PidS = list_to_binary(erlang:pid_to_list(Pid)),
- Node = ejabberd_odbc:escape(jlib:atom_to_binary(node(Pid))),
- case odbc_queries:update(
+ Node = ejabberd_sql:escape(jlib:atom_to_binary(node(Pid))),
+ case sql_queries:update(
LServer,
<<"sm">>,
[<<"usec">>, <<"pid">>, <<"node">>, <<"username">>,
@@ -70,12 +70,12 @@ set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
delete_session(_LUser, LServer, _LResource, {Now, Pid}) ->
TS = now_to_timestamp(Now),
PidS = list_to_binary(erlang:pid_to_list(Pid)),
- case ejabberd_odbc:sql_query(
+ case ejabberd_sql:sql_query(
LServer,
[<<"select usec, pid, username, resource, priority, info ">>,
<<"from sm where usec='">>, TS, <<"' and pid='">>,PidS, <<"'">>]) of
{selected, _, [Row]} ->
- ejabberd_odbc:sql_query(
+ ejabberd_sql:sql_query(
LServer, [<<"delete from sm where usec='">>,
TS, <<"' and pid='">>, PidS, <<"'">>]),
{ok, row_to_session(LServer, Row)};
@@ -90,10 +90,10 @@ get_sessions() ->
lists:flatmap(
fun(LServer) ->
get_sessions(LServer)
- end, ?MYHOSTS).
+ end, ejabberd_sm:get_vh_by_backend(?MODULE)).
get_sessions(LServer) ->
- case ejabberd_odbc:sql_query(
+ case ejabberd_sql:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm">>]) of
{selected, _, Rows} ->
@@ -104,8 +104,8 @@ get_sessions(LServer) ->
end.
get_sessions(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- case ejabberd_odbc:sql_query(
+ Username = ejabberd_sql:escape(LUser),
+ case ejabberd_sql:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm where ">>,
<<"username='">>, Username, <<"'">>]) of
@@ -117,9 +117,9 @@ get_sessions(LUser, LServer) ->
end.
get_sessions(LUser, LServer, LResource) ->
- Username = ejabberd_odbc:escape(LUser),
- Resource = ejabberd_odbc:escape(LResource),
- case ejabberd_odbc:sql_query(
+ Username = ejabberd_sql:escape(LUser),
+ Resource = ejabberd_sql:escape(LResource),
+ case ejabberd_sql:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm where ">>,
<<"username='">>, Username, <<"' and resource='">>,
@@ -162,7 +162,7 @@ row_to_session(LServer, [USec, PidS, User, Resource, PrioS, InfoS]) ->
Now = timestamp_to_now(USec),
Pid = erlang:list_to_pid(binary_to_list(PidS)),
Priority = dec_priority(PrioS),
- Info = ejabberd_odbc:decode_term(InfoS),
+ Info = ejabberd_sql:decode_term(InfoS),
#session{sid = {Now, Pid}, us = {User, LServer},
usr = {User, LServer, Resource},
priority = Priority,
diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl
index 16ffd192e..887b4a0f3 100644
--- a/src/ejabberd_socket.erl
+++ b/src/ejabberd_socket.erl
@@ -52,10 +52,10 @@
-type sockmod() :: ejabberd_http_bind |
ejabberd_http_ws |
- gen_tcp | p1_tls | ezlib.
+ gen_tcp | fast_tls | ezlib.
-type receiver() :: pid () | atom().
-type socket() :: pid() | inet:socket() |
- p1_tls:tls_socket() |
+ fast_tls:tls_socket() |
ezlib:zlib_socket() |
ejabberd_http_bind:bind_socket().
@@ -145,15 +145,15 @@ connect(Addr, Port, Opts, Timeout) ->
end.
starttls(SocketData, TLSOpts) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
ejabberd_receiver:starttls(SocketData#socket_state.receiver, TLSSocket),
- SocketData#socket_state{socket = TLSSocket, sockmod = p1_tls}.
+ SocketData#socket_state{socket = TLSSocket, sockmod = fast_tls}.
starttls(SocketData, TLSOpts, Data) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
ejabberd_receiver:starttls(SocketData#socket_state.receiver, TLSSocket),
send(SocketData, Data),
- SocketData#socket_state{socket = TLSSocket, sockmod = p1_tls}.
+ SocketData#socket_state{socket = TLSSocket, sockmod = fast_tls}.
compress(SocketData) -> compress(SocketData, undefined).
@@ -216,10 +216,10 @@ get_sockmod(SocketData) ->
SocketData#socket_state.sockmod.
get_peer_certificate(SocketData) ->
- p1_tls:get_peer_certificate(SocketData#socket_state.socket).
+ fast_tls:get_peer_certificate(SocketData#socket_state.socket).
get_verify_result(SocketData) ->
- p1_tls:get_verify_result(SocketData#socket_state.socket).
+ fast_tls:get_verify_result(SocketData#socket_state.socket).
close(SocketData) ->
ejabberd_receiver:close(SocketData#socket_state.receiver).
diff --git a/src/ejabberd_odbc.erl b/src/ejabberd_sql.erl
index a15c66b5d..4bee08f7e 100644
--- a/src/ejabberd_odbc.erl
+++ b/src/ejabberd_sql.erl
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(ejabberd_odbc).
+-module(ejabberd_sql).
-behaviour(ejabberd_config).
@@ -41,6 +41,7 @@
sql_bloc/2,
escape/1,
escape_like/1,
+ escape_like_arg/1,
to_bool/1,
sqlite_db/1,
sqlite_file/1,
@@ -63,18 +64,20 @@
-include("ejabberd.hrl").
-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
-record(state,
{db_ref = self() :: pid(),
db_type = odbc :: pgsql | mysql | sqlite | odbc | mssql,
+ db_version = undefined :: undefined | non_neg_integer(),
start_interval = 0 :: non_neg_integer(),
host = <<"">> :: binary(),
max_pending_requests_len :: non_neg_integer(),
pending_requests = {0, queue:new()} :: {non_neg_integer(), ?TQUEUE}}).
--define(STATE_KEY, ejabberd_odbc_state).
+-define(STATE_KEY, ejabberd_sql_state).
--define(NESTING_KEY, ejabberd_odbc_nesting_level).
+-define(NESTING_KEY, ejabberd_sql_nesting_level).
-define(TOP_LEVEL_TXN, 0).
@@ -92,6 +95,8 @@
-define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]).
+-define(PREPARE_KEY, ejabberd_sql_prepare).
+
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
@@ -108,19 +113,21 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
- (?GEN_FSM):start(ejabberd_odbc, [Host],
+ (?GEN_FSM):start(ejabberd_sql, [Host],
fsm_limit_opts() ++ (?FSMOPTS)).
start_link(Host, StartInterval) ->
- (?GEN_FSM):start_link(ejabberd_odbc,
+ (?GEN_FSM):start_link(ejabberd_sql,
[Host, StartInterval],
fsm_limit_opts() ++ (?FSMOPTS)).
--type sql_query() :: [sql_query() | binary()].
+-type sql_query() :: [sql_query() | binary()] | #sql_query{} |
+ fun(() -> any()) | fun((atom(), _) -> any()).
-type sql_query_result() :: {updated, non_neg_integer()} |
{error, binary()} |
{selected, [binary()],
- [[binary()]]}.
+ [[binary()]]} |
+ {selected, [any()]}.
-spec sql_query(binary(), sql_query()) -> sql_query_result().
@@ -150,7 +157,7 @@ sql_bloc(Host, F) -> sql_call(Host, {sql_bloc, F}).
sql_call(Host, Msg) ->
case get(?STATE_KEY) of
undefined ->
- case ejabberd_odbc_sup:get_random_pid(Host) of
+ case ejabberd_sql_sup:get_random_pid(Host) of
none -> {error, <<"Unknown Host">>};
Pid ->
(?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg,
@@ -183,7 +190,7 @@ sql_query_t(Query) ->
%% Escape character that will confuse an SQL engine
escape(S) ->
- << <<(odbc_queries:escape(Char))/binary>> || <<Char>> <= S >>.
+ << <<(sql_queries:escape(Char))/binary>> || <<Char>> <= S >>.
%% Escape character that will confuse an SQL engine
%% Percent and underscore only need to be escaped for pattern matching like
@@ -192,7 +199,14 @@ escape_like(S) when is_binary(S) ->
<< <<(escape_like(C))/binary>> || <<C>> <= S >>;
escape_like($%) -> <<"\\%">>;
escape_like($_) -> <<"\\_">>;
-escape_like(C) when is_integer(C), C >= 0, C =< 255 -> odbc_queries:escape(C).
+escape_like(C) when is_integer(C), C >= 0, C =< 255 -> sql_queries:escape(C).
+
+escape_like_arg(S) when is_binary(S) ->
+ << <<(escape_like_arg(C))/binary>> || <<C>> <= S >>;
+escape_like_arg($%) -> <<"\\%">>;
+escape_like_arg($_) -> <<"\\_">>;
+escape_like_arg($\\) -> <<"\\\\">>;
+escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>.
to_bool(<<"t">>) -> true;
to_bool(<<"true">>) -> true;
@@ -218,7 +232,7 @@ sqlite_db(Host) ->
-spec sqlite_file(binary()) -> string().
sqlite_file(Host) ->
- case ejabberd_config:get_option({odbc_database, Host},
+ case ejabberd_config:get_option({sql_database, Host},
fun iolist_to_binary/1) of
undefined ->
{ok, Cwd} = file:get_cwd(),
@@ -233,7 +247,7 @@ sqlite_file(Host) ->
%%%----------------------------------------------------------------------
init([Host, StartInterval]) ->
case ejabberd_config:get_option(
- {odbc_keepalive_interval, Host},
+ {sql_keepalive_interval, Host},
fun(I) when is_integer(I), I>0 -> I end) of
undefined ->
ok;
@@ -243,7 +257,7 @@ init([Host, StartInterval]) ->
end,
[DBType | _] = db_opts(Host),
(?GEN_FSM):send_event(self(), connect),
- ejabberd_odbc_sup:add_pid(Host, self()),
+ ejabberd_sql_sup:add_pid(Host, self()),
{ok, connecting,
#state{db_type = DBType, host = Host,
max_pending_requests_len = max_fsm_queue(),
@@ -255,19 +269,21 @@ connecting(connect, #state{host = Host} = State) ->
[mysql | Args] -> apply(fun mysql_connect/5, Args);
[pgsql | Args] -> apply(fun pgsql_connect/5, Args);
[sqlite | Args] -> apply(fun sqlite_connect/1, Args);
+ [mssql | Args] -> apply(fun odbc_connect/1, Args);
[odbc | Args] -> apply(fun odbc_connect/1, Args)
end,
{_, PendingRequests} = State#state.pending_requests,
case ConnectRes of
- {ok, Ref} ->
- erlang:monitor(process, Ref),
- lists:foreach(fun (Req) ->
- (?GEN_FSM):send_event(self(), Req)
- end,
- queue:to_list(PendingRequests)),
- {next_state, session_established,
- State#state{db_ref = Ref,
- pending_requests = {0, queue:new()}}};
+ {ok, Ref} ->
+ erlang:monitor(process, Ref),
+ lists:foreach(fun (Req) ->
+ (?GEN_FSM):send_event(self(), Req)
+ end,
+ queue:to_list(PendingRequests)),
+ State1 = State#state{db_ref = Ref,
+ pending_requests = {0, queue:new()}},
+ State2 = get_db_version(State1),
+ {next_state, session_established, State2};
{error, Reason} ->
?INFO_MSG("~p connection failed:~n** Reason: ~p~n** "
"Retry after: ~p seconds",
@@ -355,7 +371,7 @@ handle_info(Info, StateName, State) ->
{next_state, StateName, State}.
terminate(_Reason, _StateName, State) ->
- ejabberd_odbc_sup:remove_pid(State#state.host, self()),
+ ejabberd_sql_sup:remove_pid(State#state.host, self()),
case State#state.db_type of
mysql -> catch p1_mysql_conn:stop(State#state.db_ref);
sqlite -> catch sqlite3:close(sqlite_db(State#state.host));
@@ -469,12 +485,82 @@ execute_bloc(F) ->
Res -> {atomic, Res}
end.
+execute_fun(F) when is_function(F, 0) ->
+ F();
+execute_fun(F) when is_function(F, 2) ->
+ State = get(?STATE_KEY),
+ F(State#state.db_type, State#state.db_version).
+
+sql_query_internal([{_, _} | _] = Queries) ->
+ State = get(?STATE_KEY),
+ case select_sql_query(Queries, State) of
+ undefined ->
+ {error, <<"no matching query for the current DBMS found">>};
+ Query ->
+ sql_query_internal(Query)
+ end;
+sql_query_internal(#sql_query{} = Query) ->
+ State = get(?STATE_KEY),
+ Res =
+ try
+ case State#state.db_type of
+ odbc ->
+ generic_sql_query(Query);
+ mssql ->
+ generic_sql_query(Query);
+ pgsql ->
+ Key = {?PREPARE_KEY, Query#sql_query.hash},
+ case get(Key) of
+ undefined ->
+ case pgsql_prepare(Query, State) of
+ {ok, _, _, _} ->
+ put(Key, prepared);
+ {error, Error} ->
+ ?ERROR_MSG("PREPARE failed for SQL query "
+ "at ~p: ~p",
+ [Query#sql_query.loc, Error]),
+ put(Key, ignore)
+ end;
+ _ ->
+ ok
+ end,
+ case get(Key) of
+ prepared ->
+ pgsql_execute_sql_query(Query, State);
+ _ ->
+ generic_sql_query(Query)
+ end;
+ mysql ->
+ generic_sql_query(Query);
+ sqlite ->
+ generic_sql_query(Query)
+ end
+ catch
+ Class:Reason ->
+ ST = erlang:get_stacktrace(),
+ ?ERROR_MSG("Internal error while processing SQL query: ~p",
+ [{Class, Reason, ST}]),
+ {error, <<"internal error">>}
+ end,
+ case Res of
+ {error, <<"No SQL-driver information available.">>} ->
+ {updated, 0};
+ _Else -> Res
+ end;
+sql_query_internal(F) when is_function(F) ->
+ case catch execute_fun(F) of
+ {'EXIT', Reason} -> {error, Reason};
+ Res -> Res
+ end;
sql_query_internal(Query) ->
State = get(?STATE_KEY),
?DEBUG("SQL: \"~s\"", [Query]),
Res = case State#state.db_type of
odbc ->
- to_odbc(odbc:sql_query(State#state.db_ref, Query,
+ to_odbc(odbc:sql_query(State#state.db_ref, [Query],
+ (?TRANSACTION_TIMEOUT) - 1000));
+ mssql ->
+ to_odbc(odbc:sql_query(State#state.db_ref, [Query],
(?TRANSACTION_TIMEOUT) - 1000));
pgsql ->
pgsql_to_odbc(pgsql:squery(State#state.db_ref, Query));
@@ -495,6 +581,93 @@ sql_query_internal(Query) ->
_Else -> Res
end.
+select_sql_query(Queries, State) ->
+ select_sql_query(
+ Queries, State#state.db_type, State#state.db_version, undefined).
+
+select_sql_query([], _Type, _Version, undefined) ->
+ undefined;
+select_sql_query([], _Type, _Version, Query) ->
+ Query;
+select_sql_query([{any, Query} | _], _Type, _Version, _) ->
+ Query;
+select_sql_query([{Type, Query} | _], Type, _Version, _) ->
+ Query;
+select_sql_query([{{Type, _Version1}, Query1} | Rest], Type, undefined, _) ->
+ select_sql_query(Rest, Type, undefined, Query1);
+select_sql_query([{{Type, Version1}, Query1} | Rest], Type, Version, Query) ->
+ if
+ Version >= Version1 ->
+ Query1;
+ true ->
+ select_sql_query(Rest, Type, Version, Query)
+ end;
+select_sql_query([{_, _} | Rest], Type, Version, Query) ->
+ select_sql_query(Rest, Type, Version, Query).
+
+generic_sql_query(SQLQuery) ->
+ sql_query_format_res(
+ sql_query_internal(generic_sql_query_format(SQLQuery)),
+ SQLQuery).
+
+generic_sql_query_format(SQLQuery) ->
+ Args = (SQLQuery#sql_query.args)(generic_escape()),
+ (SQLQuery#sql_query.format_query)(Args).
+
+generic_escape() ->
+ #sql_escape{string = fun(X) -> <<"'", (escape(X))/binary, "'">> end,
+ integer = fun(X) -> integer_to_binary(X) end,
+ boolean = fun(true) -> <<"1">>;
+ (false) -> <<"0">>
+ end
+ }.
+
+pgsql_prepare(SQLQuery, State) ->
+ Escape = #sql_escape{_ = fun(X) -> X end},
+ N = length((SQLQuery#sql_query.args)(Escape)),
+ Args = [<<$$, (integer_to_binary(I))/binary>> || I <- lists:seq(1, N)],
+ Query = (SQLQuery#sql_query.format_query)(Args),
+ pgsql:prepare(State#state.db_ref, SQLQuery#sql_query.hash, Query).
+
+pgsql_execute_escape() ->
+ #sql_escape{string = fun(X) -> X end,
+ integer = fun(X) -> [integer_to_binary(X)] end,
+ boolean = fun(true) -> "1";
+ (false) -> "0"
+ end
+ }.
+
+pgsql_execute_sql_query(SQLQuery, State) ->
+ Args = (SQLQuery#sql_query.args)(pgsql_execute_escape()),
+ ExecuteRes =
+ pgsql:execute(State#state.db_ref, SQLQuery#sql_query.hash, Args),
+% {T, ExecuteRes} =
+% timer:tc(pgsql, execute, [State#state.db_ref, SQLQuery#sql_query.hash, Args]),
+% io:format("T ~s ~p~n", [SQLQuery#sql_query.hash, T]),
+ Res = pgsql_execute_to_odbc(ExecuteRes),
+ sql_query_format_res(Res, SQLQuery).
+
+
+sql_query_format_res({selected, _, Rows}, SQLQuery) ->
+ Res =
+ lists:flatmap(
+ fun(Row) ->
+ try
+ [(SQLQuery#sql_query.format_res)(Row)]
+ catch
+ Class:Reason ->
+ ST = erlang:get_stacktrace(),
+ ?ERROR_MSG("Error while processing "
+ "SQL query result: ~p~n"
+ "row: ~p",
+ [{Class, Reason, ST}, Row]),
+ []
+ end
+ end, Rows),
+ {selected, Res};
+sql_query_format_res(Res, _SQLQuery) ->
+ Res.
+
%% Generate the OTP callback return tuple depending on the driver result.
abort_on_driver_error({error, <<"query timed out">>} =
Reply,
@@ -606,6 +779,18 @@ pgsql_item_to_odbc(<<"UPDATE ", N/binary>>) ->
pgsql_item_to_odbc({error, Error}) -> {error, Error};
pgsql_item_to_odbc(_) -> {updated, undefined}.
+pgsql_execute_to_odbc({ok, {<<"SELECT", _/binary>>, Rows}}) ->
+ {selected, [], [[Field || {_, Field} <- Row] || Row <- Rows]};
+pgsql_execute_to_odbc({ok, {'INSERT', N}}) ->
+ {updated, N};
+pgsql_execute_to_odbc({ok, {'DELETE', N}}) ->
+ {updated, N};
+pgsql_execute_to_odbc({ok, {'UPDATE', N}}) ->
+ {updated, N};
+pgsql_execute_to_odbc({error, Error}) -> {error, Error};
+pgsql_execute_to_odbc(_) -> {updated, undefined}.
+
+
%% == Native MySQL code
%% part of init/1
@@ -658,6 +843,24 @@ to_odbc({error, Reason}) when is_list(Reason) ->
to_odbc(Res) ->
Res.
+get_db_version(#state{db_type = pgsql} = State) ->
+ case pgsql:squery(State#state.db_ref,
+ <<"select current_setting('server_version_num')">>) of
+ {ok, [{_, _, [[SVersion]]}]} ->
+ case catch binary_to_integer(SVersion) of
+ Version when is_integer(Version) ->
+ State#state{db_version = Version};
+ Error ->
+ ?WARNING_MSG("error getting pgsql version: ~p", [Error]),
+ State
+ end;
+ Res ->
+ ?WARNING_MSG("error getting pgsql version: ~p", [Res]),
+ State
+ end;
+get_db_version(State) ->
+ State.
+
log(Level, Format, Args) ->
case Level of
debug -> ?DEBUG(Format, Args);
@@ -666,14 +869,14 @@ log(Level, Format, Args) ->
end.
db_opts(Host) ->
- Type = ejabberd_config:get_option({odbc_type, Host},
+ Type = ejabberd_config:get_option({sql_type, Host},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end, odbc),
- Server = ejabberd_config:get_option({odbc_server, Host},
+ Server = ejabberd_config:get_option({sql_server, Host},
fun iolist_to_binary/1,
<<"localhost">>),
case Type of
@@ -683,41 +886,40 @@ db_opts(Host) ->
[sqlite, Host];
_ ->
Port = ejabberd_config:get_option(
- {odbc_port, Host},
+ {sql_port, Host},
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
case Type of
mssql -> ?MSSQL_PORT;
mysql -> ?MYSQL_PORT;
pgsql -> ?PGSQL_PORT
end),
- DB = ejabberd_config:get_option({odbc_database, Host},
+ DB = ejabberd_config:get_option({sql_database, Host},
fun iolist_to_binary/1,
<<"ejabberd">>),
- User = ejabberd_config:get_option({odbc_username, Host},
+ User = ejabberd_config:get_option({sql_username, Host},
fun iolist_to_binary/1,
<<"ejabberd">>),
- Pass = ejabberd_config:get_option({odbc_password, Host},
+ Pass = ejabberd_config:get_option({sql_password, Host},
fun iolist_to_binary/1,
<<"">>),
case Type of
mssql ->
- Username = get_mssql_user(Server, User),
- [odbc, <<"DSN=", Host/binary, ";UID=", Username/binary,
- ";PWD=", Pass/binary>>];
+ [mssql, <<"DSN=", Host/binary, ";UID=", User/binary,
+ ";PWD=", Pass/binary>>];
_ ->
[Type, Server, Port, DB, User, Pass]
end
end.
init_mssql(Host) ->
- Server = ejabberd_config:get_option({odbc_server, Host},
+ Server = ejabberd_config:get_option({sql_server, Host},
fun iolist_to_binary/1,
<<"localhost">>),
Port = ejabberd_config:get_option(
- {odbc_port, Host},
+ {sql_port, Host},
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
?MSSQL_PORT),
- DB = ejabberd_config:get_option({odbc_database, Host},
+ DB = ejabberd_config:get_option({sql_database, Host},
fun iolist_to_binary/1,
<<"ejabberd">>),
FreeTDS = io_lib:fwrite("[~s]~n"
@@ -762,21 +964,6 @@ init_mssql(Host) ->
Err
end.
-get_mssql_user(Server, User) ->
- HostName = case inet_parse:address(binary_to_list(Server)) of
- {ok, _} ->
- Server;
- {error, _} ->
- hd(str:tokens(Server, <<".">>))
- end,
- UserName = case str:chr(User, $@) of
- 0 ->
- <<User/binary, $@, HostName/binary>>;
- _ ->
- User
- end,
- UserName.
-
tmp_dir() ->
filename:join(["/tmp", "ejabberd"]).
@@ -800,30 +987,39 @@ fsm_limit_opts() ->
_ -> []
end.
+check_error({error, Why} = Err, #sql_query{} = Query) ->
+ ?ERROR_MSG("SQL query '~s' at ~p failed: ~p",
+ [Query#sql_query.hash, Query#sql_query.loc, Why]),
+ Err;
check_error({error, Why} = Err, Query) ->
- ?ERROR_MSG("SQL query '~s' failed: ~p", [Query, Why]),
+ case catch iolist_to_binary(Query) of
+ SQuery when is_binary(SQuery) ->
+ ?ERROR_MSG("SQL query '~s' failed: ~p", [SQuery, Why]);
+ _ ->
+ ?ERROR_MSG("SQL query ~p failed: ~p", [Query, Why])
+ end,
Err;
check_error(Result, _Query) ->
Result.
opt_type(max_fsm_queue) ->
fun (N) when is_integer(N), N > 0 -> N end;
-opt_type(odbc_database) -> fun iolist_to_binary/1;
-opt_type(odbc_keepalive_interval) ->
+opt_type(sql_database) -> fun iolist_to_binary/1;
+opt_type(sql_keepalive_interval) ->
fun (I) when is_integer(I), I > 0 -> I end;
-opt_type(odbc_password) -> fun iolist_to_binary/1;
-opt_type(odbc_port) ->
+opt_type(sql_password) -> fun iolist_to_binary/1;
+opt_type(sql_port) ->
fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
-opt_type(odbc_server) -> fun iolist_to_binary/1;
-opt_type(odbc_type) ->
+opt_type(sql_server) -> fun iolist_to_binary/1;
+opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end;
-opt_type(odbc_username) -> fun iolist_to_binary/1;
+opt_type(sql_username) -> fun iolist_to_binary/1;
opt_type(_) ->
- [max_fsm_queue, odbc_database, odbc_keepalive_interval,
- odbc_password, odbc_port, odbc_server, odbc_type,
- odbc_username].
+ [max_fsm_queue, sql_database, sql_keepalive_interval,
+ sql_password, sql_port, sql_server, sql_type,
+ sql_username].
diff --git a/src/ejabberd_sql_pt.erl b/src/ejabberd_sql_pt.erl
new file mode 100644
index 000000000..47e4a07c7
--- /dev/null
+++ b/src/ejabberd_sql_pt.erl
@@ -0,0 +1,544 @@
+%%%-------------------------------------------------------------------
+%%% File : ejabberd_sql_pt.erl
+%%% Author : Alexey Shchepin <alexey@process-one.net>
+%%% Description : Parse transform for SQL queries
+%%%
+%%% Created : 20 Jan 2016 by Alexey Shchepin <alexey@process-one.net>
+%%%-------------------------------------------------------------------
+-module(ejabberd_sql_pt).
+
+%% API
+-export([parse_transform/2]).
+
+-export([parse/2]).
+
+-include("ejabberd_sql_pt.hrl").
+
+-record(state, {loc,
+ 'query' = [],
+ params = [],
+ param_pos = 0,
+ args = [],
+ res = [],
+ res_vars = [],
+ res_pos = 0}).
+
+-define(QUERY_RECORD, "sql_query").
+
+-define(ESCAPE_RECORD, "sql_escape").
+-define(ESCAPE_VAR, "__SQLEscape").
+
+-define(MOD, sql__module_).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function:
+%% Description:
+%%--------------------------------------------------------------------
+parse_transform(AST, _Options) ->
+ %io:format("PT: ~p~nOpts: ~p~n", [AST, Options]),
+ NewAST = top_transform(AST),
+ %io:format("NewPT: ~p~n", [NewAST]),
+ NewAST.
+
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+
+transform(Form) ->
+ case erl_syntax:type(Form) of
+ application ->
+ case erl_syntax_lib:analyze_application(Form) of
+ {?SQL_MARK, 1} ->
+ case erl_syntax:application_arguments(Form) of
+ [Arg] ->
+ case erl_syntax:type(Arg) of
+ string ->
+ S = erl_syntax:string_value(Arg),
+ Pos = erl_syntax:get_pos(Arg),
+ ParseRes = parse(S, Pos),
+ set_pos(make_sql_query(ParseRes), Pos);
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "?SQL argument must be "
+ "a constant string"})
+ end;
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "wrong number of ?SQL args"})
+ end;
+ {?SQL_UPSERT_MARK, 2} ->
+ case erl_syntax:application_arguments(Form) of
+ [TableArg, FieldsArg] ->
+ case {erl_syntax:type(TableArg),
+ erl_syntax:is_proper_list(FieldsArg)}of
+ {string, true} ->
+ Table = erl_syntax:string_value(TableArg),
+ ParseRes =
+ parse_upsert(
+ erl_syntax:list_elements(FieldsArg)),
+ Pos = erl_syntax:get_pos(Form),
+ set_pos(
+ make_sql_upsert(Table, ParseRes, Pos),
+ Pos);
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "?SQL_UPSERT arguments must be "
+ "a constant string and a list"})
+ end;
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "wrong number of ?SQL_UPSERT args"})
+ end;
+ _ ->
+ Form
+ end;
+ attribute ->
+ case erl_syntax:atom_value(erl_syntax:attribute_name(Form)) of
+ module ->
+ case erl_syntax:attribute_arguments(Form) of
+ [M | _] ->
+ Module = erl_syntax:atom_value(M),
+ %io:format("module ~p~n", [Module]),
+ put(?MOD, Module),
+ Form;
+ _ ->
+ Form
+ end;
+ _ ->
+ Form
+ end;
+ _ ->
+ Form
+ end.
+
+top_transform(Forms) when is_list(Forms) ->
+ lists:map(
+ fun(Form) ->
+ try
+ Form2 = erl_syntax_lib:map(
+ fun(Node) ->
+ %io:format("asd ~p~n", [Node]),
+ transform(Node)
+ end, Form),
+ Form3 = erl_syntax:revert(Form2),
+ Form3
+ catch
+ throw:{error, Line, Error} ->
+ {error, {Line, erl_parse, Error}}
+ end
+ end, Forms).
+
+parse(S, Loc) ->
+ parse1(S, [], #state{loc = Loc}).
+
+parse(S, ParamPos, Loc) ->
+ parse1(S, [], #state{loc = Loc, param_pos = ParamPos}).
+
+parse1([], Acc, State) ->
+ State1 = append_string(lists:reverse(Acc), State),
+ State1#state{'query' = lists:reverse(State1#state.'query'),
+ params = lists:reverse(State1#state.params),
+ args = lists:reverse(State1#state.args),
+ res = lists:reverse(State1#state.res),
+ res_vars = lists:reverse(State1#state.res_vars)
+ };
+parse1([$@, $( | S], Acc, State) ->
+ State1 = append_string(lists:reverse(Acc), State),
+ {Name, Type, S1, State2} = parse_name(S, State1),
+ Var = "__V" ++ integer_to_list(State2#state.res_pos),
+ EVar = erl_syntax:variable(Var),
+ Convert =
+ case Type of
+ integer ->
+ erl_syntax:application(
+ erl_syntax:atom(binary_to_integer),
+ [EVar]);
+ string ->
+ EVar;
+ boolean ->
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_sql),
+ erl_syntax:atom(to_bool),
+ [EVar])
+ end,
+ State3 = append_string(Name, State2),
+ State4 = State3#state{res_pos = State3#state.res_pos + 1,
+ res = [Convert | State3#state.res],
+ res_vars = [EVar | State3#state.res_vars]},
+ parse1(S1, [], State4);
+parse1([$%, $( | S], Acc, State) ->
+ State1 = append_string(lists:reverse(Acc), State),
+ {Name, Type, S1, State2} = parse_name(S, State1),
+ Var = State2#state.param_pos,
+ Convert =
+ erl_syntax:application(
+ erl_syntax:record_access(
+ erl_syntax:variable(?ESCAPE_VAR),
+ erl_syntax:atom(?ESCAPE_RECORD),
+ erl_syntax:atom(Type)),
+ [erl_syntax:variable(Name)]),
+ State3 = State2,
+ State4 =
+ State3#state{'query' = [{var, Var} | State3#state.'query'],
+ args = [Convert | State3#state.args],
+ params = [Var | State3#state.params],
+ param_pos = State3#state.param_pos + 1},
+ parse1(S1, [], State4);
+parse1([C | S], Acc, State) ->
+ parse1(S, [C | Acc], State).
+
+append_string([], State) ->
+ State;
+append_string(S, State) ->
+ State#state{query = [{str, S} | State#state.query]}.
+
+parse_name(S, State) ->
+ parse_name(S, [], 0, State).
+
+parse_name([], _Acc, _Depth, State) ->
+ throw({error, State#state.loc,
+ "expected ')', found end of string"});
+parse_name([$), T | S], Acc, 0, State) ->
+ Type =
+ case T of
+ $d -> integer;
+ $s -> string;
+ $b -> boolean;
+ _ ->
+ throw({error, State#state.loc,
+ ["unknown type specifier '", T, "'"]})
+ end,
+ {lists:reverse(Acc), Type, S, State};
+parse_name([$)], _Acc, 0, State) ->
+ throw({error, State#state.loc,
+ "expected type specifier, found end of string"});
+parse_name([$( = C | S], Acc, Depth, State) ->
+ parse_name(S, [C | Acc], Depth + 1, State);
+parse_name([$) = C | S], Acc, Depth, State) ->
+ parse_name(S, [C | Acc], Depth - 1, State);
+parse_name([C | S], Acc, Depth, State) ->
+ parse_name(S, [C | Acc], Depth, State).
+
+
+make_var(V) ->
+ Var = "__V" ++ integer_to_list(V),
+ erl_syntax:variable(Var).
+
+
+make_sql_query(State) ->
+ Hash = erlang:phash2(State#state{loc = undefined}),
+ SHash = <<"Q", (integer_to_binary(Hash))/binary>>,
+ Query = pack_query(State#state.'query'),
+ EQuery =
+ lists:map(
+ fun({str, S}) ->
+ erl_syntax:binary(
+ [erl_syntax:binary_field(
+ erl_syntax:string(S))]);
+ ({var, V}) -> make_var(V)
+ end, Query),
+ erl_syntax:record_expr(
+ erl_syntax:atom(?QUERY_RECORD),
+ [erl_syntax:record_field(
+ erl_syntax:atom(hash),
+ %erl_syntax:abstract(SHash)
+ erl_syntax:binary(
+ [erl_syntax:binary_field(
+ erl_syntax:string(binary_to_list(SHash)))])),
+ erl_syntax:record_field(
+ erl_syntax:atom(args),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:variable(?ESCAPE_VAR)],
+ none,
+ [erl_syntax:list(State#state.args)]
+ )])),
+ erl_syntax:record_field(
+ erl_syntax:atom(format_query),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:list(lists:map(fun make_var/1, State#state.params))],
+ none,
+ [erl_syntax:list(EQuery)]
+ )])),
+ erl_syntax:record_field(
+ erl_syntax:atom(format_res),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:list(State#state.res_vars)],
+ none,
+ [erl_syntax:tuple(State#state.res)]
+ )])),
+ erl_syntax:record_field(
+ erl_syntax:atom(loc),
+ erl_syntax:abstract({get(?MOD), State#state.loc}))
+ ]).
+
+pack_query([]) ->
+ [];
+pack_query([{str, S1}, {str, S2} | Rest]) ->
+ pack_query([{str, S1 ++ S2} | Rest]);
+pack_query([X | Rest]) ->
+ [X | pack_query(Rest)].
+
+
+parse_upsert(Fields) ->
+ {Fs, _} =
+ lists:foldr(
+ fun(F, {Acc, Param}) ->
+ case erl_syntax:type(F) of
+ string ->
+ V = erl_syntax:string_value(F),
+ {_, _, State} = Res =
+ parse_upsert_field(
+ V, Param, erl_syntax:get_pos(F)),
+ {[Res | Acc], State#state.param_pos};
+ _ ->
+ throw({error, erl_syntax:get_pos(F),
+ "?SQL_UPSERT field must be "
+ "a constant string"})
+ end
+ end, {[], 0}, Fields),
+ %io:format("asd ~p~n", [{Fields, Fs}]),
+ Fs.
+
+parse_upsert_field([$! | S], ParamPos, Loc) ->
+ {Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
+ {Name, true, ParseState};
+parse_upsert_field(S, ParamPos, Loc) ->
+ {Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
+ {Name, false, ParseState}.
+
+parse_upsert_field1([], _Acc, _ParamPos, Loc) ->
+ throw({error, Loc,
+ "?SQL_UPSERT fields must have the "
+ "following form: \"[!]name=value\""});
+parse_upsert_field1([$= | S], Acc, ParamPos, Loc) ->
+ {lists:reverse(Acc), parse(S, ParamPos, Loc)};
+parse_upsert_field1([C | S], Acc, ParamPos, Loc) ->
+ parse_upsert_field1(S, [C | Acc], ParamPos, Loc).
+
+
+make_sql_upsert(Table, ParseRes, Pos) ->
+ check_upsert(ParseRes, Pos),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
+ [erl_syntax:infix_expr(
+ erl_syntax:variable("__Version"),
+ erl_syntax:operator('>='),
+ erl_syntax:integer(90100))],
+ [make_sql_upsert_pgsql901(Table, ParseRes),
+ erl_syntax:atom(ok)]),
+ erl_syntax:clause(
+ [erl_syntax:underscore(), erl_syntax:underscore()],
+ none,
+ [make_sql_upsert_generic(Table, ParseRes)])
+ ]).
+
+make_sql_upsert_generic(Table, ParseRes) ->
+ Update = make_sql_query(make_sql_upsert_update(Table, ParseRes)),
+ Insert = make_sql_query(make_sql_upsert_insert(Table, ParseRes)),
+ InsertBranch =
+ erl_syntax:case_expr(
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_sql),
+ erl_syntax:atom(sql_query_t),
+ [Insert]),
+ [erl_syntax:clause(
+ [erl_syntax:abstract({updated, 1})],
+ none,
+ [erl_syntax:atom(ok)]),
+ erl_syntax:clause(
+ [erl_syntax:variable("__UpdateRes")],
+ none,
+ [erl_syntax:variable("__UpdateRes")])]),
+ erl_syntax:case_expr(
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_sql),
+ erl_syntax:atom(sql_query_t),
+ [Update]),
+ [erl_syntax:clause(
+ [erl_syntax:abstract({updated, 1})],
+ none,
+ [erl_syntax:atom(ok)]),
+ erl_syntax:clause(
+ [erl_syntax:underscore()],
+ none,
+ [InsertBranch])]).
+
+make_sql_upsert_update(Table, ParseRes) ->
+ WPairs =
+ lists:flatmap(
+ fun({_Field, false, _ST}) ->
+ [];
+ ({Field, true, ST}) ->
+ [ST#state{
+ 'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
+ }]
+ end, ParseRes),
+ Where = join_states(WPairs, " AND "),
+ SPairs =
+ lists:flatmap(
+ fun({_Field, true, _ST}) ->
+ [];
+ ({Field, false, ST}) ->
+ [ST#state{
+ 'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
+ }]
+ end, ParseRes),
+ Set = join_states(SPairs, ", "),
+ State =
+ concat_states(
+ [#state{'query' = [{str, "UPDATE "}, {str, Table}, {str, " SET "}]},
+ Set,
+ #state{'query' = [{str, " WHERE "}]},
+ Where
+ ]),
+ State.
+
+make_sql_upsert_insert(Table, ParseRes) ->
+ Vals =
+ lists:map(
+ fun({_Field, _, ST}) ->
+ ST
+ end, ParseRes),
+ Fields =
+ lists:map(
+ fun({Field, _, _ST}) ->
+ #state{'query' = [{str, Field}]}
+ end, ParseRes),
+ State =
+ concat_states(
+ [#state{'query' = [{str, "INSERT INTO "}, {str, Table}, {str, "("}]},
+ join_states(Fields, ", "),
+ #state{'query' = [{str, ") VALUES ("}]},
+ join_states(Vals, ", "),
+ #state{'query' = [{str, ")"}]}
+ ]),
+ State.
+
+make_sql_upsert_pgsql901(Table, ParseRes) ->
+ Update = make_sql_upsert_update(Table, ParseRes),
+ Vals =
+ lists:map(
+ fun({_Field, _, ST}) ->
+ ST
+ end, ParseRes),
+ Fields =
+ lists:map(
+ fun({Field, _, _ST}) ->
+ #state{'query' = [{str, Field}]}
+ end, ParseRes),
+ Insert =
+ concat_states(
+ [#state{'query' = [{str, "INSERT INTO "}, {str, Table}, {str, "("}]},
+ join_states(Fields, ", "),
+ #state{'query' = [{str, ") SELECT "}]},
+ join_states(Vals, ", "),
+ #state{'query' = [{str, " WHERE NOT EXISTS (SELECT * FROM upsert)"}]}
+ ]),
+ State =
+ concat_states(
+ [#state{'query' = [{str, "WITH upsert AS ("}]},
+ Update,
+ #state{'query' = [{str, " RETURNING *) "}]},
+ Insert
+ ]),
+ Upsert = make_sql_query(State),
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_sql),
+ erl_syntax:atom(sql_query_t),
+ [Upsert]).
+
+
+check_upsert(ParseRes, Pos) ->
+ Set =
+ lists:filter(
+ fun({_Field, Match, _ST}) ->
+ not Match
+ end, ParseRes),
+ case Set of
+ [] ->
+ throw({error, Pos,
+ "No ?SQL_UPSERT fields to set, use INSERT instead"});
+ _ ->
+ ok
+ end,
+ ok.
+
+
+concat_states(States) ->
+ lists:foldr(
+ fun(ST11, ST2) ->
+ ST1 = resolve_vars(ST11, ST2),
+ ST1#state{
+ 'query' = ST1#state.'query' ++ ST2#state.'query',
+ params = ST1#state.params ++ ST2#state.params,
+ args = ST1#state.args ++ ST2#state.args,
+ res = ST1#state.res ++ ST2#state.res,
+ res_vars = ST1#state.res_vars ++ ST2#state.res_vars,
+ loc = case ST1#state.loc of
+ undefined -> ST2#state.loc;
+ _ -> ST1#state.loc
+ end
+ }
+ end, #state{}, States).
+
+resolve_vars(ST1, ST2) ->
+ Max = lists:max([0 | ST1#state.params ++ ST2#state.params]),
+ {Map, _} =
+ lists:foldl(
+ fun(Var, {Acc, New}) ->
+ case lists:member(Var, ST2#state.params) of
+ true ->
+ {dict:store(Var, New, Acc), New + 1};
+ false ->
+ {Acc, New}
+ end
+ end, {dict:new(), Max + 1}, ST1#state.params),
+ NewParams =
+ lists:map(
+ fun(Var) ->
+ case dict:find(Var, Map) of
+ {ok, New} ->
+ New;
+ error ->
+ Var
+ end
+ end, ST1#state.params),
+ NewQuery =
+ lists:map(
+ fun({var, Var}) ->
+ case dict:find(Var, Map) of
+ {ok, New} ->
+ {var, New};
+ error ->
+ {var, Var}
+ end;
+ (S) -> S
+ end, ST1#state.'query'),
+ ST1#state{params = NewParams, 'query' = NewQuery}.
+
+
+join_states([], _Sep) ->
+ #state{};
+join_states([H | T], Sep) ->
+ J = [[H] | [[#state{'query' = [{str, Sep}]}, X] || X <- T]],
+ concat_states(lists:append(J)).
+
+
+set_pos(Tree, Pos) ->
+ erl_syntax_lib:map(
+ fun(Node) ->
+ case erl_syntax:get_pos(Node) of
+ 0 -> erl_syntax:set_pos(Node, Pos);
+ _ -> Node
+ end
+ end, Tree).
diff --git a/src/ejabberd_odbc_sup.erl b/src/ejabberd_sql_sup.erl
index 65fa3c1c6..682414557 100644
--- a/src/ejabberd_odbc_sup.erl
+++ b/src/ejabberd_sql_sup.erl
@@ -1,7 +1,7 @@
%%%----------------------------------------------------------------------
-%%% File : ejabberd_odbc_sup.erl
+%%% File : ejabberd_sql_sup.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
-%%% Purpose : ODBC connections supervisor
+%%% Purpose : SQL connections supervisor
%%% Created : 22 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(ejabberd_odbc_sup).
+-module(ejabberd_sql_sup).
-behaviour(ejabberd_config).
@@ -42,7 +42,7 @@
-define(DEFAULT_POOL_SIZE, 10).
--define(DEFAULT_ODBC_START_INTERVAL, 30).
+-define(DEFAULT_SQL_START_INTERVAL, 30).
-define(CONNECT_TIMEOUT, 500).
@@ -62,14 +62,14 @@ start_link(Host) ->
init([Host]) ->
PoolSize = ejabberd_config:get_option(
- {odbc_pool_size, Host},
+ {sql_pool_size, Host},
fun(I) when is_integer(I), I>0 -> I end,
?DEFAULT_POOL_SIZE),
StartInterval = ejabberd_config:get_option(
- {odbc_start_interval, Host},
+ {sql_start_interval, Host},
fun(I) when is_integer(I), I>0 -> I end,
- ?DEFAULT_ODBC_START_INTERVAL),
- Type = ejabberd_config:get_option({odbc_type, Host},
+ ?DEFAULT_SQL_START_INTERVAL),
+ Type = ejabberd_config:get_option({sql_type, Host},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
@@ -80,7 +80,7 @@ init([Host]) ->
sqlite ->
check_sqlite_db(Host);
mssql ->
- ejabberd_odbc:init_mssql(Host);
+ ejabberd_sql:init_mssql(Host);
_ ->
ok
end,
@@ -89,7 +89,7 @@ init([Host]) ->
{{one_for_one, PoolSize * 10, 1},
lists:map(fun (I) ->
{I,
- {ejabberd_odbc, start_link,
+ {ejabberd_sql, start_link,
[Host, StartInterval * 1000]},
transient, 2000, worker, [?MODULE]}
end,
@@ -121,12 +121,12 @@ transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
transform_options({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) ->
- [{odbc_type, Type},
- {odbc_server, Server},
- {odbc_port, Port},
- {odbc_database, DB},
- {odbc_username, User},
- {odbc_password, Pass}|Opts];
+ [{sql_type, Type},
+ {sql_server, Server},
+ {sql_port, Port},
+ {sql_database, DB},
+ {sql_username, User},
+ {sql_password, Pass}|Opts];
transform_options({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) ->
transform_options({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts);
transform_options({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) ->
@@ -137,8 +137,8 @@ transform_options(Opt, Opts) ->
[Opt|Opts].
check_sqlite_db(Host) ->
- DB = ejabberd_odbc:sqlite_db(Host),
- File = ejabberd_odbc:sqlite_file(Host),
+ DB = ejabberd_sql:sqlite_db(Host),
+ File = ejabberd_sql:sqlite_file(Host),
Ret = case filelib:ensure_dir(File) of
ok ->
case sqlite3:open(DB, [{file, File}]) of
@@ -211,11 +211,11 @@ read_lines(Fd, File, Acc) ->
[]
end.
-opt_type(odbc_pool_size) ->
+opt_type(sql_pool_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
-opt_type(odbc_start_interval) ->
+opt_type(sql_start_interval) ->
fun (I) when is_integer(I), I > 0 -> I end;
-opt_type(odbc_type) ->
+opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
@@ -223,4 +223,4 @@ opt_type(odbc_type) ->
(odbc) -> odbc
end;
opt_type(_) ->
- [odbc_pool_size, odbc_start_interval, odbc_type].
+ [sql_pool_size, sql_start_interval, sql_type].
diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl
index f7ae19cd4..3c91117d1 100644
--- a/src/ejabberd_stun.erl
+++ b/src/ejabberd_stun.erl
@@ -38,11 +38,11 @@
%%% API
%%%===================================================================
tcp_init(Socket, Opts) ->
- ejabberd:start_app(p1_stun),
+ ejabberd:start_app(stun),
stun:tcp_init(Socket, prepare_turn_opts(Opts)).
udp_init(Socket, Opts) ->
- ejabberd:start_app(p1_stun),
+ ejabberd:start_app(stun),
stun:udp_init(Socket, prepare_turn_opts(Opts)).
udp_recv(Socket, Addr, Port, Packet, Opts) ->
diff --git a/src/ejabberd_system_monitor.erl b/src/ejabberd_system_monitor.erl
index 9a0213c1d..3f6c05667 100644
--- a/src/ejabberd_system_monitor.erl
+++ b/src/ejabberd_system_monitor.erl
@@ -71,7 +71,7 @@ process_command(From, To, Packet) ->
jid:tolower(jid:remove_resource(From)),
case lists:member(LFrom, get_admin_jids()) of
true ->
- Body = xml:get_path_s(Packet,
+ Body = fxml:get_path_s(Packet,
[{elem, <<"body">>}, cdata]),
spawn(fun () ->
process_flag(priority, high),
@@ -186,18 +186,24 @@ process_large_heap(Pid, Info) ->
"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)
+ send_message(From, jid:make(JID), Body, Hint)
end, JIDs).
send_message(From, To, Body) ->
+ send_message(From, To, Body, []).
+
+send_message(From, To, Body, ExtraEls) ->
ejabberd_router:route(From, To,
#xmlel{name = <<"message">>,
attrs = [{<<"type">>, <<"chat">>}],
children =
[#xmlel{name = <<"body">>, attrs = [],
children =
- [{xmlcdata, Body}]}]}).
+ [{xmlcdata, Body}]}
+ | ExtraEls]}).
get_admin_jids() ->
ejabberd_config:get_option(
diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl
index d711f2fc3..3281f6430 100644
--- a/src/ejabberd_web_admin.erl
+++ b/src/ejabberd_web_admin.erl
@@ -264,7 +264,7 @@ get_auth_admin(Auth, HostHTTP, RPath, Method) ->
get_auth_account(HostOfRule, AccessRule, User, Server,
Pass) ->
- case ejabberd_auth:check_password(User, Server, Pass) of
+ case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
true ->
case is_acl_match(HostOfRule, AccessRule,
jid:make(User, Server, <<"">>))
@@ -383,6 +383,9 @@ css(Host) ->
" background: #f9f9f9;\n"
" font-family: sans-serif;\n"
"}\n"
+ "body {\n"
+ " min-width: 990px;\n"
+ "}\n"
"a {\n"
" text-decoration: none;\n"
" color: #3eaffa;\n"
@@ -461,13 +464,15 @@ css(Host) ->
" font-size: 0.75em;\n"
" text-align: center;\n"
"}\n"
+ "#navigation {\n"
+ " display: inline-block;\n"
+ " vertical-align: top;\n"
+ " width: 30%;\n"
+ "}\n"
"#navigation ul {\n"
- " position: absolute;\n"
- " top: 75px;\n"
- " left: 0;\n"
" padding: 0;\n"
" margin: 0;\n"
- " width: 17em;\n"
+ " width: 90%;\n"
" background: #fff;\n"
"}\n"
"#navigation ul li {\n"
@@ -512,6 +517,9 @@ css(Host) ->
" background: #3eaffa;\n"
" color: #fff;\n"
"}\n"
+ "thead tr td a {\n"
+ " color: #fff;\n"
+ "}\n"
"td.copy {\n"
" text-align: center;\n"
"}\n"
@@ -592,8 +600,10 @@ css(Host) ->
" list-style-type: none;\n"
"}\n"
"#content {\n"
- " padding-left: 19em;\n"
+ " display: inline-block;\n"
+ " vertical-align: top;\n"
" padding-top: 25px;\n"
+ " width: 70%;\n"
"}\n"
"div.guidelink,\n"
"p[dir=ltr] {\n"
@@ -735,7 +745,7 @@ process_admin(Host,
[{{acl, '$1', '$2'}}]}])),
{NumLines, ACLsP} = term_to_paragraph(ACLs, 80),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
- <<"acl-definition">>, <<"ACL Definition">>))
+ <<"acldefinition">>, <<"ACL Definition">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -771,7 +781,7 @@ process_admin(Host,
[{{acl, {'$1', Host}, '$2'}, [],
[{{acl, '$1', '$2'}}]}])),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
- <<"acl-definition">>, <<"ACL Definition">>))
+ <<"acldefinition">>, <<"ACL Definition">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -837,7 +847,7 @@ process_admin(Host,
[{{access, '$1', '$2'}}]}]),
{NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
- <<"access-rights">>, <<"Access Rights">>))
+ <<"accessrights">>, <<"Access Rights">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -870,7 +880,7 @@ process_admin(Host,
[{{access, {'$1', Host}, '$2'}, [],
[{{access, '$1', '$2'}}]}]),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
- <<"access-rights">>, <<"Access Rights">>))
+ <<"accessrights">>, <<"Access Rights">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -929,7 +939,7 @@ process_admin(global,
lang = Lang}) ->
Res = list_vhosts(Lang, AJID),
make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)),
- <<"virtual-hosting">>, <<"Virtual Hosting">>))
+ <<"virtualhosting">>, <<"Virtual Hosting">>))
++ Res,
global, Lang, AJID);
process_admin(Host,
@@ -1510,8 +1520,7 @@ get_offlinemsg_length(ModOffline, User, Server) ->
case ModOffline of
none -> <<"disabled">>;
_ ->
- pretty_string_int(ModOffline:get_queue_length(User,
- Server))
+ pretty_string_int(ModOffline:count_offline_messages(User,Server))
end.
get_offlinemsg_module(Server) ->
@@ -2114,7 +2123,7 @@ get_node(global, Node, [<<"ports">>], Query, Lang) ->
[]])),
H1String = <<(?T(<<"Listened Ports at ">>))/binary,
(iolist_to_binary(atom_to_list(Node)))/binary>>,
- (?H1GL(H1String, <<"listening-ports">>, <<"Listening Ports">>))
+ (?H1GL(H1String, <<"listeningports">>, <<"Listening Ports">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -2142,7 +2151,7 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
NewModules = lists:sort(ejabberd_cluster:call(Node, gen_mod,
loaded_modules_with_opts, [Host])),
H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])),
- (?H1GL(H1String, <<"modules-overview">>,
+ (?H1GL(H1String, <<"modulesoverview">>,
<<"Modules Overview">>))
++
case Res of
@@ -2405,7 +2414,7 @@ node_backup_parse_query(Node, Query) ->
lists:keysearch(<<Action/binary,
"host">>,
1, Query),
- ejabberd_cluster:call(Node, ejd2odbc,
+ ejabberd_cluster:call(Node, ejd2sql,
export, [Host, Path]);
<<"import_file">> ->
ejabberd_cluster:call(Node, ejabberd_admin,
@@ -2722,14 +2731,14 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
[{Attr, Val} | RestAttrs] ->
AttrPrefix = [Prefix,
str:copies(<<" ">>, byte_size(Name) + 2)],
- [$\s, Attr, $=, $', xml:crypt(Val) | [$',
+ [$\s, Attr, $=, $', fxml:crypt(Val) | [$',
lists:map(fun ({Attr1,
Val1}) ->
[$\n,
AttrPrefix,
Attr1, $=,
$',
- xml:crypt(Val1),
+ fxml:crypt(Val1),
$']
end,
RestAttrs)]]
@@ -2741,7 +2750,7 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
end,
Els),
if OnlyCData ->
- [$>, xml:get_cdata(Els), $<, $/, Name, $>, $\n];
+ [$>, fxml:get_cdata(Els), $<, $/, Name, $>, $\n];
true ->
[$>, $\n,
lists:map(fun (E) ->
diff --git a/src/ejabberd_websocket.erl b/src/ejabberd_websocket.erl
index d9a1bafc7..0cdd9bac5 100644
--- a/src/ejabberd_websocket.erl
+++ b/src/ejabberd_websocket.erl
@@ -373,10 +373,10 @@ process_frame(#frame_info{unprocessed =
process_frame(FrameInfo#frame_info{unprocessed = <<>>},
<<UnprocessedPre/binary, Data/binary>>).
-handle_data(tcp, FrameInfo, Data, Socket, WsHandleLoopPid, p1_tls) ->
- case p1_tls:recv_data(Socket, Data) of
+handle_data(tcp, FrameInfo, Data, Socket, WsHandleLoopPid, fast_tls) ->
+ case fast_tls:recv_data(Socket, Data) of
{ok, NewData} ->
- handle_data_int(FrameInfo, NewData, Socket, WsHandleLoopPid, p1_tls);
+ handle_data_int(FrameInfo, NewData, Socket, WsHandleLoopPid, fast_tls);
{error, Error} ->
{error, Error}
end;
diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl
index 6b25adc4c..c7e72d66d 100644
--- a/src/ejabberd_xmlrpc.erl
+++ b/src/ejabberd_xmlrpc.erl
@@ -228,13 +228,13 @@ process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
end,
GetAuth = true,
State = #state{access_commands = AccessCommands, get_auth = GetAuth},
- case xml_stream:parse_element(Data) of
+ case fxml_stream:parse_element(Data) of
{error, _} ->
{400, [],
#xmlel{name = <<"h1">>, attrs = [],
children = [{xmlcdata, <<"Malformed XML">>}]}};
El ->
- case p1_xmlrpc:decode(El) of
+ case fxmlrpc:decode(El) of
{error, _} = Err ->
?ERROR_MSG("XML-RPC request ~s failed with reason: ~p",
[Data, Err]),
@@ -244,7 +244,7 @@ process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
{ok, RPC} ->
?DEBUG("got XML-RPC request: ~p", [RPC]),
{false, Result} = handler(State, RPC),
- XML = xml:element_to_binary(p1_xmlrpc:encode(Result)),
+ XML = fxml:element_to_binary(fxmlrpc:encode(Result)),
{200, [{<<"Content-Type">>, <<"text/xml">>}],
<<"<?xml version=\"1.0\"?>", XML/binary>>}
end
@@ -491,7 +491,7 @@ format_result(Atom, {Name, atom}) ->
[{Name, iolist_to_binary(atom_to_list(Atom))}]};
format_result(Int, {Name, integer}) ->
{struct, [{Name, Int}]};
-format_result(String, {Name, string}) when is_list(String) ->
+format_result([A|_]=String, {Name, string}) when is_list(String) and is_integer(A) ->
{struct, [{Name, lists:flatten(String)}]};
format_result(Binary, {Name, string}) when is_binary(Binary) ->
{struct, [{Name, binary_to_list(Binary)}]};
diff --git a/src/ejd2odbc.erl b/src/ejd2sql.erl
index 4cb937ef9..aa74286ed 100644
--- a/src/ejd2odbc.erl
+++ b/src/ejd2sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : ejd2odbc.erl
+%%% File : ejd2sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Export some mnesia tables to SQL DB
%%% Created : 22 Aug 2005 by Alexey Shchepin <alexey@process-one.net>
@@ -23,14 +23,14 @@
%%%
%%%----------------------------------------------------------------------
--module(ejd2odbc).
+-module(ejd2sql).
-author('alexey@process-one.net').
-include("logger.hrl").
-export([export/2, export/3, import_file/2, import/2,
- import/3]).
+ import/3, delete/1]).
-define(MAX_RECORDS_PER_TRANSACTION, 100).
@@ -43,7 +43,7 @@
%%% A table can be converted from Mnesia to an ODBC database by calling
%%% one of the API function with the following parameters:
%%% - Server is the server domain you want to convert
-%%% - Output can be either odbc to export to the configured relational
+%%% - Output can be either sql to export to the configured relational
%%% database or "Filename" to export to text file.
modules() ->
@@ -80,6 +80,20 @@ export(Server, Output, Module) ->
end, Module:export(Server)),
close_output(Output, IO).
+delete(Server) ->
+ Modules = modules(),
+ lists:foreach(
+ fun(Module) ->
+ delete(Server, Module)
+ end, Modules).
+
+delete(Server, Module) ->
+ LServer = jid:nameprep(iolist_to_binary(Server)),
+ lists:foreach(
+ fun({Table, ConvertFun}) ->
+ delete(LServer, Table, ConvertFun)
+ end, Module:export(Server)).
+
import_file(Server, FileName) when is_binary(FileName) ->
import(Server, binary_to_list(FileName));
import_file(Server, FileName) ->
@@ -154,17 +168,36 @@ export(LServer, Table, IO, ConvertFun) ->
output(_LServer, _Table, _IO, []) ->
ok;
-output(LServer, _Table, odbc, SQLs) ->
- ejabberd_odbc:sql_transaction(LServer, SQLs);
+output(LServer, _Table, sql, SQLs) ->
+ ejabberd_sql:sql_transaction(LServer, SQLs);
output(_LServer, Table, Fd, SQLs) ->
file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
"\n--\n", SQLs]).
+delete(LServer, Table, ConvertFun) ->
+ F = fun () ->
+ mnesia:write_lock_table(Table),
+ {_N, SQLs} =
+ mnesia:foldl(
+ fun(R, {N, SQLs} = Acc) ->
+ case ConvertFun(LServer, R) of
+ [] ->
+ Acc;
+ _SQL ->
+ mnesia:delete_object(R),
+ Acc
+ end
+ end,
+ {0, []}, Table),
+ delete(LServer, Table, SQLs)
+ end,
+ mnesia:transaction(F).
+
import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
F = case proplists:get_bool(fast, Opts) of
true ->
fun() ->
- case ejabberd_odbc:sql_query_t(SelectQuery) of
+ case ejabberd_sql:sql_query_t(SelectQuery) of
{selected, _, Rows} ->
lists:foldl(fun process_sql_row/2,
{IO, ConvertFun, undefined}, Rows);
@@ -174,16 +207,16 @@ import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
end;
false ->
fun() ->
- ejabberd_odbc:sql_query_t(
+ ejabberd_sql:sql_query_t(
[iolist_to_binary(
[<<"declare c cursor for ">>, SelectQuery])]),
fetch(IO, ConvertFun, undefined)
end
end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ ejabberd_sql:sql_transaction(LServer, F).
fetch(IO, ConvertFun, PrevRow) ->
- case ejabberd_odbc:sql_query_t([<<"fetch c;">>]) of
+ case ejabberd_sql:sql_query_t([<<"fetch c;">>]) of
{selected, _, [Row]} ->
process_sql_row(Row, {IO, ConvertFun, PrevRow}),
fetch(IO, ConvertFun, Row);
diff --git a/src/elixir_logger_backend.erl b/src/elixir_logger_backend.erl
new file mode 100644
index 000000000..c055853f7
--- /dev/null
+++ b/src/elixir_logger_backend.erl
@@ -0,0 +1,122 @@
+%%%-------------------------------------------------------------------
+%%% @author Mickael Remond <mremond@process-one.net>
+%%% @doc
+%%% This module bridges lager logs to Elixir Logger.
+%%% @end
+%%% Created : 9 March 2016 by Mickael Remond <mremond@process-one.net>
+%%%
+%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License along
+%%% with this program; if not, write to the Free Software Foundation, Inc.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%-------------------------------------------------------------------
+
+-module(elixir_logger_backend).
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {level = debug}).
+
+init(Opts) ->
+ Level = proplists:get_value(level, Opts, debug),
+ State = #state{level = Level},
+ {ok, State}.
+
+%% @private
+handle_event({log, LagerMsg}, State) ->
+ #{mode := Mode, truncate := Truncate, level := MinLevel, utc_log := UTCLog} = 'Elixir.Logger.Config':'__data__'(),
+ MsgLevel = severity_to_level(lager_msg:severity(LagerMsg)),
+ case {lager_util:is_loggable(LagerMsg, lager_util:level_to_num(State#state.level), ?MODULE),
+ 'Elixir.Logger':compare_levels(MsgLevel, MinLevel)} of
+ {_, lt}->
+ {ok, State};
+ {true, _} ->
+ Metadata = normalize_pid(lager_msg:metadata(LagerMsg)),
+ Message = 'Elixir.Logger.Utils':truncate(lager_msg:message(LagerMsg), Truncate),
+ Timestamp = timestamp(lager_msg:timestamp(LagerMsg), UTCLog),
+ GroupLeader = case proplists:get_value(pid, Metadata, self()) of
+ Pid when is_pid(Pid) ->
+ erlang:process_info(self(), group_leader);
+ _ -> {group_leader, self()}
+ end,
+ notify(Mode, {MsgLevel, GroupLeader, {'Elixir.Logger', Message, Timestamp, Metadata}}),
+ {ok, State};
+ _ ->
+ {ok, State}
+ end;
+handle_event(_Msg, State) ->
+ {ok, State}.
+
+%% @private
+%% TODO Handle loglevels
+handle_call(get_loglevel, State) ->
+ {ok, lager_util:config_to_mask(State#state.level), State};
+handle_call({set_loglevel, Config}, State) ->
+ {ok, ok, State#state{level = Config}}.
+
+%% @private
+handle_info(_Msg, State) ->
+ {ok, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+notify(sync, Msg) ->
+ gen_event:sync_notify('Elixir.Logger', Msg);
+notify(async, Msg) ->
+ gen_event:notify('Elixir.Logger', Msg).
+
+normalize_pid(Metadata) ->
+ case proplists:get_value(pid, Metadata) of
+ Pid when is_pid(Pid) -> Metadata;
+ Pid when is_list(Pid) ->
+ M1 = proplists:delete(pid, Metadata),
+ case catch erlang:list_to_pid(Pid) of
+ {'EXIT', _} ->
+ M1;
+ PidAsPid ->
+ [{pid, PidAsPid}|M1]
+ end;
+ _ ->
+ proplists:delete(pid, Metadata)
+ end.
+
+%% Return timestamp with milliseconds
+timestamp(Time, UTCLog) ->
+ {_, _, Micro} = p1_time_compat:timestamp(),
+ {Date, {Hours, Minutes, Seconds}} =
+ case UTCLog of
+ true -> calendar:now_to_universal_time(Time);
+ false -> calendar:now_to_local_time(Time)
+ end,
+ {Date, {Hours, Minutes, Seconds, Micro div 1000}}.
+
+
+severity_to_level(debug) -> debug;
+severity_to_level(info) -> info;
+severity_to_level(notice) -> info;
+severity_to_level(warning) -> warn;
+severity_to_level(error) -> error;
+severity_to_level(critical) -> error;
+severity_to_level(alert) -> error;
+severity_to_level(emergency) -> error.
diff --git a/src/ext_mod.erl b/src/ext_mod.erl
index 46ece873c..91526e71f 100644
--- a/src/ext_mod.erl
+++ b/src/ext_mod.erl
@@ -509,12 +509,11 @@ 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, "xml.hrl"),
- Logger = [{d, 'P1LOGGER'} || code:is_loaded(lager)==false],
+ XmlHrl = filename:join(EjabInc, "fxml.hrl"),
ExtLib = [{d, 'NO_EXT_LIB'} || filelib:is_file(XmlHrl)],
Options = [{outdir, Ebin}, {i, "include"}, {i, EjabInc},
verbose, report_errors, report_warnings]
- ++ Logger ++ ExtLib,
+ ++ ExtLib,
[file:copy(App, Ebin) || App <- filelib:wildcard("src/*.app")],
Result = [case compile:file(File, Options) of
{ok, _} -> ok;
@@ -589,10 +588,10 @@ rebar_dep({App, _, {git, Url, Ref}}) ->
%% -- YAML spec parser
consult(File) ->
- case p1_yaml:decode_from_file(File, [plain_as_atom]) of
+ case fast_yaml:decode_from_file(File, [plain_as_atom]) of
{ok, []} -> {ok, []};
{ok, [Doc|_]} -> {ok, [format(Spec) || Spec <- Doc]};
- {error, Err} -> {error, p1_yaml:format_error(Err)}
+ {error, Err} -> {error, fast_yaml:format_error(Err)}
end.
format({Key, Val}) when is_binary(Val) ->
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index c45642d47..26e662dc6 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -35,7 +35,8 @@
get_module_opt/4, get_module_opt/5, get_module_opt_host/3,
loaded_modules/1, loaded_modules_with_opts/1,
get_hosts/2, get_module_proc/2, is_loaded/2,
- start_modules/1, default_db/1, v_db/1, opt_type/1]).
+ start_modules/0, start_modules/1, stop_modules/0, stop_modules/1,
+ default_db/1, v_db/1, opt_type/1, db_mod/2, db_mod/3]).
%%-export([behaviour_info/1]).
@@ -47,7 +48,7 @@
opts = [] :: opts() | '_' | '$2'}).
-type opts() :: [{atom(), any()}].
--type db_type() :: odbc | mnesia | riak.
+-type db_type() :: sql | mnesia | riak.
-callback start(binary(), opts()) -> any().
-callback stop(binary()) -> any().
@@ -64,23 +65,38 @@ start() ->
{keypos, #ejabberd_module.module_host}]),
ok.
+-spec start_modules() -> any().
+
+%% Start all the modules in all the hosts
+start_modules() ->
+ lists:foreach(
+ fun(Host) ->
+ start_modules(Host)
+ end, ?MYHOSTS).
+
+get_modules_options(Host) ->
+ ejabberd_config:get_option(
+ {modules, Host},
+ fun(Mods) ->
+ lists:map(
+ fun({M, A}) when is_atom(M), is_list(A) ->
+ {M, A}
+ end, Mods)
+ end, []).
+
-spec start_modules(binary()) -> any().
start_modules(Host) ->
- Modules = ejabberd_config:get_option(
- {modules, Host},
- fun(L) when is_list(L) -> L end, []),
+ Modules = get_modules_options(Host),
lists:foreach(
- fun({Module, Opts}) ->
- start_module(Host, Module, Opts)
- end, Modules).
+ fun({Module, Opts}) ->
+ start_module(Host, Module, Opts)
+ end, Modules).
-spec start_module(binary(), atom()) -> any().
start_module(Host, Module) ->
- Modules = ejabberd_config:get_option(
- {modules, Host},
- fun(L) when is_list(L) -> L end, []),
+ Modules = get_modules_options(Host),
case lists:keyfind(Module, 1, Modules) of
{_, Opts} ->
start_module(Host, Module, Opts);
@@ -121,6 +137,23 @@ is_app_running(AppName) ->
lists:keymember(AppName, 1,
application:which_applications(Timeout)).
+-spec stop_modules() -> any().
+
+stop_modules() ->
+ lists:foreach(
+ fun(Host) ->
+ stop_modules(Host)
+ end, ?MYHOSTS).
+
+-spec stop_modules(binary()) -> any().
+
+stop_modules(Host) ->
+ Modules = get_modules_options(Host),
+ lists:foreach(
+ fun({Module, _Args}) ->
+ gen_mod:stop_module_keep_config(Host, Module)
+ end, Modules).
+
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
stop_module(Host, Module) ->
@@ -264,7 +297,8 @@ validate_opts(Module, Opts) ->
-spec v_db(db_type() | internal) -> db_type().
-v_db(odbc) -> odbc;
+v_db(odbc) -> sql;
+v_db(sql) -> sql;
v_db(internal) -> mnesia;
v_db(mnesia) -> mnesia;
v_db(riak) -> riak.
@@ -286,6 +320,20 @@ db_type(Host, Opts) when is_list(Opts) ->
default_db(Host) ->
ejabberd_config:get_option({default_db, Host}, fun v_db/1, mnesia).
+-spec db_mod(binary() | global | db_type(), module()) -> module().
+
+db_mod(odbc, Module) -> list_to_atom(atom_to_list(Module) ++ "_sql");
+db_mod(sql, Module) -> list_to_atom(atom_to_list(Module) ++ "_sql");
+db_mod(mnesia, Module) -> list_to_atom(atom_to_list(Module) ++ "_mnesia");
+db_mod(riak, Module) -> list_to_atom(atom_to_list(Module) ++ "_riak");
+db_mod(Host, Module) when is_binary(Host) orelse Host == global ->
+ db_mod(db_type(Host, Module), Module).
+
+-spec db_mod(binary() | global, opts(), module()) -> module().
+
+db_mod(Host, Opts, Module) when is_list(Opts) ->
+ db_mod(db_type(Host, Opts), Module).
+
-spec loaded_modules(binary()) -> [atom()].
loaded_modules(Host) ->
diff --git a/src/jd2ejd.erl b/src/jd2ejd.erl
index cda8d11ce..099387c9a 100644
--- a/src/jd2ejd.erl
+++ b/src/jd2ejd.erl
@@ -48,7 +48,7 @@ import_file(File) ->
true ->
case file:read_file(File) of
{ok, Text} ->
- case xml_stream:parse_element(Text) of
+ case fxml_stream:parse_element(Text) of
El when is_record(El, xmlel) ->
case catch process_xdb(User, Server, El) of
{'EXIT', Reason} ->
@@ -113,16 +113,16 @@ process_xdb(User, Server,
xdb_data(_User, _Server, {xmlcdata, _CData}) -> ok;
xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
From = jid:make(User, Server, <<"">>),
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_AUTH ->
- Password = xml:get_tag_cdata(El),
+ Password = fxml:get_tag_cdata(El),
ejabberd_auth:set_password(User, Server, Password),
ok;
?NS_ROSTER ->
catch mod_roster:set_items(User, Server, El), ok;
?NS_LAST ->
- TimeStamp = xml:get_attr_s(<<"last">>, Attrs),
- Status = xml:get_tag_cdata(El),
+ TimeStamp = fxml:get_attr_s(<<"last">>, Attrs),
+ Status = fxml:get_tag_cdata(El),
catch mod_last:store_last_info(User, Server,
jlib:binary_to_integer(TimeStamp),
Status),
@@ -136,7 +136,7 @@ xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
<<"jabber:x:offline">> ->
process_offline(Server, From, El), ok;
XMLNS ->
- case xml:get_attr_s(<<"j_private_flag">>, Attrs) of
+ case fxml:get_attr_s(<<"j_private_flag">>, Attrs) of
<<"1">> ->
catch mod_private:process_sm_iq(From,
jid:make(<<"">>, Server,
@@ -160,7 +160,7 @@ 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 = xml:get_attr_s(<<"from">>, Attrs),
+ FromS = fxml:get_attr_s(<<"from">>, Attrs),
From = case FromS of
<<"">> ->
jid:make(<<"">>, Server, <<"">>);
diff --git a/src/jid.erl b/src/jid.erl
index cc387ecbe..7bdd652ae 100644
--- a/src/jid.erl
+++ b/src/jid.erl
@@ -87,9 +87,13 @@ split(#jid{user = U, server = S, resource = R}) ->
split(_) ->
error.
--spec from_string(binary()) -> jid() | error.
-
-from_string(S) ->
+-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,
diff --git a/src/jlib.erl b/src/jlib.erl
index 61aaf690e..8eaebbc80 100644
--- a/src/jlib.erl
+++ b/src/jlib.erl
@@ -92,8 +92,8 @@ make_result_iq_reply(#xmlel{name = Name, attrs = Attrs,
-spec make_result_iq_reply_attrs([attr()]) -> [attr()].
make_result_iq_reply_attrs(Attrs) ->
- To = xml:get_attr(<<"to">>, Attrs),
- From = xml:get_attr(<<"from">>, Attrs),
+ To = fxml:get_attr(<<"to">>, Attrs),
+ From = fxml:get_attr(<<"from">>, Attrs),
Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
Attrs3 = case To of
@@ -133,8 +133,8 @@ make_error_reply(#xmlel{name = Name, attrs = Attrs,
-spec make_error_reply_attrs([attr()]) -> [attr()].
make_error_reply_attrs(Attrs) ->
- To = xml:get_attr(<<"to">>, Attrs),
- From = xml:get_attr(<<"from">>, Attrs),
+ To = fxml:get_attr(<<"to">>, Attrs),
+ From = fxml:get_attr(<<"from">>, Attrs),
Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
Attrs3 = case To of
@@ -159,7 +159,7 @@ make_error_element(Code, Desc) ->
make_correct_from_to_attrs(From, To, Attrs) ->
Attrs1 = lists:keydelete(<<"from">>, 1, Attrs),
- Attrs2 = case xml:get_attr(<<"to">>, Attrs) of
+ Attrs2 = case fxml:get_attr(<<"to">>, Attrs) of
{value, _} -> Attrs1;
_ -> [{<<"to">>, To} | Attrs1]
end,
@@ -299,8 +299,8 @@ jid_replace_resource(JID, Resource) ->
-spec get_iq_namespace(xmlel()) -> binary().
get_iq_namespace(#xmlel{name = <<"iq">>, children = Els}) ->
- case xml:remove_cdata(Els) of
- [#xmlel{attrs = Attrs}] -> xml:get_attr_s(<<"xmlns">>, Attrs);
+ case fxml:remove_cdata(Els) of
+ [#xmlel{attrs = Attrs}] -> fxml:get_attr_s(<<"xmlns">>, Attrs);
_ -> <<"">>
end;
get_iq_namespace(_) -> <<"">>.
@@ -326,9 +326,9 @@ iq_query_or_response_info(El) ->
iq_info_internal(El, any).
iq_info_internal(#xmlel{name = <<"iq">>, attrs = Attrs, children = Els}, Filter) ->
- ID = xml:get_attr_s(<<"id">>, Attrs),
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
- {Type, Class} = case xml:get_attr_s(<<"type">>, Attrs) of
+ ID = fxml:get_attr_s(<<"id">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ {Type, Class} = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"set">> -> {set, request};
<<"get">> -> {get, request};
<<"result">> -> {result, reply};
@@ -336,15 +336,15 @@ iq_info_internal(#xmlel{name = <<"iq">>, attrs = Attrs, children = Els}, Filter)
_ -> {invalid, invalid}
end,
if Type == invalid -> invalid; Class == request; Filter == any ->
- FilteredEls = xml:remove_cdata(Els),
+ FilteredEls = fxml:remove_cdata(Els),
{XMLNS, SubEl} = case {Class, FilteredEls} of
{request, [#xmlel{attrs = Attrs2}]} ->
- {xml:get_attr_s(<<"xmlns">>, Attrs2), hd(FilteredEls)};
+ {fxml:get_attr_s(<<"xmlns">>, Attrs2), hd(FilteredEls)};
{reply, _} ->
NonErrorEls = [El || #xmlel{name = SubName} = El <- FilteredEls,
SubName /= <<"error">>],
{case NonErrorEls of
- [NonErrorEl] -> xml:get_tag_attr_s(<<"xmlns">>, NonErrorEl);
+ [NonErrorEl] -> fxml:get_tag_attr_s(<<"xmlns">>, NonErrorEl);
_ -> <<"">>
end,
FilteredEls};
@@ -399,7 +399,7 @@ iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
).
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"submit">> ->
lists:reverse(parse_xdata_fields(Els, []));
<<"form">> -> %% This is a workaround to accept Psi's wrong forms
@@ -418,7 +418,7 @@ parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
parse_xdata_fields([], Res) -> Res;
parse_xdata_fields([#xmlel{name = <<"field">>, attrs = Attrs, children = SubEls}
| Els], Res) ->
- case xml:get_attr_s(<<"var">>, Attrs) of
+ case fxml:get_attr_s(<<"var">>, Attrs) of
<<>> ->
parse_xdata_fields(Els, Res);
Var ->
@@ -437,7 +437,7 @@ parse_xdata_fields([_ | Els], Res) ->
parse_xdata_values([], Res) -> Res;
parse_xdata_values([#xmlel{name = <<"value">>, children = SubEls} | Els], Res) ->
- Val = xml:get_cdata(SubEls),
+ Val = fxml:get_cdata(SubEls),
parse_xdata_values(Els, [Val | Res]);
parse_xdata_values([_ | Els], Res) ->
parse_xdata_values(Els, Res).
@@ -446,7 +446,7 @@ parse_xdata_values([_ | Els], Res) ->
rsm_decode(#iq{sub_el = SubEl}) -> rsm_decode(SubEl);
rsm_decode(#xmlel{} = SubEl) ->
- case xml:get_subtag(SubEl, <<"set">>) of
+ case fxml:get_subtag(SubEl, <<"set">>) of
false -> none;
#xmlel{name = <<"set">>, children = SubEls} ->
lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls)
@@ -455,26 +455,26 @@ rsm_decode(#xmlel{} = SubEl) ->
rsm_parse_element(#xmlel{name = <<"max">>, attrs = []} =
Elem,
RsmIn) ->
- CountStr = xml:get_tag_cdata(Elem),
+ CountStr = fxml:get_tag_cdata(Elem),
{Count, _} = str:to_integer(CountStr),
RsmIn#rsm_in{max = Count};
rsm_parse_element(#xmlel{name = <<"before">>,
attrs = []} =
Elem,
RsmIn) ->
- UID = xml:get_tag_cdata(Elem),
+ UID = fxml:get_tag_cdata(Elem),
RsmIn#rsm_in{direction = before, id = UID};
rsm_parse_element(#xmlel{name = <<"after">>,
attrs = []} =
Elem,
RsmIn) ->
- UID = xml:get_tag_cdata(Elem),
+ UID = fxml:get_tag_cdata(Elem),
RsmIn#rsm_in{direction = aft, id = UID};
rsm_parse_element(#xmlel{name = <<"index">>,
attrs = []} =
Elem,
RsmIn) ->
- IndexStr = xml:get_tag_cdata(Elem),
+ IndexStr = fxml:get_tag_cdata(Elem),
{Index, _} = str:to_integer(IndexStr),
RsmIn#rsm_in{index = Index};
rsm_parse_element(_, RsmIn) -> RsmIn.
@@ -535,7 +535,7 @@ is_standalone_chat_state(#xmlel{name = <<"message">>} = El) ->
<<"paused">>],
Stripped =
lists:foldl(fun(ChatState, AccEl) ->
- xml:remove_subtags(AccEl, ChatState,
+ fxml:remove_subtags(AccEl, ChatState,
{<<"xmlns">>, ?NS_CHATSTATES})
end, El, ChatStates),
case Stripped of
@@ -558,15 +558,15 @@ add_delay_info(El, From, Time) ->
binary()) -> xmlel().
add_delay_info(El, From, Time, Desc) ->
- case xml:get_subtag_with_xmlns(El, <<"delay">>, ?NS_DELAY) of
+ case fxml:get_subtag_with_xmlns(El, <<"delay">>, ?NS_DELAY) of
false ->
%% Add new tag
DelayTag = create_delay_tag(Time, From, Desc),
- xml:append_subtags(El, [DelayTag]);
+ fxml:append_subtags(El, [DelayTag]);
DelayTag ->
%% Update existing tag
NewDelayTag =
- case {xml:get_tag_cdata(DelayTag), Desc} of
+ case {fxml:get_tag_cdata(DelayTag), Desc} of
{<<"">>, <<"">>} ->
DelayTag;
{OldDesc, <<"">>} ->
@@ -582,8 +582,8 @@ add_delay_info(El, From, Time, Desc) ->
DelayTag#xmlel{children = [{xmlcdata, OldDesc}]}
end
end,
- NewEl = xml:remove_subtags(El, <<"delay">>, {<<"xmlns">>, ?NS_DELAY}),
- xml:append_subtags(NewEl, [NewDelayTag])
+ NewEl = fxml:remove_subtags(El, <<"delay">>, {<<"xmlns">>, ?NS_DELAY}),
+ fxml:append_subtags(NewEl, [NewDelayTag])
end.
-spec create_delay_tag(erlang:timestamp(), jid() | ljid() | binary(), binary())
diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl
index 9947c84ac..9e0682f7d 100644
--- a/src/mod_adhoc.erl
+++ b/src/mod_adhoc.erl
@@ -233,7 +233,7 @@ process_sm_iq(From, To, IQ) ->
process_adhoc_request(From, To, IQ, adhoc_sm_commands).
process_adhoc_request(From, To,
- #iq{sub_el = SubEl} = IQ, Hook) ->
+ #iq{sub_el = SubEl, lang = Lang} = IQ, Hook) ->
?DEBUG("About to parse ~p...", [IQ]),
case adhoc:parse_request(IQ) of
{error, Error} ->
@@ -245,8 +245,9 @@ process_adhoc_request(From, To,
of
ignore -> ignore;
empty ->
+ Txt = <<"No hook has processed this command">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
+ 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]}
@@ -277,7 +278,9 @@ ping_command(_Acc, _From, _To,
[{<<"info">>,
translate:translate(Lang,
<<"Pong">>)}]});
- true -> {error, ?ERR_BAD_REQUEST}
+ true ->
+ Txt = <<"Incorrect value of 'action' attribute">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
ping_command(Acc, _From, _To, _Request) -> Acc.
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
index dae775e47..b12a80007 100644
--- a/src/mod_admin_extra.erl
+++ b/src/mod_admin_extra.erl
@@ -31,7 +31,7 @@
-include("logger.hrl").
-export([start/2, stop/1, compile/1, get_cookie/0,
- remove_node/1, set_password/3,
+ 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,
num_active_users/2, num_resources/2, resource_num/3,
@@ -101,89 +101,156 @@ get_commands_spec() ->
desc = "Recompile and reload Erlang source code file",
module = ?MODULE, function = compile,
args = [{file, string}],
- result = {res, rescode}},
+ args_example = ["/home/me/srcs/ejabberd/mod_example.erl"],
+ args_desc = ["Filename of erlang source file to compile"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = get_cookie, tags = [erlang],
desc = "Get the Erlang cookie of this node",
module = ?MODULE, function = get_cookie,
args = [],
- result = {cookie, string}},
+ result = {cookie, string},
+ result_example = "MWTAVMODFELNLSMYXPPD",
+ result_desc = "Erlang cookie used for authentication by ejabberd"},
#ejabberd_commands{name = remove_node, tags = [erlang],
desc = "Remove an ejabberd node from Mnesia clustering config",
module = ?MODULE, function = remove_node,
args = [{node, string}],
- result = {res, rescode}},
-
+ args_example = ["ejabberd@server2"],
+ args_desc = ["Name of erlang node to remove"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = num_active_users, tags = [accounts, stats],
desc = "Get number of users active in the last days",
policy = admin,
module = ?MODULE, function = num_active_users,
args = [{host, binary}, {days, integer}],
- result = {users, integer}},
+ args_example = [<<"myserver.com">>, 3],
+ args_desc = ["Name of host to check", "Number of days to calculate sum"],
+ result = {users, integer},
+ result_example = 123,
+ result_desc = "Number of users active on given server in last n days"},
#ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
desc = "Delete users that didn't log in last days, or that never logged",
module = ?MODULE, function = delete_old_users,
args = [{days, integer}],
- result = {res, restuple}},
+ args_example = [30],
+ args_desc = ["Last login age in days of accounts that should be removed"],
+ result = {res, restuple},
+ result_example = {ok, <<"Deleted 2 users: [\"oldman@myserver.com\", \"test@myserver.com\"]">>},
+ result_desc = "Result tuple"},
#ejabberd_commands{name = delete_old_users_vhost, tags = [accounts, purge],
desc = "Delete users that didn't log in last days in vhost, or that never logged",
module = ?MODULE, function = delete_old_users_vhost,
args = [{host, binary}, {days, integer}],
- result = {res, restuple}},
-
+ args_example = [<<"myserver.com">>, 30],
+ args_desc = ["Server name",
+ "Last login age in days of accounts that should be removed"],
+ result = {res, restuple},
+ result_example = {ok, <<"Deleted 2 users: [\"oldman@myserver.com\", \"test@myserver.com\"]">>},
+ result_desc = "Result tuple"},
#ejabberd_commands{name = check_account, tags = [accounts],
desc = "Check if an account exists or not",
module = ejabberd_auth, function = is_user_exists,
args = [{user, binary}, {host, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>],
+ args_desc = ["User name to check", "Server to check"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password, tags = [accounts],
desc = "Check if a password is correct",
- module = ejabberd_auth, function = check_password,
+ module = ?MODULE, function = check_password,
args = [{user, binary}, {host, binary}, {password, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>],
+ args_desc = ["User name to check", "Server to check", "Password to check"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password_hash, tags = [accounts],
desc = "Check if the password hash is correct",
longdesc = "Allowed hash methods: md5, sha.",
module = ?MODULE, function = check_password_hash,
- args = [{user, binary}, {host, binary}, {passwordhash, string}, {hashmethod, string}],
- result = {res, rescode}},
+ args = [{user, binary}, {host, binary}, {passwordhash, string},
+ {hashmethod, string}],
+ args_example = [<<"peter">>, <<"myserver.com">>,
+ <<"5ebe2294ecd0e0f08eab7690d2a6ee69">>, <<"md5">>],
+ args_desc = ["User name to check", "Server to check",
+ "Password's hash value", "Name of hash method"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = change_password, tags = [accounts],
desc = "Change the password of an account",
module = ?MODULE, function = set_password,
args = [{user, binary}, {host, binary}, {newpass, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>, <<"blank">>],
+ args_desc = ["User name", "Server name",
+ "New password for user"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = ban_account, tags = [accounts],
desc = "Ban an account: kick sessions and set random password",
module = ?MODULE, function = ban_account,
args = [{user, binary}, {host, binary}, {reason, binary}],
- result = {res, rescode}},
-
+ args_example = [<<"attacker">>, <<"myserver.com">>, <<"Spaming other users">>],
+ args_desc = ["User name to ban", "Server name",
+ "Reason for banning user"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = num_resources, tags = [session],
desc = "Get the number of resources of a user",
module = ?MODULE, function = num_resources,
args = [{user, binary}, {host, binary}],
- result = {resources, integer}},
+ args_example = [<<"peter">>, <<"myserver.com">>],
+ args_desc = ["User name", "Server name"],
+ result = {resources, integer},
+ result_example = 5,
+ result_desc = "Number of active resources for a user"},
#ejabberd_commands{name = resource_num, tags = [session],
desc = "Resource string of a session number",
module = ?MODULE, function = resource_num,
args = [{user, binary}, {host, binary}, {num, integer}],
- result = {resource, string}},
+ args_example = [<<"peter">>, <<"myserver.com">>, 2],
+ args_desc = ["User name", "Server name", "ID of resource to return"],
+ result = {resource, string},
+ result_example = <<"Psi">>,
+ result_desc = "Name of user resource"},
#ejabberd_commands{name = kick_session, tags = [session],
desc = "Kick a user session",
module = ?MODULE, function = kick_session,
args = [{user, binary}, {host, binary}, {resource, binary}, {reason, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>, <<"Psi">>,
+ <<"Stuck connection">>],
+ args_desc = ["User name", "Server name", "User's resource",
+ "Reason for closing session"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = status_num_host, tags = [session, stats],
desc = "Number of logged users with this status in host",
policy = admin,
module = ?MODULE, function = status_num,
args = [{host, binary}, {status, binary}],
- result = {users, integer}},
+ args_example = [<<"myserver.com">>, <<"dnd">>],
+ args_desc = ["Server name", "Status type to check"],
+ result = {users, integer},
+ result_example = 23,
+ result_desc = "Number of connected sessions with given status type"},
#ejabberd_commands{name = status_num, tags = [session, stats],
desc = "Number of logged users with this status",
policy = admin,
module = ?MODULE, function = status_num,
args = [{status, binary}],
- result = {users, integer}},
+ args_example = [<<"dnd">>],
+ args_desc = ["Status type to check"],
+ result = {users, integer},
+ result_example = 23,
+ result_desc = "Number of connected sessions with given status type"},
#ejabberd_commands{name = status_list_host, tags = [session],
desc = "List of users logged in host with their statuses",
module = ?MODULE, function = status_list,
@@ -464,7 +531,7 @@ get_commands_spec() ->
tags = [offline],
desc = "Get the number of unread offline messages",
policy = user,
- module = mod_offline, function = get_queue_length,
+ module = mod_offline, function = count_offline_messages,
args = [],
result = {res, integer}},
#ejabberd_commands{name = send_message, tags = [stanza],
@@ -523,36 +590,38 @@ remove_node(Node) ->
%%%
set_password(User, Host, Password) ->
- case ejabberd_auth:set_password(User, Host, Password) of
- ok ->
- ok;
- _ ->
- error
- end.
+ Fun = fun () -> ejabberd_auth:set_password(User, Host, Password) end,
+ user_action(User, Host, Fun, ok).
+
+check_password(User, Host, Password) ->
+ ejabberd_auth:check_password(User, <<>>, Host, Password).
%% Copied some code from ejabberd_commands.erl
check_password_hash(User, Host, PasswordHash, HashMethod) ->
AccountPass = ejabberd_auth:get_password_s(User, Host),
AccountPassHash = case {AccountPass, HashMethod} of
{A, _} when is_tuple(A) -> scrammed;
- {_, "md5"} -> get_md5(AccountPass);
- {_, "sha"} -> get_sha(AccountPass);
- _ -> undefined
+ {_, <<"md5">>} -> get_md5(AccountPass);
+ {_, <<"sha">>} -> get_sha(AccountPass);
+ {_, Method} ->
+ ?ERROR_MSG("check_password_hash called "
+ "with hash method: ~p", [Method]),
+ undefined
end,
case AccountPassHash of
scrammed ->
- ?ERROR_MSG("Passwords are scrammed, and check_password_hash can not work.", []),
+ ?ERROR_MSG("Passwords are scrammed, and check_password_hash cannot work.", []),
throw(passwords_scrammed_command_cannot_work);
- undefined -> error;
+ undefined -> throw(unkown_hash_method);
PasswordHash -> ok;
- _ -> error
+ _ -> false
end.
get_md5(AccountPass) ->
- lists:flatten([io_lib:format("~.16B", [X])
- || X <- binary_to_list(erlang:md5(AccountPass))]).
+ iolist_to_binary([io_lib:format("~2.16.0B", [X])
+ || X <- binary_to_list(erlang:md5(AccountPass))]).
get_sha(AccountPass) ->
- lists:flatten([io_lib:format("~.16B", [X])
- || X <- binary_to_list(p1_sha:sha1(AccountPass))]).
+ iolist_to_binary([io_lib:format("~2.16.0B", [X])
+ || X <- binary_to_list(p1_sha:sha1(AccountPass))]).
num_active_users(Host, Days) ->
list_last_activity(Host, true, Days).
@@ -681,21 +750,7 @@ kick_sessions(User, Server, Reason) ->
fun(Resource) ->
kick_this_session(User, Server, Resource, Reason)
end,
- get_resources(User, Server)).
-
-get_resources(User, Server) ->
- lists:map(
- fun(Session) ->
- element(3, Session#session.usr)
- end,
- get_sessions(User, Server)).
-
-get_sessions(User, Server) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
- Sessions = mnesia:dirty_index_read(session, {LUser, LServer}, #session.us),
- true = is_list(Sessions),
- Sessions.
+ ejabberd_sm:get_user_resources(User, Server)).
set_random_password(User, Server, Reason) ->
NewPass = build_random_password(Reason),
@@ -729,7 +784,8 @@ resource_num(User, Host, Num) ->
true ->
lists:nth(Num, Resources);
false ->
- lists:flatten(io_lib:format("Error: Wrong resource number: ~p", [Num]))
+ throw({bad_argument,
+ lists:flatten(io_lib:format("Wrong resource number: ~p", [Num]))})
end.
kick_session(User, Server, Resource, ReasonText) ->
@@ -794,7 +850,8 @@ connected_users_info() ->
PI when is_integer(PI) -> PI;
_ -> nil
end,
- {[U, $@, S, $/, R], atom_to_list(Conn), IPS, Port, PriorityI, NodeS, Uptime}
+ {binary_to_list(<<U/binary, $@, S/binary, $/, R/binary>>),
+ atom_to_list(Conn), IPS, Port, PriorityI, NodeS, Uptime}
end,
USRIs).
@@ -912,7 +969,7 @@ get_vcard_content(User, Server, Data) ->
[_|_] ->
case get_vcard(Data, A1) of
[false] -> throw(error_no_value_found_in_vcard);
- ElemList -> ?DEBUG("ELS ~p", [ElemList]), [xml:get_tag_cdata(Elem) || Elem <- ElemList]
+ ElemList -> ?DEBUG("ELS ~p", [ElemList]), [fxml:get_tag_cdata(Elem) || Elem <- ElemList]
end;
[] ->
throw(error_no_vcard_found)
@@ -933,7 +990,7 @@ get_vcard([Data], A1) ->
get_subtag(A1, Data).
get_subtag(Xmlelement, Name) ->
- [xml:get_subtag(Xmlelement, Name)].
+ [fxml:get_subtag(Xmlelement, Name)].
set_vcard_content(User, Server, Data, SomeContent) ->
ContentList = case SomeContent of
@@ -963,7 +1020,7 @@ set_vcard_content(User, Server, Data, SomeContent) ->
take_vcard_tel(TelType, [{xmlel, <<"TEL">>, _, SubEls}=OldEl | OldEls], NewEls, Taken) ->
{Taken2, NewEls2} = case lists:keymember(TelType, 2, SubEls) of
- true -> {xml:get_subtag(OldEl, <<"NUMBER">>), NewEls};
+ true -> {fxml:get_subtag(OldEl, <<"NUMBER">>), NewEls};
false -> {Taken, [OldEl | NewEls]}
end,
take_vcard_tel(TelType, OldEls, NewEls2, Taken2);
@@ -1126,7 +1183,7 @@ push_roster_item(LU, LS, R, U, S, Action) ->
ejabberd_sm:route(LJID, LJID, BroadcastEl),
Item = build_roster_item(U, S, Action),
ResIQ = build_iq_roster_push(Item),
- ejabberd_router:route(LJID, LJID, ResIQ).
+ ejabberd_router:route(jid:remove_resource(LJID), LJID, ResIQ).
build_roster_item(U, S, {add, Nick, Subs, Group}) ->
{xmlel, <<"item">>,
@@ -1206,10 +1263,10 @@ private_get(Username, Host, Element, Ns) ->
[{xmlel, <<"query">>,
[{<<"xmlns">>, ?NS_PRIVATE}],
[SubEl]}] = ResIq#iq.sub_el,
- binary_to_list(xml:element_to_binary(SubEl)).
+ binary_to_list(fxml:element_to_binary(SubEl)).
private_set(Username, Host, ElementString) ->
- case xml_stream:parse_element(ElementString) of
+ case fxml_stream:parse_element(ElementString) of
{error, Error} ->
io:format("Error found parsing the element:~n ~p~nError: ~p~n",
[ElementString, Error]),
@@ -1255,8 +1312,7 @@ srg_get_info(Group, Host) ->
Os when is_list(Os) -> Os;
error -> []
end,
- [{jlib:atom_to_binary(Title),
- io_lib:format("~p", [btl(Value)])} || {Title, Value} <- Opts].
+ [{jlib:atom_to_binary(Title), btl(Value)} || {Title, Value} <- Opts].
btl([]) -> [];
btl([B|L]) -> [btl(B)|btl(L)];
@@ -1333,7 +1389,7 @@ build_packet(Type, Subject, Body) ->
}.
send_stanza(FromString, ToString, Stanza) ->
- case xml_stream:parse_element(Stanza) of
+ case fxml_stream:parse_element(Stanza) of
{error, Error} ->
{error, Error};
XmlEl ->
@@ -1344,7 +1400,7 @@ send_stanza(FromString, ToString, Stanza) ->
end.
send_stanza_c2s(Username, Host, Resource, Stanza) ->
- case {xml_stream:parse_element(Stanza),
+ case {fxml_stream:parse_element(Stanza),
ejabberd_sm:get_session_pid(Username, Host, Resource)}
of
{{error, Error}, _} ->
@@ -1358,7 +1414,7 @@ send_stanza_c2s(Username, Host, Resource, Stanza) ->
privacy_set(Username, Host, QueryS) ->
From = jid:make(Username, Host, <<"">>),
To = jid:make(<<"">>, Host, <<"">>),
- QueryEl = xml_stream:parse_element(QueryS),
+ QueryEl = fxml_stream:parse_element(QueryS),
StanzaEl = {xmlel, <<"iq">>, [{<<"type">>, <<"set">>}], [QueryEl]},
IQ = jlib:iq_query_info(StanzaEl),
ejabberd_hooks:run_fold(
@@ -1515,6 +1571,20 @@ decide_rip_jid({UName, UServer}, Match_list) ->
end,
Match_list).
+user_action(User, Server, Fun, OK) ->
+ case ejabberd_auth:is_user_exists(User, Server) of
+ true ->
+ case catch Fun() of
+ OK -> ok;
+ {error, Error} -> throw(Error);
+ Error ->
+ ?ERROR_MSG("Command returned: ~p", [Error]),
+ 1
+ end;
+ false ->
+ throw({not_found, "unknown_user"})
+ end.
+
%% Copied from ejabberd-2.0.0/src/acl.erl
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
diff --git a/src/mod_announce.erl b/src/mod_announce.erl
index 3d0924a6d..d7251c50b 100644
--- a/src/mod_announce.erl
+++ b/src/mod_announce.erl
@@ -41,11 +41,16 @@
-include("logger.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
+-include("mod_announce.hrl").
--record(motd, {server = <<"">> :: binary(),
- packet = #xmlel{} :: xmlel()}).
--record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
- dummy = [] :: [] | '_'}).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #motd{} | #motd_users{}) -> ok | pass.
+-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}.
+-callback set_motd(binary(), xmlel()) -> {atomic, any()}.
+-callback delete_motd(binary()) -> {atomic, any()}.
+-callback get_motd(binary()) -> {ok, xmlel()} | error.
+-callback is_motd_user(binary(), binary()) -> boolean().
+-callback set_motd_user(binary(), binary()) -> {atomic, any()}.
-define(PROCNAME, ejabberd_announce).
@@ -55,20 +60,8 @@
tokenize(Node) -> str:tokens(Node, <<"/#">>).
start(Host, Opts) ->
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(motd,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, motd)}]),
- mnesia:create_table(motd_users,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, motd_users)}]),
- update_tables();
- _ ->
- ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, announce, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
@@ -211,15 +204,15 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
%%-------------------------------------------------------------------------
--define(INFO_RESULT(Allow, Feats),
+-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
deny ->
- {error, ?ERR_FORBIDDEN};
+ {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
{result, Feats}
end).
-disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, _Lang) ->
+disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -229,13 +222,14 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, _Lang)
case {acl:match_rule(LServer, Access1, From),
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
- {error, ?ERR_FORBIDDEN};
+ Txt = <<"Denied by ACL">>,
+ {error, ?ERRT_FORBIDDEN(Lang, Txt)};
_ ->
{result, []}
end
end;
-disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
+disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -246,25 +240,25 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
case Node of
?NS_ADMIN_ANNOUNCE ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_ANNOUNCE_ALL ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_SET_MOTD ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_EDIT_MOTD ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_DELETE_MOTD ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
- ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+ ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
- ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+ ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_SET_MOTD_ALLHOSTS ->
- ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+ ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
- ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+ ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
- ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+ ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
_ ->
Acc
end
@@ -283,10 +277,10 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
}
)).
--define(ITEMS_RESULT(Allow, Items),
+-define(ITEMS_RESULT(Allow, Items, Lang),
case Allow of
deny ->
- {error, ?ERR_FORBIDDEN};
+ {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
{result, Items}
end).
@@ -320,7 +314,7 @@ disco_items(Acc, From, #jid{lserver = LServer} = To, <<"announce">>, Lang) ->
announce_items(Acc, From, To, Lang)
end;
-disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
+disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -331,25 +325,25 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
case Node of
?NS_ADMIN_ANNOUNCE ->
- ?ITEMS_RESULT(Allow, []);
+ ?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_ANNOUNCE_ALL ->
- ?ITEMS_RESULT(Allow, []);
+ ?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_SET_MOTD ->
- ?ITEMS_RESULT(Allow, []);
+ ?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_EDIT_MOTD ->
- ?ITEMS_RESULT(Allow, []);
+ ?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_DELETE_MOTD ->
- ?ITEMS_RESULT(Allow, []);
+ ?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
- ?ITEMS_RESULT(AllowGlobal, []);
+ ?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
- ?ITEMS_RESULT(AllowGlobal, []);
+ ?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_SET_MOTD_ALLHOSTS ->
- ?ITEMS_RESULT(AllowGlobal, []);
+ ?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
- ?ITEMS_RESULT(AllowGlobal, []);
+ ?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
- ?ITEMS_RESULT(AllowGlobal, []);
+ ?ITEMS_RESULT(AllowGlobal, [], Lang);
_ ->
Acc
end
@@ -396,7 +390,8 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang)
commands_result(Allow, From, To, Request) ->
case Allow of
deny ->
- {error, ?ERR_FORBIDDEN};
+ Lang = Request#adhoc_request.lang,
+ {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
announce_commands(From, To, Request)
end.
@@ -463,12 +458,13 @@ announce_commands(From, To,
%% User returns form.
case jlib:parse_xdata_submit(XData) of
invalid ->
- {error, ?ERR_BAD_REQUEST};
+ {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
Fields ->
handle_adhoc_form(From, To, Request, Fields)
end;
true ->
- {error, ?ERR_BAD_REQUEST}
+ Txt = <<"Incorrect action or data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end.
-define(VVALUE(Val),
@@ -688,7 +684,9 @@ announce_all(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ 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);
allow ->
Local = jid:make(<<>>, To#jid.server, <<>>),
@@ -703,7 +701,9 @@ announce_all_hosts_all(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ 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);
allow ->
Local = jid:make(<<>>, To#jid.server, <<>>),
@@ -719,7 +719,9 @@ announce_online(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ 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);
allow ->
announce_online1(ejabberd_sm:get_vh_session_list(Host),
@@ -731,7 +733,9 @@ announce_all_hosts_online(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ 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);
allow ->
announce_online1(ejabberd_sm:dirty_get_sessions_list(),
@@ -752,7 +756,9 @@ announce_motd(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ 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);
allow ->
announce_motd(Host, Packet)
@@ -762,7 +768,9 @@ announce_all_hosts_motd(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ 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);
allow ->
Hosts = ?MYHOSTS,
@@ -774,48 +782,17 @@ announce_motd(Host, Packet) ->
announce_motd_update(LServer, Packet),
Sessions = ejabberd_sm:get_vh_session_list(LServer),
announce_online1(Sessions, LServer, Packet),
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia ->
- F = fun() ->
- lists:foreach(
- fun({U, S, _R}) ->
- mnesia:write(#motd_users{us = {U, S}})
- end, Sessions)
- end,
- mnesia:transaction(F);
- riak ->
- try
- lists:foreach(
- fun({U, S, _R}) ->
- ok = ejabberd_riak:put(#motd_users{us = {U, S}},
- motd_users_schema(),
- [{'2i', [{<<"server">>, S}]}])
- end, Sessions),
- {atomic, ok}
- catch _:{badmatch, Err} ->
- {atomic, Err}
- end;
- odbc ->
- F = fun() ->
- lists:foreach(
- fun({U, _S, _R}) ->
- Username = ejabberd_odbc:escape(U),
- odbc_queries:update_t(
- <<"motd">>,
- [<<"username">>, <<"xml">>],
- [Username, <<"">>],
- [<<"username='">>, Username, <<"'">>])
- end, Sessions)
- end,
- ejabberd_odbc:sql_transaction(LServer, F)
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_motd_users(LServer, Sessions).
announce_motd_update(From, To, Packet) ->
Host = To#jid.lserver,
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ 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);
allow ->
announce_motd_update(Host, Packet)
@@ -825,7 +802,9 @@ announce_all_hosts_motd_update(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ 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);
allow ->
Hosts = ?MYHOSTS,
@@ -834,34 +813,17 @@ announce_all_hosts_motd_update(From, To, Packet) ->
announce_motd_update(LServer, Packet) ->
announce_motd_delete(LServer),
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia ->
- F = fun() ->
- mnesia:write(#motd{server = LServer, packet = Packet})
- end,
- mnesia:transaction(F);
- riak ->
- {atomic, ejabberd_riak:put(#motd{server = LServer,
- packet = Packet},
- motd_schema())};
- odbc ->
- XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)),
- F = fun() ->
- odbc_queries:update_t(
- <<"motd">>,
- [<<"username">>, <<"xml">>],
- [<<"">>, XML],
- [<<"username=''">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F)
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_motd(LServer, Packet).
announce_motd_delete(From, To, Packet) ->
Host = To#jid.lserver,
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ 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);
allow ->
announce_motd_delete(Host)
@@ -871,7 +833,9 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ 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);
allow ->
Hosts = ?MYHOSTS,
@@ -879,147 +843,37 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
end.
announce_motd_delete(LServer) ->
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia ->
- F = fun() ->
- mnesia:delete({motd, LServer}),
- mnesia:write_lock_table(motd_users),
- Users = mnesia:select(
- motd_users,
- [{#motd_users{us = '$1', _ = '_'},
- [{'==', {element, 2, '$1'}, LServer}],
- ['$1']}]),
- lists:foreach(fun(US) ->
- mnesia:delete({motd_users, US})
- end, Users)
- end,
- mnesia:transaction(F);
- riak ->
- try
- ok = ejabberd_riak:delete(motd, LServer),
- ok = ejabberd_riak:delete_by_index(motd_users,
- <<"server">>,
- LServer),
- {atomic, ok}
- catch _:{badmatch, Err} ->
- {atomic, Err}
- end;
- odbc ->
- F = fun() ->
- ejabberd_odbc:sql_query_t([<<"delete from motd;">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F)
- end.
-
-send_motd(JID) ->
- send_motd(JID, gen_mod:db_type(JID#jid.lserver, ?MODULE)).
-
-send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
- case catch mnesia:dirty_read({motd, LServer}) of
- [#motd{packet = Packet}] ->
- US = {LUser, LServer},
- case catch mnesia:dirty_read({motd_users, US}) of
- [#motd_users{}] ->
- ok;
- _ ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:delete_motd(LServer).
+
+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),
- F = fun() ->
- mnesia:write(#motd_users{us = US})
- end,
- mnesia:transaction(F)
+ Mod:set_motd_user(LUser, LServer);
+ true ->
+ ok
end;
- _ ->
+ error ->
ok
end;
-send_motd(#jid{luser = LUser, lserver = LServer} = JID, riak) ->
- case catch ejabberd_riak:get(motd, motd_schema(), LServer) of
- {ok, #motd{packet = Packet}} ->
- US = {LUser, LServer},
- case ejabberd_riak:get(motd_users, motd_users_schema(), US) of
- {ok, #motd_users{}} ->
- ok;
- _ ->
- Local = jid:make(<<>>, LServer, <<>>),
- ejabberd_router:route(Local, JID, Packet),
- {atomic, ejabberd_riak:put(
- #motd_users{us = US}, motd_users_schema(),
- [{'2i', [{<<"server">>, LServer}]}])}
- end;
- _ ->
- ok
- end;
-send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>> ->
- case catch ejabberd_odbc:sql_query(
- LServer, [<<"select xml from motd where username='';">>]) of
- {selected, [<<"xml">>], [[XML]]} ->
- case xml_stream:parse_element(XML) of
- {error, _} ->
- ok;
- Packet ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(
- LServer,
- [<<"select username from motd "
- "where username='">>, Username, <<"';">>]) of
- {selected, [<<"username">>], []} ->
- Local = jid:make(<<"">>, LServer, <<"">>),
- ejabberd_router:route(Local, JID, Packet),
- F = fun() ->
- odbc_queries:update_t(
- <<"motd">>,
- [<<"username">>, <<"xml">>],
- [Username, <<"">>],
- [<<"username='">>, Username, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F);
- _ ->
- ok
- end
- end;
- _ ->
- ok
- end;
-send_motd(_, odbc) ->
+send_motd(_) ->
ok.
get_stored_motd(LServer) ->
- case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:get_motd(LServer) of
{ok, Packet} ->
- {xml:get_subtag_cdata(Packet, <<"subject">>),
- xml:get_subtag_cdata(Packet, <<"body">>)};
+ {fxml:get_subtag_cdata(Packet, <<"subject">>),
+ fxml:get_subtag_cdata(Packet, <<"body">>)};
error ->
{<<>>, <<>>}
end.
-get_stored_motd_packet(LServer, mnesia) ->
- case catch mnesia:dirty_read({motd, LServer}) of
- [#motd{packet = Packet}] ->
- {ok, Packet};
- _ ->
- error
- end;
-get_stored_motd_packet(LServer, riak) ->
- case ejabberd_riak:get(motd, motd_schema(), LServer) of
- {ok, #motd{packet = Packet}} ->
- {ok, Packet};
- _ ->
- error
- end;
-get_stored_motd_packet(LServer, odbc) ->
- case catch ejabberd_odbc:sql_query(
- LServer, [<<"select xml from motd where username='';">>]) of
- {selected, [<<"xml">>], [[XML]]} ->
- case xml_stream:parse_element(XML) of
- {error, _} ->
- error;
- Packet ->
- {ok, Packet}
- 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 /= <<>> ->
@@ -1053,96 +907,17 @@ get_access(Host) ->
none).
%%-------------------------------------------------------------------------
-
-update_tables() ->
- update_motd_table(),
- update_motd_users_table().
-
-update_motd_table() ->
- Fields = record_info(fields, motd),
- case mnesia:table_info(motd, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- motd, Fields, set,
- fun(#motd{server = S}) -> S end,
- fun(#motd{server = S, packet = P} = R) ->
- NewS = iolist_to_binary(S),
- NewP = xml:to_xmlel(P),
- R#motd{server = NewS, packet = NewP}
- end);
- _ ->
- ?INFO_MSG("Recreating motd table", []),
- mnesia:transform_table(motd, ignore, Fields)
- end.
-
-
-update_motd_users_table() ->
- Fields = record_info(fields, motd_users),
- case mnesia:table_info(motd_users, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- motd_users, Fields, set,
- fun(#motd_users{us = {U, _}}) -> U end,
- fun(#motd_users{us = {U, S}} = R) ->
- NewUS = {iolist_to_binary(U),
- iolist_to_binary(S)},
- R#motd_users{us = NewUS}
- end);
- _ ->
- ?INFO_MSG("Recreating motd_users table", []),
- mnesia:transform_table(motd_users, ignore, Fields)
- end.
-
-motd_schema() ->
- {record_info(fields, motd), #motd{}}.
-
-motd_users_schema() ->
- {record_info(fields, motd_users), #motd_users{}}.
-
-export(_Server) ->
- [{motd,
- fun(Host, #motd{server = LServer, packet = El})
- when LServer == Host ->
- [[<<"delete from motd where username='';">>],
- [<<"insert into motd(username, xml) values ('', '">>,
- ejabberd_odbc:escape(xml:element_to_binary(El)),
- <<"');">>]];
- (_Host, _R) ->
- []
- end},
- {motd_users,
- fun(Host, #motd_users{us = {LUser, LServer}})
- when LServer == Host, LUser /= <<"">> ->
- Username = ejabberd_odbc:escape(LUser),
- [[<<"delete from motd where username='">>, Username, <<"';">>],
- [<<"insert into motd(username, xml) values ('">>,
- Username, <<"', '');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select xml from motd where username='';">>,
- fun([XML]) ->
- El = xml_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(_LServer, mnesia, #motd{} = Motd) ->
- mnesia:dirty_write(Motd);
-import(_LServer, mnesia, #motd_users{} = Users) ->
- mnesia:dirty_write(Users);
-import(_LServer, riak, #motd{} = Motd) ->
- ejabberd_riak:put(Motd, motd_schema());
-import(_LServer, riak, #motd_users{us = {_, S}} = Users) ->
- ejabberd_riak:put(Users, motd_users_schema(),
- [{'2i', [{<<"server">>, S}]}]);
-import(_, _, _) ->
- pass.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+import(LServer, DBType, LA) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, LA).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
diff --git a/src/mod_announce_mnesia.erl b/src/mod_announce_mnesia.erl
new file mode 100644
index 000000000..c43eb853b
--- /dev/null
+++ b/src/mod_announce_mnesia.erl
@@ -0,0 +1,129 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_announce_mnesia).
+-behaviour(mod_announce).
+
+%% 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]).
+
+-include("jlib.hrl").
+-include("mod_announce.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(motd,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, motd)}]),
+ mnesia:create_table(motd_users,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, motd_users)}]),
+ update_tables().
+
+set_motd_users(_LServer, USRs) ->
+ F = fun() ->
+ lists:foreach(
+ fun({U, S, _R}) ->
+ mnesia:write(#motd_users{us = {U, S}})
+ end, USRs)
+ end,
+ mnesia:transaction(F).
+
+set_motd(LServer, Packet) ->
+ F = fun() ->
+ mnesia:write(#motd{server = LServer, packet = Packet})
+ end,
+ mnesia:transaction(F).
+
+delete_motd(LServer) ->
+ F = fun() ->
+ mnesia:delete({motd, LServer}),
+ mnesia:write_lock_table(motd_users),
+ Users = mnesia:select(
+ motd_users,
+ [{#motd_users{us = '$1', _ = '_'},
+ [{'==', {element, 2, '$1'}, LServer}],
+ ['$1']}]),
+ lists:foreach(fun(US) ->
+ mnesia:delete({motd_users, US})
+ end, Users)
+ end,
+ mnesia:transaction(F).
+
+get_motd(LServer) ->
+ case mnesia:dirty_read({motd, LServer}) of
+ [#motd{packet = Packet}] ->
+ {ok, Packet};
+ _ ->
+ error
+ end.
+
+is_motd_user(LUser, LServer) ->
+ case mnesia:dirty_read({motd_users, {LUser, LServer}}) of
+ [#motd_users{}] -> true;
+ _ -> false
+ end.
+
+set_motd_user(LUser, LServer) ->
+ F = fun() ->
+ mnesia:write(#motd_users{us = {LUser, LServer}})
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #motd{} = Motd) ->
+ mnesia:dirty_write(Motd);
+import(_LServer, #motd_users{} = Users) ->
+ mnesia:dirty_write(Users).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_tables() ->
+ update_motd_table(),
+ update_motd_users_table().
+
+update_motd_table() ->
+ Fields = record_info(fields, motd),
+ case mnesia:table_info(motd, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ motd, Fields, set,
+ fun(#motd{server = S}) -> S end,
+ fun(#motd{server = S, packet = P} = R) ->
+ NewS = iolist_to_binary(S),
+ NewP = fxml:to_xmlel(P),
+ R#motd{server = NewS, packet = NewP}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating motd table", []),
+ mnesia:transform_table(motd, ignore, Fields)
+ end.
+
+
+update_motd_users_table() ->
+ Fields = record_info(fields, motd_users),
+ case mnesia:table_info(motd_users, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ motd_users, Fields, set,
+ fun(#motd_users{us = {U, _}}) -> U end,
+ fun(#motd_users{us = {U, S}} = R) ->
+ NewUS = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ R#motd_users{us = NewUS}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating motd_users table", []),
+ mnesia:transform_table(motd_users, ignore, Fields)
+ end.
diff --git a/src/mod_announce_riak.erl b/src/mod_announce_riak.erl
new file mode 100644
index 000000000..7ced0b3ce
--- /dev/null
+++ b/src/mod_announce_riak.erl
@@ -0,0 +1,87 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_announce_riak).
+-behaviour(mod_announce).
+
+%% 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]).
+
+-include("jlib.hrl").
+-include("mod_announce.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+set_motd_users(_LServer, USRs) ->
+ try
+ lists:foreach(
+ fun({U, S, _R}) ->
+ ok = ejabberd_riak:put(#motd_users{us = {U, S}},
+ motd_users_schema(),
+ [{'2i', [{<<"server">>, S}]}])
+ end, USRs),
+ {atomic, ok}
+ catch _:{badmatch, Err} ->
+ {atomic, Err}
+ end.
+
+set_motd(LServer, Packet) ->
+ {atomic, ejabberd_riak:put(#motd{server = LServer,
+ packet = Packet},
+ motd_schema())}.
+
+delete_motd(LServer) ->
+ try
+ ok = ejabberd_riak:delete(motd, LServer),
+ ok = ejabberd_riak:delete_by_index(motd_users,
+ <<"server">>,
+ LServer),
+ {atomic, ok}
+ catch _:{badmatch, Err} ->
+ {atomic, Err}
+ end.
+
+get_motd(LServer) ->
+ case ejabberd_riak:get(motd, motd_schema(), LServer) of
+ {ok, #motd{packet = Packet}} ->
+ {ok, Packet};
+ _ ->
+ error
+ end.
+
+is_motd_user(LUser, LServer) ->
+ case ejabberd_riak:get(motd_users, motd_users_schema(),
+ {LUser, LServer}) of
+ {ok, #motd_users{}} -> true;
+ _ -> false
+ end.
+
+set_motd_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:put(
+ #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) ->
+ ejabberd_riak:put(Users, motd_users_schema(),
+ [{'2i', [{<<"server">>, S}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+motd_schema() ->
+ {record_info(fields, motd), #motd{}}.
+
+motd_users_schema() ->
+ {record_info(fields, motd_users), #motd_users{}}.
diff --git a/src/mod_announce_sql.erl b/src/mod_announce_sql.erl
new file mode 100644
index 000000000..692c8fae4
--- /dev/null
+++ b/src/mod_announce_sql.erl
@@ -0,0 +1,132 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_announce_sql).
+-behaviour(mod_announce).
+
+%% 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]).
+
+-include("jlib.hrl").
+-include("mod_announce.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+set_motd_users(LServer, USRs) ->
+ F = fun() ->
+ lists:foreach(
+ fun({U, _S, _R}) ->
+ Username = ejabberd_sql:escape(U),
+ sql_queries:update_t(
+ <<"motd">>,
+ [<<"username">>, <<"xml">>],
+ [Username, <<"">>],
+ [<<"username='">>, Username, <<"'">>])
+ end, USRs)
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+set_motd(LServer, Packet) ->
+ XML = ejabberd_sql:escape(fxml:element_to_binary(Packet)),
+ F = fun() ->
+ sql_queries:update_t(
+ <<"motd">>,
+ [<<"username">>, <<"xml">>],
+ [<<"">>, XML],
+ [<<"username=''">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+delete_motd(LServer) ->
+ F = fun() ->
+ ejabberd_sql:sql_query_t([<<"delete from motd;">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+get_motd(LServer) ->
+ case catch ejabberd_sql:sql_query(
+ LServer, [<<"select xml from motd where username='';">>]) of
+ {selected, [<<"xml">>], [[XML]]} ->
+ case fxml_stream:parse_element(XML) of
+ {error, _} ->
+ error;
+ Packet ->
+ {ok, Packet}
+ end;
+ _ ->
+ error
+ end.
+
+is_motd_user(LUser, LServer) ->
+ Username = ejabberd_sql:escape(LUser),
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ [<<"select username from motd "
+ "where username='">>, Username, <<"';">>]) of
+ {selected, [<<"username">>], [_|_]} ->
+ true;
+ _ ->
+ false
+ end.
+
+set_motd_user(LUser, LServer) ->
+ Username = ejabberd_sql:escape(LUser),
+ F = fun() ->
+ sql_queries:update_t(
+ <<"motd">>,
+ [<<"username">>, <<"xml">>],
+ [Username, <<"">>],
+ [<<"username='">>, Username, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+export(_Server) ->
+ [{motd,
+ fun(Host, #motd{server = LServer, packet = El})
+ when LServer == Host ->
+ [[<<"delete from motd where username='';">>],
+ [<<"insert into motd(username, xml) values ('', '">>,
+ ejabberd_sql:escape(fxml:element_to_binary(El)),
+ <<"');">>]];
+ (_Host, _R) ->
+ []
+ end},
+ {motd_users,
+ fun(Host, #motd_users{us = {LUser, LServer}})
+ when LServer == Host, LUser /= <<"">> ->
+ Username = ejabberd_sql:escape(LUser),
+ [[<<"delete from motd where username='">>, Username, <<"';">>],
+ [<<"insert into motd(username, xml) values ('">>,
+ Username, <<"', '');">>]];
+ (_Host, _R) ->
+ []
+ 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.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl
index 4f9c82734..af06e650d 100644
--- a/src/mod_blocking.erl
+++ b/src/mod_blocking.erl
@@ -39,6 +39,10 @@
-include("mod_privacy.hrl").
+-callback process_blocklist_block(binary(), binary(), function()) -> {atomic, any()}.
+-callback unblock_by_filter(binary(), binary(), function()) -> {atomic, any()}.
+-callback process_blocklist_get(binary(), binary()) -> [listitem()] | error.
+
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
@@ -64,29 +68,33 @@ process_iq(_From, _To, IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
process_iq_get(_, From, _To,
- #iq{xmlns = ?NS_BLOCKING,
+ #iq{xmlns = ?NS_BLOCKING, lang = Lang,
sub_el = #xmlel{name = <<"blocklist">>}},
_) ->
#jid{luser = LUser, lserver = LServer} = From,
- {stop, process_blocklist_get(LUser, LServer)};
+ {stop, process_blocklist_get(LUser, LServer, Lang)};
process_iq_get(Acc, _, _, _, _) -> Acc.
process_iq_set(_, From, _To,
- #iq{xmlns = ?NS_BLOCKING,
+ #iq{xmlns = ?NS_BLOCKING, lang = Lang,
sub_el =
#xmlel{name = SubElName, children = SubEls}}) ->
#jid{luser = LUser, lserver = LServer} = From,
- Res = case {SubElName, xml:remove_cdata(SubEls)} of
- {<<"block">>, []} -> {error, ?ERR_BAD_REQUEST};
+ 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);
+ process_blocklist_block(LUser, LServer, JIDs, Lang);
{<<"unblock">>, []} ->
- process_blocklist_unblock_all(LUser, LServer);
+ process_blocklist_unblock_all(LUser, LServer, Lang);
{<<"unblock">>, Els} ->
JIDs = parse_blocklist_items(Els, []),
- process_blocklist_unblock(LUser, LServer, JIDs);
- _ -> {error, ?ERR_BAD_REQUEST}
+ 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.
@@ -116,7 +124,7 @@ parse_blocklist_items([#xmlel{name = <<"item">>,
attrs = Attrs}
| Els],
JIDs) ->
- case xml:get_attr(<<"jid">>, Attrs) of
+ case fxml:get_attr(<<"jid">>, Attrs) of
{value, JID1} ->
JID = jid:tolower(jid:from_string(JID1)),
parse_blocklist_items(Els, [JID | JIDs]);
@@ -125,7 +133,7 @@ parse_blocklist_items([#xmlel{name = <<"item">>,
parse_blocklist_items([_ | Els], JIDs) ->
parse_blocklist_items(Els, JIDs).
-process_blocklist_block(LUser, LServer, JIDs) ->
+process_blocklist_block(LUser, LServer, JIDs, Lang) ->
Filter = fun (List) ->
AlreadyBlocked = list_to_blocklist_jids(List, []),
lists:foldr(fun (JID, List1) ->
@@ -143,9 +151,8 @@ process_blocklist_block(LUser, LServer, JIDs) ->
end,
List, JIDs)
end,
- case process_blocklist_block(LUser, LServer, Filter,
- gen_mod:db_type(LServer, mod_privacy))
- of
+ Mod = db_mod(LServer),
+ case Mod:process_blocklist_block(LUser, LServer, Filter) of
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
broadcast_list_update(LUser, LServer, Default,
@@ -155,110 +162,17 @@ process_blocklist_block(LUser, LServer, JIDs) ->
{result, [], UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end.
-process_blocklist_block(LUser, LServer, Filter,
- mnesia) ->
- F = fun () ->
- case mnesia:wread({privacy, {LUser, LServer}}) of
- [] ->
- P = #privacy{us = {LUser, LServer}},
- NewDefault = <<"Blocked contacts">>,
- NewLists1 = [],
- List = [];
- [#privacy{default = Default, lists = Lists} = P] ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- NewDefault = Default,
- NewLists1 = lists:keydelete(Default, 1, Lists);
- false ->
- NewDefault = <<"Blocked contacts">>,
- NewLists1 = Lists,
- List = []
- end
- end,
- NewList = Filter(List),
- NewLists = [{NewDefault, NewList} | NewLists1],
- mnesia:write(P#privacy{default = NewDefault,
- lists = NewLists}),
- {ok, NewDefault, NewList}
- end,
- mnesia:transaction(F);
-process_blocklist_block(LUser, LServer, Filter,
- riak) ->
- {atomic,
- begin
- case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
- {LUser, LServer}) of
- {ok, #privacy{default = Default, lists = Lists} = P} ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- NewDefault = Default,
- NewLists1 = lists:keydelete(Default, 1, Lists);
- false ->
- NewDefault = <<"Blocked contacts">>,
- NewLists1 = Lists,
- List = []
- end;
- {error, _} ->
- P = #privacy{us = {LUser, LServer}},
- NewDefault = <<"Blocked contacts">>,
- NewLists1 = [],
- List = []
- end,
- NewList = Filter(List),
- NewLists = [{NewDefault, NewList} | NewLists1],
- case ejabberd_riak:put(P#privacy{default = NewDefault,
- lists = NewLists},
- mod_privacy:privacy_schema()) of
- ok ->
- {ok, NewDefault, NewList};
- Err ->
- Err
- end
- end};
-process_blocklist_block(LUser, LServer, Filter, odbc) ->
- F = fun () ->
- Default = case
- mod_privacy:sql_get_default_privacy_list_t(LUser)
- of
- {selected, [<<"name">>], []} ->
- Name = <<"Blocked contacts">>,
- mod_privacy:sql_add_privacy_list(LUser, Name),
- mod_privacy:sql_set_default_privacy_list(LUser,
- Name),
- Name;
- {selected, [<<"name">>], [[Name]]} -> Name
- end,
- {selected, [<<"id">>], [[ID]]} =
- mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
- case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems = [_ | _]} ->
- List = lists:flatmap(fun mod_privacy:raw_to_item/1, RItems);
- _ -> List = []
- end,
- NewList = Filter(List),
- NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
- NewList),
- mod_privacy:sql_set_privacy_list(ID, NewRItems),
- {ok, Default, NewList}
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
-
-process_blocklist_unblock_all(LUser, LServer) ->
+process_blocklist_unblock_all(LUser, LServer, Lang) ->
Filter = fun (List) ->
lists:filter(fun (#listitem{action = A}) -> A =/= deny
end,
List)
end,
- DBType = gen_mod:db_type(LServer, mod_privacy),
- case unblock_by_filter(LUser, LServer, Filter, DBType) of
+ Mod = db_mod(LServer),
+ case Mod:unblock_by_filter(LUser, LServer, Filter) of
{atomic, ok} -> {result, []};
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
@@ -268,10 +182,10 @@ process_blocklist_unblock_all(LUser, LServer) ->
{result, [], UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end.
-process_blocklist_unblock(LUser, LServer, JIDs) ->
+process_blocklist_unblock(LUser, LServer, JIDs, Lang) ->
Filter = fun (List) ->
lists:filter(fun (#listitem{action = deny, type = jid,
value = JID}) ->
@@ -280,8 +194,8 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
end,
List)
end,
- DBType = gen_mod:db_type(LServer, mod_privacy),
- case unblock_by_filter(LUser, LServer, Filter, DBType) of
+ Mod = db_mod(LServer),
+ case Mod:unblock_by_filter(LUser, LServer, Filter) of
{atomic, ok} -> {result, []};
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
@@ -292,84 +206,9 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
{result, [], UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end.
-unblock_by_filter(LUser, LServer, Filter, mnesia) ->
- F = fun () ->
- case mnesia:read({privacy, {LUser, LServer}}) of
- [] ->
- % No lists, nothing to unblock
- ok;
- [#privacy{default = Default, lists = Lists} = P] ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- NewList = Filter(List),
- NewLists1 = lists:keydelete(Default, 1, Lists),
- NewLists = [{Default, NewList} | NewLists1],
- mnesia:write(P#privacy{lists = NewLists}),
- {ok, Default, NewList};
- false ->
- % No default list, nothing to unblock
- ok
- end
- end
- end,
- mnesia:transaction(F);
-unblock_by_filter(LUser, LServer, Filter, riak) ->
- {atomic,
- case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
- {LUser, LServer}) of
- {error, _} ->
- %% No lists, nothing to unblock
- ok;
- {ok, #privacy{default = Default, lists = Lists} = P} ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- NewList = Filter(List),
- NewLists1 = lists:keydelete(Default, 1, Lists),
- NewLists = [{Default, NewList} | NewLists1],
- case ejabberd_riak:put(P#privacy{lists = NewLists},
- mod_privacy:privacy_schema()) of
- ok ->
- {ok, Default, NewList};
- Err ->
- Err
- end;
- false ->
- %% No default list, nothing to unblock
- ok
- end
- end};
-unblock_by_filter(LUser, LServer, Filter, odbc) ->
- F = fun () ->
- case mod_privacy:sql_get_default_privacy_list_t(LUser)
- of
- {selected, [<<"name">>], []} -> ok;
- {selected, [<<"name">>], [[Default]]} ->
- {selected, [<<"id">>], [[ID]]} =
- mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
- case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems = [_ | _]} ->
- List = lists:flatmap(fun mod_privacy:raw_to_item/1,
- RItems),
- NewList = Filter(List),
- NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
- NewList),
- mod_privacy:sql_set_privacy_list(ID, NewRItems),
- {ok, Default, NewList};
- _ -> ok
- end;
- _ -> ok
- end
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
-
make_userlist(Name, List) ->
NeedDb = mod_privacy:is_list_needdb(List),
#userlist{name = Name, list = List, needdb = NeedDb}.
@@ -385,11 +224,11 @@ broadcast_blocklist_event(LUser, LServer, Event) ->
ejabberd_sm:route(JID, JID,
{broadcast, {blocking, Event}}).
-process_blocklist_get(LUser, LServer) ->
- case process_blocklist_get(LUser, LServer,
- gen_mod:db_type(LServer, mod_privacy))
- of
- error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+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">>)};
List ->
JIDs = list_to_blocklist_jids(List, []),
Items = lists:map(fun (JID) ->
@@ -407,49 +246,9 @@ process_blocklist_get(LUser, LServer) ->
children = Items}]}
end.
-process_blocklist_get(LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer})
- of
- {'EXIT', _Reason} -> error;
- [] -> [];
- [#privacy{default = Default, lists = Lists}] ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} -> List;
- _ -> []
- end
- end;
-process_blocklist_get(LUser, LServer, riak) ->
- case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
- {LUser, LServer}) of
- {ok, #privacy{default = Default, lists = Lists}} ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} -> List;
- _ -> []
- end;
- {error, notfound} ->
- [];
- {error, _} ->
- error
- end;
-process_blocklist_get(LUser, LServer, odbc) ->
- case catch
- mod_privacy:sql_get_default_privacy_list(LUser, LServer)
- of
- {selected, [<<"name">>], []} -> [];
- {selected, [<<"name">>], [[Default]]} ->
- case catch mod_privacy:sql_get_privacy_list_data(LUser,
- LServer, Default)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
- lists:flatmap(fun mod_privacy:raw_to_item/1, RItems);
- {'EXIT', _} -> error
- end;
- {'EXIT', _} -> error
- end.
+db_mod(LServer) ->
+ DBType = gen_mod:db_type(LServer, mod_privacy),
+ gen_mod:db_mod(DBType, ?MODULE).
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [iqdisc].
diff --git a/src/mod_blocking_mnesia.erl b/src/mod_blocking_mnesia.erl
new file mode 100644
index 000000000..5a4bde64c
--- /dev/null
+++ b/src/mod_blocking_mnesia.erl
@@ -0,0 +1,85 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_blocking_mnesia).
+
+-behaviour(mod_blocking).
+
+%% API
+-export([process_blocklist_block/3, unblock_by_filter/3,
+ process_blocklist_get/2]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+process_blocklist_block(LUser, LServer, Filter) ->
+ F = fun () ->
+ case mnesia:wread({privacy, {LUser, LServer}}) of
+ [] ->
+ P = #privacy{us = {LUser, LServer}},
+ NewDefault = <<"Blocked contacts">>,
+ NewLists1 = [],
+ List = [];
+ [#privacy{default = Default, lists = Lists} = P] ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewDefault = Default,
+ NewLists1 = lists:keydelete(Default, 1, Lists);
+ false ->
+ NewDefault = <<"Blocked contacts">>,
+ NewLists1 = Lists,
+ List = []
+ end
+ end,
+ NewList = Filter(List),
+ NewLists = [{NewDefault, NewList} | NewLists1],
+ mnesia:write(P#privacy{default = NewDefault,
+ lists = NewLists}),
+ {ok, NewDefault, NewList}
+ end,
+ mnesia:transaction(F).
+
+unblock_by_filter(LUser, LServer, Filter) ->
+ F = fun () ->
+ case mnesia:read({privacy, {LUser, LServer}}) of
+ [] ->
+ %% No lists, nothing to unblock
+ ok;
+ [#privacy{default = Default, lists = Lists} = P] ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewList = Filter(List),
+ NewLists1 = lists:keydelete(Default, 1, Lists),
+ NewLists = [{Default, NewList} | NewLists1],
+ mnesia:write(P#privacy{lists = NewLists}),
+ {ok, Default, NewList};
+ false ->
+ %% No default list, nothing to unblock
+ ok
+ end
+ end
+ end,
+ mnesia:transaction(F).
+
+process_blocklist_get(LUser, LServer) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ {'EXIT', _Reason} -> error;
+ [] -> [];
+ [#privacy{default = Default, lists = Lists}] ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} -> List;
+ _ -> []
+ end
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_blocking_riak.erl b/src/mod_blocking_riak.erl
new file mode 100644
index 000000000..5dd5cfa92
--- /dev/null
+++ b/src/mod_blocking_riak.erl
@@ -0,0 +1,98 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_blocking_riak).
+
+-behaviour(mod_blocking).
+
+%% API
+-export([process_blocklist_block/3, unblock_by_filter/3,
+ process_blocklist_get/2]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+process_blocklist_block(LUser, LServer, Filter) ->
+ {atomic,
+ begin
+ case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
+ {LUser, LServer}) of
+ {ok, #privacy{default = Default, lists = Lists} = P} ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewDefault = Default,
+ NewLists1 = lists:keydelete(Default, 1, Lists);
+ false ->
+ NewDefault = <<"Blocked contacts">>,
+ NewLists1 = Lists,
+ List = []
+ end;
+ {error, _} ->
+ P = #privacy{us = {LUser, LServer}},
+ NewDefault = <<"Blocked contacts">>,
+ NewLists1 = [],
+ List = []
+ end,
+ NewList = Filter(List),
+ NewLists = [{NewDefault, NewList} | NewLists1],
+ case ejabberd_riak:put(P#privacy{default = NewDefault,
+ lists = NewLists},
+ mod_privacy_riak:privacy_schema()) of
+ ok ->
+ {ok, NewDefault, NewList};
+ Err ->
+ Err
+ end
+ end}.
+
+unblock_by_filter(LUser, LServer, Filter) ->
+ {atomic,
+ case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
+ {LUser, LServer}) of
+ {error, _} ->
+ %% No lists, nothing to unblock
+ ok;
+ {ok, #privacy{default = Default, lists = Lists} = P} ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewList = Filter(List),
+ NewLists1 = lists:keydelete(Default, 1, Lists),
+ NewLists = [{Default, NewList} | NewLists1],
+ case ejabberd_riak:put(P#privacy{lists = NewLists},
+ mod_privacy_riak:privacy_schema()) of
+ ok ->
+ {ok, Default, NewList};
+ Err ->
+ Err
+ end;
+ false ->
+ %% No default list, nothing to unblock
+ ok
+ end
+ end}.
+
+process_blocklist_get(LUser, LServer) ->
+ case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
+ {LUser, LServer}) of
+ {ok, #privacy{default = Default, lists = Lists}} ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} -> List;
+ _ -> []
+ end;
+ {error, notfound} ->
+ [];
+ {error, _} ->
+ error
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_blocking_sql.erl b/src/mod_blocking_sql.erl
new file mode 100644
index 000000000..fb8380e2c
--- /dev/null
+++ b/src/mod_blocking_sql.erl
@@ -0,0 +1,87 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_blocking_sql).
+
+-behaviour(mod_blocking).
+
+%% API
+-export([process_blocklist_block/3, unblock_by_filter/3,
+ process_blocklist_get/2]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+process_blocklist_block(LUser, LServer, Filter) ->
+ F = fun () ->
+ Default = case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
+ {selected, []} ->
+ Name = <<"Blocked contacts">>,
+ mod_privacy_sql:sql_add_privacy_list(LUser, Name),
+ mod_privacy_sql:sql_set_default_privacy_list(LUser, Name),
+ Name;
+ {selected, [{Name}]} -> Name
+ end,
+ {selected, [{ID}]} =
+ mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
+ case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
+ {selected, RItems = [_ | _]} ->
+ List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
+ _ ->
+ List = []
+ end,
+ NewList = Filter(List),
+ NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
+ NewList),
+ mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
+ {ok, Default, NewList}
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+unblock_by_filter(LUser, LServer, Filter) ->
+ F = fun () ->
+ case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
+ {selected, []} -> ok;
+ {selected, [{Default}]} ->
+ {selected, [{ID}]} =
+ mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
+ case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
+ {selected, RItems = [_ | _]} ->
+ List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1,
+ RItems),
+ NewList = Filter(List),
+ NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
+ NewList),
+ mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
+ {ok, Default, NewList};
+ _ -> ok
+ end;
+ _ -> ok
+ end
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+process_blocklist_get(LUser, LServer) ->
+ case catch mod_privacy_sql:sql_get_default_privacy_list(LUser, LServer) of
+ {selected, []} -> [];
+ {selected, [{Default}]} ->
+ case catch mod_privacy_sql:sql_get_privacy_list_data(
+ LUser, LServer, Default) of
+ {selected, RItems} ->
+ lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
+ {'EXIT', _} -> error
+ end;
+ {'EXIT', _} -> error
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index 7d764c4bf..3d5c360a8 100644
--- a/src/mod_caps.erl
+++ b/src/mod_caps.erl
@@ -80,6 +80,12 @@
-record(state, {host = <<"">> :: binary()}).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback caps_read(binary(), {binary(), binary()}) ->
+ {ok, non_neg_integer() | [binary()]} | error.
+-callback caps_write(binary(), {binary(), binary()},
+ non_neg_integer() | [binary()]) -> any().
+
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE,
@@ -120,12 +126,12 @@ read_caps(Els) -> read_caps(Els, nothing).
read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
| Tail],
Result) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_CAPS ->
- Node = xml:get_attr_s(<<"node">>, Attrs),
- Version = xml:get_attr_s(<<"ver">>, Attrs),
- Hash = xml:get_attr_s(<<"hash">>, Attrs),
- Exts = str:tokens(xml:get_attr_s(<<"ext">>, Attrs),
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
+ Version = fxml:get_attr_s(<<"ver">>, Attrs),
+ Hash = fxml:get_attr_s(<<"hash">>, Attrs),
+ Exts = str:tokens(fxml:get_attr_s(<<"ext">>, Attrs),
<<" ">>),
read_caps(Tail,
#caps{node = Node, hash = Hash, version = Version,
@@ -135,7 +141,7 @@ read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
read_caps([#xmlel{name = <<"x">>, attrs = Attrs}
| Tail],
Result) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC_USER -> nothing;
_ -> read_caps(Tail, Result)
end;
@@ -149,7 +155,7 @@ user_send_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
#jid{luser = User, lserver = Server} = From,
#jid{luser = User, lserver = Server,
lresource = <<"">>}) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
if Type == <<"">>; Type == <<"available">> ->
case read_caps(Els) of
nothing -> ok;
@@ -167,7 +173,7 @@ user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
_C2SState,
#jid{lserver = Server},
From, _To) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
if IsRemote and
((Type == <<"">>) or (Type == <<"available">>)) ->
@@ -227,7 +233,7 @@ disco_info(Acc, Host, Module, Node, Lang) ->
c2s_presence_in(C2SState,
{From, To, {_, _, Attrs, Els}}) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
Subscription = ejabberd_c2s:get_subscription(From,
C2SState),
Insert = ((Type == <<"">>) or (Type == <<"available">>))
@@ -300,28 +306,9 @@ c2s_broadcast_recipients(InAcc, Host, C2SState,
end;
c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
-init_db(mnesia, _Host) ->
- case catch mnesia:table_info(caps_features, storage_type) of
- {'EXIT', _} ->
- ok;
- disc_only_copies ->
- ok;
- _ ->
- mnesia:delete_table(caps_features)
- end,
- mnesia:create_table(caps_features,
- [{disc_only_copies, [node()]},
- {local_content, true},
- {attributes,
- record_info(fields, caps_features)}]),
- update_table(),
- mnesia:add_table_copy(caps_features, node(),
- disc_only_copies);
-init_db(_, _) ->
- ok.
-
init([Host, Opts]) ->
- init_db(gen_mod:db_type(Host, Opts), Host),
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
MaxSize = gen_mod:get_opt(cache_size, Opts,
fun(I) when is_integer(I), I>0 -> I end,
1000),
@@ -434,7 +421,7 @@ feature_response(#iq{type = result,
Features = lists:flatmap(fun (#xmlel{name =
<<"feature">>,
attrs = FAttrs}) ->
- [xml:get_attr_s(<<"var">>, FAttrs)];
+ [fxml:get_attr_s(<<"var">>, FAttrs)];
(_) -> []
end,
Els),
@@ -450,65 +437,13 @@ feature_response(_IQResult, Host, From, Caps,
caps_read_fun(Host, Node) ->
LServer = jid:nameprep(Host),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- caps_read_fun(LServer, Node, DBType).
-
-caps_read_fun(_LServer, Node, mnesia) ->
- fun () ->
- case mnesia:dirty_read({caps_features, Node}) of
- [#caps_features{features = Features}] -> {ok, Features};
- _ -> error
- end
- end;
-caps_read_fun(_LServer, Node, riak) ->
- fun() ->
- case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of
- {ok, #caps_features{features = Features}} -> {ok, Features};
- _ -> error
- end
- end;
-caps_read_fun(LServer, {Node, SubNode}, odbc) ->
- fun() ->
- SNode = ejabberd_odbc:escape(Node),
- SSubNode = ejabberd_odbc:escape(SubNode),
- case ejabberd_odbc:sql_query(
- LServer, [<<"select feature from caps_features where ">>,
- <<"node='">>, SNode, <<"' and subnode='">>,
- SSubNode, <<"';">>]) of
- {selected, [<<"feature">>], [[H]|_] = Fs} ->
- case catch jlib:binary_to_integer(H) of
- Int when is_integer(Int), Int>=0 ->
- {ok, Int};
- _ ->
- {ok, lists:flatten(Fs)}
- end;
- _ ->
- error
- end
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ fun() -> Mod:caps_read(LServer, Node) end.
caps_write_fun(Host, Node, Features) ->
LServer = jid:nameprep(Host),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- caps_write_fun(LServer, Node, Features, DBType).
-
-caps_write_fun(_LServer, Node, Features, mnesia) ->
- fun () ->
- mnesia:dirty_write(#caps_features{node_pair = Node,
- features = Features})
- end;
-caps_write_fun(_LServer, Node, Features, riak) ->
- fun () ->
- ejabberd_riak:put(#caps_features{node_pair = Node,
- features = Features},
- caps_features_schema())
- end;
-caps_write_fun(LServer, NodePair, Features, odbc) ->
- fun () ->
- ejabberd_odbc:sql_transaction(
- LServer,
- sql_write_features_t(NodePair, Features))
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ fun() -> Mod:caps_write(LServer, Node, Features) end.
make_my_disco_hash(Host) ->
JID = jid:make(<<"">>, Host, <<"">>),
@@ -567,7 +502,7 @@ concat_features(Els) ->
lists:usort(lists:flatmap(fun (#xmlel{name =
<<"feature">>,
attrs = Attrs}) ->
- [[xml:get_attr_s(<<"var">>, Attrs), $<]];
+ [[fxml:get_attr_s(<<"var">>, Attrs), $<]];
(_) -> []
end,
Els)).
@@ -576,11 +511,11 @@ concat_identities(Els) ->
lists:sort(lists:flatmap(fun (#xmlel{name =
<<"identity">>,
attrs = Attrs}) ->
- [[xml:get_attr_s(<<"category">>, Attrs),
- $/, xml:get_attr_s(<<"type">>, Attrs),
+ [[fxml:get_attr_s(<<"category">>, Attrs),
+ $/, fxml:get_attr_s(<<"type">>, Attrs),
$/,
- xml:get_attr_s(<<"xml:lang">>, Attrs),
- $/, xml:get_attr_s(<<"name">>, Attrs),
+ fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ $/, fxml:get_attr_s(<<"name">>, Attrs),
$<]];
(_) -> []
end,
@@ -589,8 +524,8 @@ concat_identities(Els) ->
concat_info(Els) ->
lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>,
attrs = Attrs, children = Fields}) ->
- case {xml:get_attr_s(<<"xmlns">>, Attrs),
- xml:get_attr_s(<<"type">>, Attrs)}
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs),
+ fxml:get_attr_s(<<"type">>, Attrs)}
of
{?NS_XDATA, <<"result">>} ->
[concat_xdata_fields(Fields)];
@@ -606,10 +541,10 @@ concat_xdata_fields(Fields) ->
attrs = Attrs, children = Els} =
El,
[FormType, VarFields] = Acc) ->
- case xml:get_attr_s(<<"var">>, Attrs) of
+ case fxml:get_attr_s(<<"var">>, Attrs) of
<<"">> -> Acc;
<<"FORM_TYPE">> ->
- [xml:get_subtag_cdata(El,
+ [fxml:get_subtag_cdata(El,
<<"value">>),
VarFields];
Var ->
@@ -622,7 +557,7 @@ concat_xdata_fields(Fields) ->
children
=
VEls}) ->
- [[xml:get_cdata(VEls),
+ [[fxml:get_cdata(VEls),
$<]];
(_) ->
[]
@@ -658,64 +593,23 @@ is_valid_node(Node) ->
false
end.
-update_table() ->
- Fields = record_info(fields, caps_features),
- case mnesia:table_info(caps_features, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- caps_features, Fields, set,
- fun(#caps_features{node_pair = {N, _}}) -> N end,
- fun(#caps_features{node_pair = {N, P},
- features = Fs} = R) ->
- NewFs = if is_integer(Fs) ->
- Fs;
- true ->
- [iolist_to_binary(F) || F <- Fs]
- end,
- R#caps_features{node_pair = {iolist_to_binary(N),
- iolist_to_binary(P)},
- features = NewFs}
- end);
- _ ->
- ?INFO_MSG("Recreating caps_features table", []),
- mnesia:transform_table(caps_features, ignore, Fields)
- end.
-
-sql_write_features_t({Node, SubNode}, Features) ->
- SNode = ejabberd_odbc:escape(Node),
- SSubNode = ejabberd_odbc:escape(SubNode),
- NewFeatures = if is_integer(Features) ->
- [jlib:integer_to_binary(Features)];
- true ->
- Features
- end,
- [[<<"delete from caps_features where node='">>,
- SNode, <<"' and subnode='">>, SSubNode, <<"';">>]|
- [[<<"insert into caps_features(node, subnode, feature) ">>,
- <<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>,
- ejabberd_odbc:escape(F), <<"');">>] || F <- NewFeatures]].
-
caps_features_schema() ->
{record_info(fields, caps_features), #caps_features{}}.
-export(_Server) ->
- [{caps_features,
- fun(_Host, #caps_features{node_pair = NodePair,
- features = Features}) ->
- sql_write_features_t(NodePair, Features);
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import_info() ->
[{<<"caps_features">>, 4}].
import_start(LServer, DBType) ->
ets:new(caps_features_tmp, [private, named_table, bag]),
- init_db(DBType, LServer),
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []),
ok.
-import(_LServer, {odbc, _}, _DBType, <<"caps_features">>,
+import(_LServer, {sql, _}, _DBType, <<"caps_features">>,
[Node, SubNode, Feature, _TimeStamp]) ->
Feature1 = case catch jlib:binary_to_integer(Feature) of
I when is_integer(I), I>0 -> I;
@@ -748,7 +642,7 @@ import_next(LServer, DBType, NodePair) ->
ejabberd_riak:put(
#caps_features{node_pair = NodePair, features = Features},
caps_features_schema());
- _ when DBType == odbc ->
+ _ when DBType == sql ->
ok
end,
import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)).
diff --git a/src/mod_caps_mnesia.erl b/src/mod_caps_mnesia.erl
new file mode 100644
index 000000000..0bf04b2c3
--- /dev/null
+++ b/src/mod_caps_mnesia.erl
@@ -0,0 +1,73 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_caps_mnesia).
+-behaviour(mod_caps).
+
+%% API
+-export([init/2, caps_read/2, caps_write/3]).
+
+-include("mod_caps.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ case catch mnesia:table_info(caps_features, storage_type) of
+ {'EXIT', _} ->
+ ok;
+ disc_only_copies ->
+ ok;
+ _ ->
+ mnesia:delete_table(caps_features)
+ end,
+ mnesia:create_table(caps_features,
+ [{disc_only_copies, [node()]},
+ {local_content, true},
+ {attributes,
+ record_info(fields, caps_features)}]),
+ update_table(),
+ mnesia:add_table_copy(caps_features, node(),
+ disc_only_copies).
+
+caps_read(_LServer, Node) ->
+ case mnesia:dirty_read({caps_features, Node}) of
+ [#caps_features{features = Features}] -> {ok, Features};
+ _ -> error
+ end.
+
+caps_write(_LServer, Node, Features) ->
+ mnesia:dirty_write(#caps_features{node_pair = Node,
+ features = Features}).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, caps_features),
+ case mnesia:table_info(caps_features, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ caps_features, Fields, set,
+ fun(#caps_features{node_pair = {N, _}}) -> N end,
+ fun(#caps_features{node_pair = {N, P},
+ features = Fs} = R) ->
+ NewFs = if is_integer(Fs) ->
+ Fs;
+ true ->
+ [iolist_to_binary(F) || F <- Fs]
+ end,
+ R#caps_features{node_pair = {iolist_to_binary(N),
+ iolist_to_binary(P)},
+ features = NewFs}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating caps_features table", []),
+ mnesia:transform_table(caps_features, ignore, Fields)
+ end.
diff --git a/src/mod_caps_riak.erl b/src/mod_caps_riak.erl
new file mode 100644
index 000000000..6e59ba867
--- /dev/null
+++ b/src/mod_caps_riak.erl
@@ -0,0 +1,38 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_caps_riak).
+-behaviour(mod_caps).
+
+%% API
+-export([init/2, caps_read/2, caps_write/3]).
+
+-include("mod_caps.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+caps_read(_LServer, Node) ->
+ case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of
+ {ok, #caps_features{features = Features}} -> {ok, Features};
+ _ -> error
+ end.
+
+caps_write(_LServer, Node, Features) ->
+ ejabberd_riak:put(#caps_features{node_pair = Node,
+ features = Features},
+ caps_features_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+caps_features_schema() ->
+ {record_info(fields, caps_features), #caps_features{}}.
diff --git a/src/mod_caps_sql.erl b/src/mod_caps_sql.erl
new file mode 100644
index 000000000..9f587db35
--- /dev/null
+++ b/src/mod_caps_sql.erl
@@ -0,0 +1,71 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_caps_sql).
+-behaviour(mod_caps).
+
+%% API
+-export([init/2, caps_read/2, caps_write/3, export/1]).
+
+-include("mod_caps.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+caps_read(LServer, {Node, SubNode}) ->
+ SNode = ejabberd_sql:escape(Node),
+ SSubNode = ejabberd_sql:escape(SubNode),
+ case ejabberd_sql:sql_query(
+ LServer, [<<"select feature from caps_features where ">>,
+ <<"node='">>, SNode, <<"' and subnode='">>,
+ SSubNode, <<"';">>]) of
+ {selected, [<<"feature">>], [[H]|_] = Fs} ->
+ case catch jlib:binary_to_integer(H) of
+ Int when is_integer(Int), Int>=0 ->
+ {ok, Int};
+ _ ->
+ {ok, lists:flatten(Fs)}
+ end;
+ _ ->
+ error
+ end.
+
+caps_write(LServer, NodePair, Features) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ sql_write_features_t(NodePair, Features)).
+
+export(_Server) ->
+ [{caps_features,
+ fun(_Host, #caps_features{node_pair = NodePair,
+ features = Features}) ->
+ sql_write_features_t(NodePair, Features);
+ (_Host, _R) ->
+ []
+ end}].
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+sql_write_features_t({Node, SubNode}, Features) ->
+ SNode = ejabberd_sql:escape(Node),
+ SSubNode = ejabberd_sql:escape(SubNode),
+ NewFeatures = if is_integer(Features) ->
+ [jlib:integer_to_binary(Features)];
+ true ->
+ Features
+ end,
+ [[<<"delete from caps_features where node='">>,
+ SNode, <<"' and subnode='">>, SSubNode, <<"';">>]|
+ [[<<"insert into caps_features(node, subnode, feature) ">>,
+ <<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>,
+ ejabberd_sql:escape(F), <<"');">>] || F <- NewFeatures]].
+
diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl
index a4ca45ddf..ebf1d0b0f 100644
--- a/src/mod_carboncopy.erl
+++ b/src/mod_carboncopy.erl
@@ -43,21 +43,20 @@
-include("logger.hrl").
-include("jlib.hrl").
-define(PROCNAME, ?MODULE).
--define(TABLE, carboncopy).
--type matchspec_atom() :: '_' | '$1' | '$2' | '$3'.
--record(carboncopy,{us :: {binary(), binary()} | matchspec_atom(),
- resource :: binary() | matchspec_atom(),
- version :: binary() | matchspec_atom()}).
+-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 xml:get_subtag(Packet, Direction) of
+ case fxml:get_subtag(Packet, Direction) of
#xmlel{name = Direction, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_CARBONS_2 -> true;
?NS_CARBONS_1 -> true;
_ -> false
@@ -69,17 +68,8 @@ 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),
- Fields = record_info(fields, ?TABLE),
- try mnesia:table_info(?TABLE, attributes) of
- Fields -> ok;
- _ -> mnesia:delete_table(?TABLE) %% recreate..
- catch _:_Error -> ok %%probably table don't exist
- end,
- mnesia:create_table(?TABLE,
- [{ram_copies, [node()]},
- {attributes, record_info(fields, ?TABLE)},
- {type, bag}]),
- mnesia:add_table_copy(?TABLE, node(), ram_copies),
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(unset_presence_hook,Host, ?MODULE, remove_connection, 10),
%% 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),
@@ -102,7 +92,9 @@ iq_handler2(From, To, IQ) ->
iq_handler1(From, To, IQ) ->
iq_handler(From, To, IQ, ?NS_CARBONS_1).
-iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children = []}} = IQ, CC)->
+iq_handler(From, _To,
+ #iq{type=set, lang = Lang,
+ sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)->
?DEBUG("carbons IQ received: ~p", [IQ]),
{U, S, R} = jid:tolower(From),
Result = case Operation of
@@ -118,12 +110,14 @@ iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children
?DEBUG("carbons IQ result: ok", []),
IQ#iq{type=result, sub_el=[]};
{error,_Error} ->
- ?WARNING_MSG("Error enabling / disabling carbons: ~p", [Result]),
- IQ#iq{type=error,sub_el = [?ERR_BAD_REQUEST]}
+ ?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]),
+ Txt = <<"Database failure">>,
+ IQ#iq{type=error,sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
end;
-iq_handler(_From, _To, IQ, _CC)->
- IQ#iq{type=error, sub_el = [?ERR_NOT_ALLOWED]}.
+iq_handler(_From, _To, #iq{lang = Lang, sub_el = SubEl} = IQ, _CC)->
+ Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
+ IQ#iq{type=error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
user_send_packet(Packet, _C2SState, From, To) ->
check_and_forward(From, To, Packet, sent).
@@ -137,8 +131,8 @@ user_receive_packet(Packet, _C2SState, JID, _From, To) ->
% - we also replicate "read" notifications
check_and_forward(JID, To, Packet, Direction)->
case is_chat_message(Packet) andalso
- xml:get_subtag(Packet, <<"private">>) == false andalso
- xml:get_subtag(Packet, <<"no-copy">>) == false of
+ fxml:get_subtag(Packet, <<"private">>) == false andalso
+ fxml:get_subtag(Packet, <<"no-copy">>) == false of
true ->
case is_carbon_copy(Packet) of
false ->
@@ -240,18 +234,13 @@ build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) ->
enable(Host, U, R, CC)->
?DEBUG("enabling for ~p", [U]),
- try mnesia:dirty_write(#carboncopy{us = {U, Host}, resource=R, version = CC}) of
- ok -> ok
- catch _:Error -> {error, Error}
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:enable(U, Host, R, CC).
disable(Host, U, R)->
?DEBUG("disabling for ~p", [U]),
- ToDelete = mnesia:dirty_match_object(?TABLE, #carboncopy{us = {U, Host}, resource = R, version = '_'}),
- try lists:foreach(fun mnesia:dirty_delete_object/1, ToDelete) of
- ok -> ok
- catch _:Error -> {error, Error}
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:disable(U, Host, R).
complete_packet(From, #xmlel{name = <<"message">>, attrs = OrigAttrs} = Packet, sent) ->
%% if this is a packet sent by user on this host, then Packet doesn't
@@ -268,7 +257,7 @@ complete_packet(_From, #xmlel{name = <<"message">>, attrs=OrigAttrs} = Packet, r
Packet#xmlel{attrs = Attrs}.
message_type(#xmlel{attrs = Attrs}) ->
- case xml:get_attr(<<"type">>, Attrs) of
+ case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} -> Type;
false -> <<"normal">>
end.
@@ -282,12 +271,16 @@ is_chat_message(#xmlel{name = <<"message">>} = Packet) ->
is_chat_message(_Packet) -> false.
has_non_empty_body(Packet) ->
- xml:get_subtag_cdata(Packet, <<"body">>) =/= <<"">>.
+ fxml:get_subtag_cdata(Packet, <<"body">>) =/= <<"">>.
%% list {resource, cc_version} with carbons enabled for given user and host
list(User, Server) ->
- mnesia:dirty_select(?TABLE, [{#carboncopy{us = {User, Server}, resource = '$2', version = '$3'}, [], [{{'$2','$3'}}]}]).
-
+ Mod = gen_mod:db_mod(Server, ?MODULE),
+ Mod:list(User, Server).
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
-mod_opt_type(_) -> [iqdisc].
+mod_opt_type(db_type) ->
+ fun(internal) -> mnesia;
+ (mnesia) -> mnesia
+ end;
+mod_opt_type(_) -> [db_type, iqdisc].
diff --git a/src/mod_carboncopy_mnesia.erl b/src/mod_carboncopy_mnesia.erl
new file mode 100644
index 000000000..bf69bd21c
--- /dev/null
+++ b/src/mod_carboncopy_mnesia.erl
@@ -0,0 +1,68 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_carboncopy_mnesia).
+
+-behaviour(mod_carboncopy).
+
+%% API
+-export([init/2, enable/4, disable/3, list/2]).
+
+-include("mod_carboncopy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ Fields = record_info(fields, carboncopy),
+ try mnesia:table_info(carboncopy, attributes) of
+ Fields ->
+ ok;
+ _ ->
+ %% recreate..
+ mnesia:delete_table(carboncopy)
+ catch _:_Error ->
+ %% probably table don't exist
+ ok
+ end,
+ mnesia:create_table(carboncopy,
+ [{ram_copies, [node()]},
+ {attributes, record_info(fields, carboncopy)},
+ {type, bag}]),
+ mnesia:add_table_copy(carboncopy, node(), ram_copies).
+
+enable(LUser, LServer, LResource, NS) ->
+ try mnesia:dirty_write(
+ #carboncopy{us = {LUser, LServer},
+ resource = LResource,
+ version = NS}) of
+ ok -> ok
+ catch _:Error ->
+ {error, Error}
+ end.
+
+disable(LUser, LServer, LResource) ->
+ ToDelete = mnesia:dirty_match_object(
+ #carboncopy{us = {LUser, LServer},
+ resource = LResource,
+ version = '_'}),
+ try lists:foreach(fun mnesia:dirty_delete_object/1, ToDelete) of
+ ok -> ok
+ catch _:Error ->
+ {error, Error}
+ end.
+
+list(LUser, LServer) ->
+ mnesia:dirty_select(
+ carboncopy,
+ [{#carboncopy{us = {LUser, LServer}, resource = '$2', version = '$3'},
+ [], [{{'$2','$3'}}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl
index 3c046d776..dfbfc028e 100644
--- a/src/mod_client_state.erl
+++ b/src/mod_client_state.erl
@@ -80,7 +80,7 @@ add_stream_feature(Features, _Host) ->
[Feature | Features].
filter_presence(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) ->
- case xml:get_attr(<<"type">>, Attrs) of
+ case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} when Type /= <<"unavailable">> ->
?DEBUG("Got important presence stanza", []),
{stop, send};
diff --git a/src/mod_configure.erl b/src/mod_configure.erl
index 0c7e75268..a836c33bd 100644
--- a/src/mod_configure.erl
+++ b/src/mod_configure.erl
@@ -198,81 +198,81 @@ get_local_identity(Acc, _From, _To, Node, Lang) ->
%%%-----------------------------------------------------------------------
--define(INFO_RESULT(Allow, Feats),
+-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
- deny -> {error, ?ERR_FORBIDDEN};
+ deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow -> {result, Feats}
end).
get_sm_features(Acc, From,
- #jid{lserver = LServer} = _To, Node, _Lang) ->
+ #jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
Allow = acl:match_rule(LServer, configure, From),
case Node of
- <<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ <<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
_ -> Acc
end
end.
get_local_features(Acc, From,
- #jid{lserver = LServer} = _To, Node, _Lang) ->
+ #jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
LNode = tokenize(Node),
Allow = acl:match_rule(LServer, configure, From),
case LNode of
- [<<"config">>] -> ?INFO_RESULT(Allow, []);
- [<<"user">>] -> ?INFO_RESULT(Allow, []);
- [<<"online users">>] -> ?INFO_RESULT(Allow, []);
- [<<"all users">>] -> ?INFO_RESULT(Allow, []);
+ [<<"config">>] -> ?INFO_RESULT(Allow, [], Lang);
+ [<<"user">>] -> ?INFO_RESULT(Allow, [], Lang);
+ [<<"online users">>] -> ?INFO_RESULT(Allow, [], Lang);
+ [<<"all users">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"all users">>, <<$@, _/binary>>] ->
- ?INFO_RESULT(Allow, []);
- [<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, []);
- [<<"running nodes">>] -> ?INFO_RESULT(Allow, []);
- [<<"stopped nodes">>] -> ?INFO_RESULT(Allow, []);
+ ?INFO_RESULT(Allow, [], Lang);
+ [<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, [], Lang);
+ [<<"running nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
+ [<<"stopped nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode] ->
- ?INFO_RESULT(Allow, [?NS_STATS]);
+ ?INFO_RESULT(Allow, [?NS_STATS], Lang);
[<<"running nodes">>, _ENode, <<"DB">>] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"modules">>] ->
- ?INFO_RESULT(Allow, []);
+ ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"backup">>] ->
- ?INFO_RESULT(Allow, []);
+ ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"import">>] ->
- ?INFO_RESULT(Allow, []);
+ ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"import">>, _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"restart">>] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"config">>, _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"add-user">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"delete-user">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"end-user-session">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-user-password">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"change-user-password">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-user-lastlogin">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"user-stats">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-registered-users-num">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-online-users-num">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
_ -> Acc
end
end.
@@ -318,7 +318,8 @@ get_sm_items(Acc, From,
{result,
Items ++ Nodes ++ get_user_resources(User, Server)};
{allow, <<"config">>} -> {result, []};
- {_, <<"config">>} -> {error, ?ERR_FORBIDDEN};
+ {_, <<"config">>} ->
+ {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
_ -> Acc
end
end.
@@ -350,7 +351,7 @@ adhoc_local_items(Acc, From,
Nodes = recursively_get_local_items(PermLev, LServer,
<<"">>, Server, Lang),
Nodes1 = lists:filter(fun (N) ->
- Nd = xml:get_tag_attr_s(<<"node">>, N),
+ Nd = fxml:get_tag_attr_s(<<"node">>, N),
F = get_local_features([], From, To, Nd,
Lang),
case F of
@@ -379,9 +380,9 @@ recursively_get_local_items(PermLev, LServer, Node,
{error, _Error} -> []
end,
Nodes = lists:flatten(lists:map(fun (N) ->
- S = xml:get_tag_attr_s(<<"jid">>,
+ S = fxml:get_tag_attr_s(<<"jid">>,
N),
- Nd = xml:get_tag_attr_s(<<"node">>,
+ Nd = fxml:get_tag_attr_s(<<"node">>,
N),
if (S /= Server) or
(Nd == <<"">>) ->
@@ -448,63 +449,64 @@ 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">>),
case LNode of
[<<"config">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"user">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"online users">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"all users">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"all users">>, <<$@, _/binary>>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"outgoing s2s">> | _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"stopped nodes">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"DB">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"modules">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"backup">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"import">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"import">>, _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"restart">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"config">>, _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"add-user">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"delete-user">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"end-user-session">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-user-password">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"change-user-password">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-user-lastlogin">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"user-stats">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-registered-users-num">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-online-users-num">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
_ -> Acc
end
end.
@@ -562,33 +564,29 @@ get_local_items({_, Host}, [<<"all users">>], _Server,
get_local_items({_, Host},
[<<"all users">>, <<$@, Diap/binary>>], _Server,
_Lang) ->
- case catch ejabberd_auth:get_vh_registered_users(Host)
- of
- {'EXIT', _Reason} -> ?ERR_INTERNAL_SERVER_ERROR;
- Users ->
- 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}
- end
+ 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}
end;
get_local_items({_, Host}, [<<"outgoing s2s">>],
_Server, Lang) ->
@@ -826,33 +824,33 @@ get_stopped_nodes(_Lang) ->
%%-------------------------------------------------------------------------
-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
- Request),
+ Request, Lang),
case acl:match_rule(LServerOrGlobal, configure, From) of
- deny -> {error, ?ERR_FORBIDDEN};
+ deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow -> adhoc_local_commands(From, To, Request)
end).
adhoc_local_commands(Acc, From,
#jid{lserver = LServer} = To,
- #adhoc_request{node = Node} = Request) ->
+ #adhoc_request{node = Node, lang = Lang} = Request) ->
LNode = tokenize(Node),
case LNode of
[<<"running nodes">>, _ENode, <<"DB">>] ->
- ?COMMANDS_RESULT(global, From, To, Request);
+ ?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
- ?COMMANDS_RESULT(LServer, From, To, Request);
+ ?COMMANDS_RESULT(LServer, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
- ?COMMANDS_RESULT(global, From, To, Request);
+ ?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"import">>, _] ->
- ?COMMANDS_RESULT(global, From, To, Request);
+ ?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"restart">>] ->
- ?COMMANDS_RESULT(global, From, To, Request);
+ ?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
- ?COMMANDS_RESULT(global, From, To, Request);
+ ?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"config">>, _] ->
- ?COMMANDS_RESULT(LServer, From, To, Request);
+ ?COMMANDS_RESULT(LServer, From, To, Request, Lang);
?NS_ADMINL(_) ->
- ?COMMANDS_RESULT(LServer, From, To, Request);
+ ?COMMANDS_RESULT(LServer, From, To, Request, Lang);
_ -> Acc
end.
@@ -882,7 +880,8 @@ adhoc_local_commands(From,
end;
XData /= false, ActionIsExecute ->
case jlib:parse_xdata_submit(XData) of
- invalid -> {error, ?ERR_BAD_REQUEST};
+ invalid ->
+ {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
Fields ->
case catch set_form(From, LServer, LNode, Lang, Fields)
of
@@ -898,7 +897,8 @@ adhoc_local_commands(From,
{error, Error} -> {error, Error}
end
end;
- true -> {error, ?ERR_BAD_REQUEST}
+ true ->
+ {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect action or data form">>)}
end.
-define(TVFIELD(Type, Var, Val),
@@ -980,10 +980,14 @@ adhoc_local_commands(From,
get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>],
Lang) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of
- {badrpc, _Reason} ->
+ {badrpc, Reason} ->
+ ?ERROR_MSG("RPC call mnesia:system_info(tables) on node "
+ "~s failed: ~p", [Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
Tables ->
STables = lists:sort(Tables),
@@ -1023,10 +1027,14 @@ get_form(Host,
[<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
Lang) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case ejabberd_cluster:call(Node, gen_mod, loaded_modules, [Host]) of
- {badrpc, _Reason} ->
+ {badrpc, Reason} ->
+ ?ERROR_MSG("RPC call gen_mod:loaded_modules(~s) on node "
+ "~s failed: ~p", [Host, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
Modules ->
SModules = lists:sort(Modules),
@@ -1562,9 +1570,11 @@ get_form(_Host, _, _Lang) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
set_form(_From, _Host,
- [<<"running nodes">>, ENode, <<"DB">>], _Lang, XData) ->
+ [<<"running nodes">>, ENode, <<"DB">>], Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
lists:foreach(fun ({SVar, SVals}) ->
Table = jlib:binary_to_atom(SVar),
@@ -1596,9 +1606,11 @@ set_form(_From, _Host,
end;
set_form(_From, Host,
[<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
lists:foreach(fun ({Var, Vals}) ->
case Vals of
@@ -1615,12 +1627,16 @@ set_form(_From, Host,
set_form(_From, Host,
[<<"running nodes">>, ENode, <<"modules">>,
<<"start">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"modules">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ 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">>
@@ -1637,98 +1653,143 @@ set_form(_From, Host,
end,
Modules),
{result, []};
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
end
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"backup">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ 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} ->
+ {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};
- {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
_ -> {result, []}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"restore">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ 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} ->
+ {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};
- {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
_ -> {result, []}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"textfile">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ 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} ->
+ {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};
- {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
_ -> {result, []}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"import">>, <<"file">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ 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, []};
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ 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, []};
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(From, Host,
@@ -1739,7 +1800,7 @@ set_form(From, Host,
[<<"running nodes">>, ENode, <<"shutdown">>], _Lang,
XData) ->
stop_node(From, Host, ENode, stop, XData);
-set_form(_From, Host, [<<"config">>, <<"acls">>], _Lang,
+set_form(_From, Host, [<<"config">>, <<"acls">>], Lang,
XData) ->
case lists:keysearch(<<"acls">>, 1, XData) of
{value, {_, Strings}} ->
@@ -1753,14 +1814,16 @@ set_form(_From, Host, [<<"config">>, <<"acls">>], _Lang,
{ok, ACLs} ->
acl:add_list(Host, ACLs, true),
{result, []};
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"No 'acls' found in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
set_form(_From, Host, [<<"config">>, <<"access">>],
- _Lang, XData) ->
+ Lang, XData) ->
SetAccess = fun (Rs) ->
mnesia:transaction(fun () ->
Os = mnesia:select(local_config,
@@ -1803,11 +1866,13 @@ set_form(_From, Host, [<<"config">>, <<"access">>],
{atomic, _} -> {result, []};
_ -> {error, ?ERR_BAD_REQUEST}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"No 'access' found in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang,
XData) ->
@@ -2052,7 +2117,7 @@ adhoc_sm_commands(_Acc, From,
action = Action, xdata = XData} =
Request) ->
case acl:match_rule(LServer, configure, From) of
- deny -> {error, ?ERR_FORBIDDEN};
+ deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
ActionIsExecute = lists:member(Action,
[<<"">>, <<"execute">>,
@@ -2071,11 +2136,15 @@ adhoc_sm_commands(_Acc, From,
end;
XData /= false, ActionIsExecute ->
case jlib:parse_xdata_submit(XData) of
- invalid -> {error, ?ERR_BAD_REQUEST};
+ invalid ->
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
Fields ->
set_sm_form(User, Server, <<"config">>, Request, Fields)
end;
- true -> {error, ?ERR_BAD_REQUEST}
+ true ->
+ Txt = <<"Incorrect action or data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc.
@@ -2135,12 +2204,16 @@ set_sm_form(User, Server, <<"config">>,
{value, {_, [Password]}} ->
ejabberd_auth:set_password(User, Server, Password),
adhoc:produce_response(Response);
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ 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);
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"Incorrect value of 'action' in data form">>,
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)}
end;
set_sm_form(_User, _Server, _Node, _Request, _Fields) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
diff --git a/src/mod_configure2.erl b/src/mod_configure2.erl
index 3e00a9cf7..a8287b4d4 100644
--- a/src/mod_configure2.erl
+++ b/src/mod_configure2.erl
@@ -56,16 +56,18 @@ stop(Host) ->
?NS_ECONFIGURE).
process_local_iq(From, To,
- #iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case acl:match_rule(To#jid.lserver, configure, From) of
deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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, ?ERR_FEATURE_NOT_IMPLEMENTED]};
- %%case xml:get_tag_attr_s("type", SubEl) of
+ 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",
@@ -79,7 +81,7 @@ process_local_iq(From, To,
%% _ ->
%% Node =
%% string:tokens(
- %% xml:get_tag_attr_s("node", SubEl),
+ %% fxml:get_tag_attr_s("node", SubEl),
%% "/"),
%% case set_form(Node, Lang, XData) of
%% {result, Res} ->
@@ -98,7 +100,7 @@ process_local_iq(From, To,
%% sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
%%end;
get ->
- case process_get(SubEl) of
+ 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]}
@@ -106,7 +108,7 @@ process_local_iq(From, To,
end
end.
-process_get(#xmlel{name = <<"info">>}) ->
+process_get(#xmlel{name = <<"info">>}, _Lang) ->
S2SConns = ejabberd_s2s:dirty_get_connections(),
TConns = lists:usort([element(2, C) || C <- S2SConns]),
Attrs = [{<<"registered-users">>,
@@ -130,7 +132,7 @@ process_get(#xmlel{name = <<"info">>}) ->
attrs = [{<<"xmlns">>, ?NS_ECONFIGURE} | Attrs],
children = []}};
process_get(#xmlel{name = <<"welcome-message">>,
- attrs = Attrs}) ->
+ attrs = Attrs}, _Lang) ->
{Subj, Body} = ejabberd_config:get_option(
welcome_message,
fun({Subj, Body}) ->
@@ -146,7 +148,7 @@ process_get(#xmlel{name = <<"welcome-message">>,
#xmlel{name = <<"body">>, attrs = [],
children = [{xmlcdata, Body}]}]}};
process_get(#xmlel{name = <<"registration-watchers">>,
- attrs = Attrs}) ->
+ attrs = Attrs}, _Lang) ->
SubEls = ejabberd_config:get_option(
registration_watchers,
fun(JIDs) when is_list(JIDs) ->
@@ -160,14 +162,14 @@ process_get(#xmlel{name = <<"registration-watchers">>,
{result,
#xmlel{name = <<"registration_watchers">>,
attrs = Attrs, children = SubEls}};
-process_get(#xmlel{name = <<"acls">>, attrs = Attrs}) ->
+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}) ->
+ attrs = Attrs}, _Lang) ->
Str = iolist_to_binary(io_lib:format("~p.",
[ets:select(local_config,
[{{local_config, {access, '$1'},
@@ -178,13 +180,14 @@ process_get(#xmlel{name = <<"access">>,
{result,
#xmlel{name = <<"access">>, attrs = Attrs,
children = [{xmlcdata, Str}]}};
-process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
+process_get(#xmlel{name = <<"last">>, attrs = Attrs}, Lang) ->
case catch mnesia:dirty_select(last_activity,
[{{last_activity, '_', '$1', '_'}, [],
['$1']}])
of
{'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ Txt = <<"Database failure">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)};
Vals ->
TimeStamp = p1_time_compat:system_time(seconds),
Str = list_to_binary(
@@ -196,7 +199,7 @@ process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
end;
%%process_get({xmlelement, Name, Attrs, SubEls}) ->
%% {result, };
-process_get(_) -> {error, ?ERR_BAD_REQUEST}.
+process_get(_, _) -> {error, ?ERR_BAD_REQUEST}.
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [iqdisc].
diff --git a/src/mod_disco.erl b/src/mod_disco.erl
index 6e0b9c929..0d5abcb4b 100644
--- a/src/mod_disco.erl
+++ b/src/mod_disco.erl
@@ -150,9 +150,10 @@ process_local_iq_items(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Host = To#jid.lserver,
case ejabberd_hooks:run_fold(disco_local_items, Host,
empty, [From, To, Node, Lang])
@@ -177,10 +178,11 @@ process_local_iq_info(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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 = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Identity = ejabberd_hooks:run_fold(disco_local_identity,
Host, [], [From, To, Node, Lang]),
Info = ejabberd_hooks:run_fold(disco_info, Host, [],
@@ -229,10 +231,12 @@ get_local_features(Acc, _From, To, <<>>, _Lang) ->
ets:select(disco_features,
[{{{'_', Host}}, [], ['$_']}])
++ Feats};
-get_local_features(Acc, _From, _To, _Node, _Lang) ->
+get_local_features(Acc, _From, _To, _Node, Lang) ->
case Acc of
{result, _Features} -> Acc;
- empty -> {error, ?ERR_ITEM_NOT_FOUND}
+ empty ->
+ Txt = <<"No features available">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}
end.
features_to_xml(FeatureList) ->
@@ -271,8 +275,8 @@ get_local_services(Acc, _From, To, <<>>, _Lang) ->
get_local_services({result, _} = Acc, _From, _To, _Node,
_Lang) ->
Acc;
-get_local_services(empty, _From, _To, _Node, _Lang) ->
- {error, ?ERR_ITEM_NOT_FOUND}.
+get_local_services(empty, _From, _To, _Node, Lang) ->
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, <<"No services available">>)}.
get_vh_services(Host) ->
Hosts = lists:sort(fun (H1, H2) ->
@@ -300,12 +304,13 @@ process_sm_iq_items(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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 = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
case ejabberd_hooks:run_fold(disco_sm_items, Host,
empty, [From, To, Node, Lang])
of
@@ -325,8 +330,9 @@ process_sm_iq_items(From, To,
IQ#iq{type = error, sub_el = [SubEl, Error]}
end;
false ->
+ Txt = <<"Not subscribed">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
+ sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
end
end.
@@ -347,12 +353,14 @@ get_sm_items(Acc, From,
get_sm_items({result, _} = Acc, _From, _To, _Node,
_Lang) ->
Acc;
-get_sm_items(empty, From, To, _Node, _Lang) ->
+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};
- _ -> {error, ?ERR_NOT_ALLOWED}
+ _ ->
+ Txt = <<"Query to another users is forbidden">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end.
is_presence_subscribed(#jid{luser = User, lserver = Server},
@@ -373,15 +381,18 @@ process_sm_iq_info(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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 = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Identity = ejabberd_hooks:run_fold(disco_sm_identity,
Host, [],
[From, To, Node, Lang]),
+ Info = ejabberd_hooks:run_fold(disco_info, Host, [],
+ [From, To, Node, Lang]),
case ejabberd_hooks:run_fold(disco_sm_features, Host,
empty, [From, To, Node, Lang])
of
@@ -397,14 +408,15 @@ process_sm_iq_info(From, To,
[{<<"xmlns">>, ?NS_DISCO_INFO}
| ANode],
children =
- Identity ++
+ Identity ++ Info ++
features_to_xml(Features)}]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
end;
false ->
+ Txt = <<"Not subscribed">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
+ sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
end
end.
@@ -421,12 +433,14 @@ get_sm_identity(Acc, _From,
_ -> []
end.
-get_sm_features(empty, From, To, _Node, _Lang) ->
+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};
- _ -> {error, ?ERR_NOT_ALLOWED}
+ _ ->
+ Txt = <<"Query to another users is forbidden">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end;
get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
diff --git a/src/mod_echo.erl b/src/mod_echo.erl
index 3f91f1d03..7d9f81f82 100644
--- a/src/mod_echo.erl
+++ b/src/mod_echo.erl
@@ -86,7 +86,7 @@ stop(Host) ->
init([Host, Opts]) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"echo.@HOST@">>),
- ejabberd_router:register_route(MyHost),
+ ejabberd_router:register_route(MyHost, Host),
{ok, #state{host = MyHost}}.
%%--------------------------------------------------------------------
@@ -118,7 +118,10 @@ handle_cast(_Msg, State) -> {noreply, State}.
handle_info({route, From, To, Packet}, State) ->
Packet2 = case From#jid.user of
<<"">> ->
- jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"User part of JID in 'from' is empty">>,
+ jlib:make_error_reply(
+ Packet, ?ERRT_BAD_REQUEST(Lang, Txt));
_ -> Packet
end,
do_client_version(disabled, To, From),
@@ -182,7 +185,7 @@ do_client_version(enabled, From, To) ->
Els = receive
{route, To, From2, IQ} ->
#xmlel{name = <<"query">>, children = List} =
- xml:get_subtag(IQ, <<"query">>),
+ fxml:get_subtag(IQ, <<"query">>),
List
after 5000 -> % Timeout in miliseconds: 5 seconds
[]
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index 7f86d5cc1..c4fae2022 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -28,9 +28,14 @@
%% in ejabberd_http listener
%% request_handlers:
%% "/api": mod_http_api
-%%
+%%
+%% To use a specific API version N, add a vN element in the URL path:
+%% in ejabberd_http listener
+%% request_handlers:
+%% "/api/v2": mod_http_api
+%%
%% Access rights are defined with:
-%% commands_admin_access: configure
+%% commands_admin_access: configure
%% commands:
%% - add_commands: user
%%
@@ -43,6 +48,25 @@
%%
%% Then to perform an action, send a POST request to the following URL:
%% http://localhost:5280/api/<call_name>
+%%
+%% It's also possible to enable unrestricted access to some commands from group
+%% of IP addresses by using option `admin_ip_access` by having fragment like
+%% this in configuration file:
+%% modules:
+%% mod_http_api:
+%% admin_ip_access: admin_ip_access_rule
+%%...
+%% access:
+%% admin_ip_access_rule:
+%% admin_ip_acl:
+%% - command1
+%% - command2
+%% %% use `all` to give access to all commands
+%%...
+%% acl:
+%% admin_ip_acl:
+%% ip:
+%% - "127.0.0.1/8"
-module(mod_http_api).
@@ -57,6 +81,8 @@
-include("logger.hrl").
-include("ejabberd_http.hrl").
+-define(DEFAULT_API_VERSION, 0).
+
-define(CT_PLAIN,
{<<"Content-Type">>, <<"text/plain">>}).
@@ -97,60 +123,94 @@ start(_Host, _Opts) ->
stop(_Host) ->
ok.
-
%% ----------
%% basic auth
%% ----------
-check_permissions(#request{auth = HTTPAuth, headers = Headers}, Command)
- when HTTPAuth /= undefined ->
+check_permissions(Request, Command) ->
case catch binary_to_existing_atom(Command, utf8) of
Call when is_atom(Call) ->
- Admin =
- case lists:keysearch(<<"X-Admin">>, 1, Headers) of
- {value, {_, <<"true">>}} -> true;
- _ -> false
- end,
- Auth =
- case HTTPAuth of
- {SJID, Pass} ->
- case jid:from_string(SJID) of
- #jid{user = User, server = Server} ->
- case ejabberd_auth:check_password(User, Server, Pass) of
- true -> {ok, {User, Server, Pass, Admin}};
- false -> false
- end;
- _ ->
- false
- end;
- {oauth, Token, _} ->
- case ejabberd_oauth:check_token(Command, Token) of
- {ok, User, Server} ->
- {ok, {User, Server, {oauth, Token}, Admin}};
- false ->
- false
+ {ok, CommandPolicy} = ejabberd_commands:get_command_policy(Call),
+ check_permissions2(Request, Call, CommandPolicy);
+ _ ->
+ unauthorized_response()
+ end.
+
+check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
+ when HTTPAuth /= undefined ->
+ Admin =
+ case lists:keysearch(<<"X-Admin">>, 1, Headers) of
+ {value, {_, <<"true">>}} -> true;
+ _ -> false
+ end,
+ Auth =
+ case HTTPAuth of
+ {SJID, Pass} ->
+ case jid:from_string(SJID) of
+ #jid{user = User, server = Server} ->
+ case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
+ true -> {ok, {User, Server, Pass, Admin}};
+ false -> false
end;
_ ->
false
- end,
- case Auth of
- {ok, A} -> {allowed, Call, A};
+ end;
+ {oauth, Token, _} ->
+ case oauth_check_token(Call, Token) of
+ {ok, User, Server} ->
+ {ok, {User, Server, {oauth, Token}, Admin}};
+ false ->
+ false
+ end;
+ _ ->
+ false
+ end,
+ case Auth of
+ {ok, A} -> {allowed, Call, A};
+ _ -> unauthorized_response()
+ end;
+check_permissions2(_Request, Call, open) ->
+ {allowed, Call, noauth};
+check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
+ Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access,
+ mod_opt_type(admin_ip_access),
+ none),
+ Res = acl:match_rule(global, Access, IP),
+ case Res of
+ all ->
+ {allowed, Call, admin};
+ [all] ->
+ {allowed, Call, admin};
+ allow ->
+ {allowed, Call, admin};
+ Commands when is_list(Commands) ->
+ case lists:member(Call, Commands) of
+ true -> {allowed, Call, admin};
_ -> unauthorized_response()
end;
- _ ->
+ E ->
+ ?DEBUG("Unauthorized: ~p", [E]),
unauthorized_response()
end;
-check_permissions(_, _Command) ->
+check_permissions2(_Request, _Call, _Policy) ->
unauthorized_response().
+oauth_check_token(Scope, Token) when is_atom(Scope) ->
+ oauth_check_token(atom_to_binary(Scope, utf8), Token);
+oauth_check_token(Scope, Token) ->
+ ejabberd_oauth:check_token(Scope, Token).
+
%% ------------------
%% command processing
%% ------------------
+%process(Call, Request) ->
+% ?DEBUG("~p~n~p", [Call, Request]), ok;
process(_, #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []),
- badrequest_response();
+ badrequest_response(<<"Missing POST data">>);
process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
+ Version = get_api_version(Req),
try
Args = case jiffy:decode(Data) of
List when is_list(List) -> List;
@@ -160,31 +220,37 @@ process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
log(Call, Args, IP),
case check_permissions(Req, Call) of
{allowed, Cmd, Auth} ->
- {Code, Result} = handle(Cmd, Auth, Args),
+ {Code, Result} = handle(Cmd, Auth, Args, Version),
json_response(Code, jiffy:encode(Result));
+ %% Warning: check_permission direcly formats 401 reply if not authorized
ErrorResponse ->
ErrorResponse
end
- catch _:Error ->
- ?DEBUG("Bad Request: ~p", [Error]),
- badrequest_response()
+ catch _:{error,{_,invalid_json}} = _Err ->
+ ?DEBUG("Bad Request: ~p", [_Err]),
+ badrequest_response(<<"Invalid JSON input">>);
+ _:_Error ->
+ ?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
+ badrequest_response()
end;
process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
+ Version = get_api_version(Req),
try
Args = case Data of
- [{nokey, <<>>}] -> [];
- _ -> Data
- end,
+ [{nokey, <<>>}] -> [];
+ _ -> Data
+ end,
log(Call, Args, IP),
case check_permissions(Req, Call) of
{allowed, Cmd, Auth} ->
- {Code, Result} = handle(Cmd, Auth, Args),
+ {Code, Result} = handle(Cmd, Auth, Args, Version),
json_response(Code, jiffy:encode(Result));
+ %% Warning: check_permission direcly formats 401 reply if not authorized
ErrorResponse ->
ErrorResponse
end
- catch _:Error ->
- ?DEBUG("Bad Request: ~p", [Error]),
+ catch _:_Error ->
+ ?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
badrequest_response()
end;
process([], #request{method = 'OPTIONS', data = <<>>}) ->
@@ -193,13 +259,28 @@ process(_Path, Request) ->
?DEBUG("Bad Request: no handler ~p", [Request]),
badrequest_response().
+% get API version N from last "vN" element in URL path
+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)
+ end;
+get_api_version([_Head | Tail]) ->
+ get_api_version(Tail);
+get_api_version([]) ->
+ ?DEFAULT_API_VERSION.
+
%% ----------------
%% command handlers
%% ----------------
% generic ejabberd command handler
-handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
- case ejabberd_commands:get_command_format(Call, Auth) of
+handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
+ case ejabberd_commands:get_command_format(Call, Auth, Version) of
{ArgsSpec, _} when is_list(ArgsSpec) ->
Args2 = [{jlib:binary_to_atom(Key), Value} || {Key, Value} <- Args],
Spec = lists:foldr(
@@ -214,23 +295,48 @@ handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
({Key, atom}, Acc) ->
[{Key, undefined}|Acc]
end, [], ArgsSpec),
- handle2(Call, Auth, match(Args2, Spec));
+ try
+ handle2(Call, Auth, match(Args2, Spec), Version)
+ catch throw:not_found ->
+ {404, <<"not_found">>};
+ throw:{not_found, Why} when is_atom(Why) ->
+ {404, jlib:atom_to_binary(Why)};
+ throw:{not_found, Msg} ->
+ {404, iolist_to_binary(Msg)};
+ throw:not_allowed ->
+ {401, <<"not_allowed">>};
+ throw:{not_allowed, Why} when is_atom(Why) ->
+ {401, jlib:atom_to_binary(Why)};
+ throw:{not_allowed, Msg} ->
+ {401, iolist_to_binary(Msg)};
+ throw:{error, account_unprivileged} ->
+ {401, iolist_to_binary(<<"Unauthorized: Account Unpriviledged">>)};
+ throw:{invalid_parameter, Msg} ->
+ {400, iolist_to_binary(Msg)};
+ throw:{error, Why} when is_atom(Why) ->
+ {400, jlib:atom_to_binary(Why)};
+ throw:{error, Msg} ->
+ {400, iolist_to_binary(Msg)};
+ throw:Error when is_atom(Error) ->
+ {400, jlib:atom_to_binary(Error)};
+ throw:Msg when is_list(Msg); is_binary(Msg) ->
+ {400, iolist_to_binary(Msg)};
+ _Error ->
+ ?ERROR_MSG("REST API Error: ~p ~p", [_Error, erlang:get_stacktrace()]),
+ {500, <<"internal_error">>}
+ end;
{error, Msg} ->
+ ?ERROR_MSG("REST API Error: ~p", [Msg]),
{400, Msg};
_Error ->
+ ?ERROR_MSG("REST API Error: ~p", [_Error]),
{400, <<"Error">>}
end.
-handle2(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
- {ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth),
+handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
+ {ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version),
ArgsFormatted = format_args(Args, ArgsF),
- case ejabberd_command(Auth, Call, ArgsFormatted, 400) of
- 0 -> {200, <<"OK">>};
- 1 -> {500, <<"500 Internal server error">>};
- 400 -> {400, <<"400 Bad Request">>};
- 404 -> {404, <<"404 Not found">>};
- Res -> format_command_result(Call, Auth, Res)
- end.
+ ejabberd_command(Auth, Call, ArgsFormatted, Version).
get_elem_delete(A, L) ->
case proplists:get_all_values(A, L) of
@@ -294,7 +400,9 @@ format_arg(undefined, binary) -> <<>>;
format_arg(undefined, string) -> <<>>;
format_arg(Arg, Format) ->
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
- error.
+ throw({invalid_parameter,
+ io_lib:format("Arg ~p is not in format ~p",
+ [Arg, Format])}).
process_unicode_codepoints(Str) ->
iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
@@ -308,32 +416,37 @@ process_unicode_codepoints(Str) ->
match(Args, Spec) ->
[{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
-ejabberd_command(Auth, Cmd, Args, Default) ->
- case catch ejabberd_commands:execute_command(undefined, Auth, Cmd, Args) of
- {'EXIT', _} -> Default;
- {error, _} -> Default;
- Result -> Result
+ejabberd_command(Auth, Cmd, Args, Version) ->
+ Access = case Auth of
+ admin -> [];
+ _ -> undefined
+ end,
+ case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version) of
+ {error, Error} ->
+ throw(Error);
+ Res ->
+ format_command_result(Cmd, Auth, Res, Version)
end.
-format_command_result(Cmd, Auth, Result) ->
- {_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth),
+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, <<"">>};
- {{_, rescode}, _} ->
- {500, <<"">>};
- {{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
- {200, iolist_to_binary(Text1)};
- {{_, restuple}, {_, Text2}} ->
- {500, iolist_to_binary(Text2)};
- {{_, {list, _}}, _V} ->
- {_, L} = format_result(Result, ResultFormat),
- {200, L};
- {{_, {tuple, _}}, _V} ->
- {_, T} = format_result(Result, ResultFormat),
- {200, T};
- _ ->
- {200, {[format_result(Result, ResultFormat)]}}
+ {{_, rescode}, V} when V == true; V == ok ->
+ {200, 0};
+ {{_, rescode}, _} ->
+ {200, 1};
+ {{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
+ {200, iolist_to_binary(Text1)};
+ {{_, restuple}, {_, Text2}} ->
+ {500, iolist_to_binary(Text2)};
+ {{_, {list, _}}, _V} ->
+ {_, L} = format_result(Result, ResultFormat),
+ {200, L};
+ {{_, {tuple, _}}, _V} ->
+ {_, T} = format_result(Result, ResultFormat),
+ {200, T};
+ _ ->
+ {200, {[format_result(Result, ResultFormat)]}}
end.
format_result(Atom, {Name, atom}) ->
@@ -372,21 +485,24 @@ format_result(404, {_Name, _}) ->
"not_found".
unauthorized_response() ->
- {401, ?HEADER(?CT_XML),
- #xmlel{name = <<"h1">>, attrs = [],
- children = [{xmlcdata, <<"401 Unauthorized">>}]}}.
+ unauthorized_response(<<"401 Unauthorized">>).
+unauthorized_response(Body) ->
+ json_response(401, jiffy:encode(Body)).
badrequest_response() ->
- {400, ?HEADER(?CT_XML),
- #xmlel{name = <<"h1">>, attrs = [],
- children = [{xmlcdata, <<"400 Bad Request">>}]}}.
+ badrequest_response(<<"400 Bad Request">>).
+badrequest_response(Body) ->
+ json_response(400, jiffy:encode(Body)).
+
json_response(Code, Body) when is_integer(Code) ->
{Code, ?HEADER(?CT_JSON), Body}.
log(Call, Args, {Addr, Port}) ->
AddrS = jlib:ip_to_list({Addr, Port}),
- ?INFO_MSG("Admin call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]).
+ ?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
+log(Call, Args, IP) ->
+ ?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
-mod_opt_type(access) ->
+mod_opt_type(admin_ip_access) ->
fun(Access) when is_atom(Access) -> Access end;
-mod_opt_type(_) -> [access].
+mod_opt_type(_) -> [admin_ip_access].
diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl
index 36d3626be..b01ca9d2d 100644
--- a/src/mod_http_upload.erl
+++ b/src/mod_http_upload.erl
@@ -86,7 +86,8 @@
%% Utility functions.
-export([get_proc_name/2,
- expand_home/1]).
+ expand_home/1,
+ expand_host/2]).
-include("ejabberd.hrl").
-include("ejabberd_http.hrl").
@@ -273,6 +274,8 @@ init({ServerHost, Opts}) ->
Thumbnail = gen_mod:get_opt(thumbnail, Opts,
fun(B) when is_boolean(B) -> B end,
true),
+ DocRoot1 = expand_home(str:strip(DocRoot, right, $/)),
+ DocRoot2 = expand_host(DocRoot1, ServerHost),
case ServiceURL of
undefined ->
ok;
@@ -289,7 +292,7 @@ init({ServerHost, Opts}) ->
undefined ->
ok;
Mode ->
- file:change_mode(DocRoot, Mode)
+ file:change_mode(DocRoot2, Mode)
end,
case Thumbnail of
true ->
@@ -303,13 +306,13 @@ init({ServerHost, Opts}) ->
false ->
ok
end,
- ejabberd_router:register_route(Host),
+ ejabberd_router:register_route(Host, ServerHost),
{ok, #state{server_host = ServerHost, host = Host, name = Name,
access = Access, max_size = MaxSize,
secret_length = SecretLength, jid_in_url = JIDinURL,
file_mode = FileMode, dir_mode = DirMode,
thumbnail = Thumbnail,
- docroot = expand_home(str:strip(DocRoot, right, $/)),
+ docroot = DocRoot2,
put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
get_url = expand_host(str:strip(GetURL, right, $/), ServerHost),
service_url = ServiceURL}}.
@@ -372,7 +375,7 @@ handle_info(Info, State) ->
?ERROR_MSG("Got unexpected info: ~p", [Info]),
{noreply, State}.
--spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
+-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
terminate(Reason, #state{server_host = ServerHost, host = Host}) ->
?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
@@ -509,6 +512,12 @@ expand_home(Subject) ->
Parts = binary:split(Subject, <<"@HOME@">>, [global]),
str:join(Parts, list_to_binary(Home)).
+-spec expand_host(binary(), binary()) -> binary().
+
+expand_host(Subject, Host) ->
+ Parts = binary:split(Subject, <<"@HOST@">>, [global]),
+ str:join(Parts, Host).
+
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
@@ -526,7 +535,8 @@ process_iq(_From,
IQ#iq{type = result,
sub_el = [#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}],
- children = iq_disco_info(Lang, Name) ++ AddInfo}]};
+ 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)
@@ -559,7 +569,8 @@ process_iq(From,
deny ->
?DEBUG("Denying HTTP upload slot request from ~s",
[jid:to_string(From)]),
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ Txt = <<"Denied by ACL">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
end;
process_iq(_From, #iq{sub_el = SubEl} = IQ, _State) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
@@ -572,12 +583,12 @@ process_iq(_From, invalid, _State) ->
-> {ok, binary(), pos_integer(), binary()} | {error, xmlel()}.
parse_request(#xmlel{name = <<"request">>, attrs = Attrs} = Request, Lang) ->
- case xml:get_attr(<<"xmlns">>, Attrs) of
+ case fxml:get_attr(<<"xmlns">>, Attrs) of
{value, XMLNS} when XMLNS == ?NS_HTTP_UPLOAD;
XMLNS == ?NS_HTTP_UPLOAD_OLD ->
- case {xml:get_subtag_cdata(Request, <<"filename">>),
- xml:get_subtag_cdata(Request, <<"size">>),
- xml:get_subtag_cdata(Request, <<"content-type">>)} of
+ 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 ->
@@ -591,7 +602,8 @@ parse_request(#xmlel{name = <<"request">>, attrs = Attrs} = Request, Lang) ->
{error, ?ERRT_BAD_REQUEST(Lang, Text)}
end;
_ ->
- {error, ?ERR_BAD_REQUEST}
+ Text = <<"No or invalid XML namespace">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Text)}
end;
parse_request(_El, _Lang) -> {error, ?ERR_BAD_REQUEST}.
@@ -629,7 +641,7 @@ create_slot(#state{service_url = undefined,
end;
create_slot(#state{service_url = ServiceURL},
#jid{luser = U, lserver = S} = JID, File, Size, ContentType,
- _Lang) ->
+ Lang) ->
Options = [{body_format, binary}, {full_result, false}],
HttpOptions = [{timeout, ?SERVICE_REQUEST_TIMEOUT}],
SizeStr = jlib:integer_to_binary(Size),
@@ -649,7 +661,8 @@ create_slot(#state{service_url = ServiceURL},
Lines ->
?ERROR_MSG("Can't parse data received for ~s from <~s>: ~p",
[jid:to_string(JID), ServiceURL, Lines]),
- {error, ?ERR_SERVICE_UNAVAILABLE}
+ Txt = <<"Failed to parse HTTP response">>,
+ {error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)}
end;
{ok, {402, _Body}} ->
?INFO_MSG("Got status code 402 for ~s from <~s>",
@@ -737,20 +750,41 @@ map_int_to_char(N) when N =< 9 -> N + 48; % Digit.
map_int_to_char(N) when N =< 35 -> N + 55; % Upper-case character.
map_int_to_char(N) when N =< 61 -> N + 61. % Lower-case character.
--spec expand_host(binary(), binary()) -> binary().
-
-expand_host(Subject, Host) ->
- Parts = binary:split(Subject, <<"@HOST@">>, [global]),
- str:join(Parts, Host).
-
-spec yield_content_type(binary()) -> binary().
yield_content_type(<<"">>) -> ?DEFAULT_CONTENT_TYPE;
yield_content_type(Type) -> Type.
--spec iq_disco_info(binary(), binary()) -> [xmlel()].
-
-iq_disco_info(Lang, Name) ->
+-spec iq_disco_info(binary(), binary(), binary()) -> [xmlel()].
+
+iq_disco_info(Host, Lang, Name) ->
+ Form = case gen_mod:get_module_opt(Host, ?MODULE, max_size,
+ fun(I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end,
+ 104857600) of
+ infinity ->
+ [];
+ MaxSize ->
+ MaxSizeStr = jlib:integer_to_binary(MaxSize),
+ Fields = [#xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"hidden">>},
+ {<<"var">>, <<"FORM_TYPE">>}],
+ children = [#xmlel{name = <<"value">>,
+ children =
+ [{xmlcdata,
+ ?NS_HTTP_UPLOAD}]}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"max-file-size">>}],
+ children = [#xmlel{name = <<"value">>,
+ children =
+ [{xmlcdata,
+ MaxSizeStr}]}]}],
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"result">>}],
+ children = Fields}]
+ end,
[#xmlel{name = <<"identity">>,
attrs = [{<<"category">>, <<"store">>},
{<<"type">>, <<"file">>},
@@ -758,7 +792,7 @@ iq_disco_info(Lang, Name) ->
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, ?NS_HTTP_UPLOAD}]},
#xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_HTTP_UPLOAD_OLD}]}].
+ attrs = [{<<"var">>, ?NS_HTTP_UPLOAD_OLD}]} | Form].
%% HTTP request handling.
@@ -796,7 +830,7 @@ store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) ->
{ok,
[{<<"Content-Type">>,
<<"text/xml; charset=utf-8">>}],
- xml:element_to_binary(ThumbEl)};
+ fxml:element_to_binary(ThumbEl)};
pass ->
ok
end;
@@ -974,8 +1008,9 @@ remove_user(User, Server) ->
(node) -> node
end,
sha1),
+ DocRoot1 = expand_host(expand_home(DocRoot), ServerHost),
UserStr = make_user_string(jid:make(User, Server, <<"">>), JIDinURL),
- UserDir = str:join([expand_home(DocRoot), UserStr], <<$/>>),
+ UserDir = str:join([DocRoot1, UserStr], <<$/>>),
case del_tree(UserDir) of
ok ->
?INFO_MSG("Removed HTTP upload directory of ~s@~s", [User, Server]);
diff --git a/src/mod_http_upload_quota.erl b/src/mod_http_upload_quota.erl
index db0b4aa4c..4f9d95217 100644
--- a/src/mod_http_upload_quota.erl
+++ b/src/mod_http_upload_quota.erl
@@ -132,6 +132,7 @@ init({ServerHost, Opts}) ->
fun iolist_to_binary/1,
<<"@HOME@/upload">>),
DocRoot2 = mod_http_upload:expand_home(str:strip(DocRoot1, right, $/)),
+ DocRoot3 = mod_http_upload:expand_host(DocRoot2, ServerHost),
Timers = if MaxDays == infinity -> [];
true ->
{ok, T1} = timer:send_after(?INITIAL_TIMEOUT, sweep),
@@ -144,7 +145,7 @@ init({ServerHost, Opts}) ->
access_soft_quota = AccessSoftQuota,
access_hard_quota = AccessHardQuota,
max_days = MaxDays,
- docroot = DocRoot2,
+ docroot = DocRoot3,
timers = Timers}}.
-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
@@ -238,7 +239,7 @@ handle_info(Info, State) ->
?ERROR_MSG("Got unexpected info: ~p", [Info]),
{noreply, State}.
--spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
+-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
?DEBUG("Stopping upload quota process for ~s: ~p", [ServerHost, Reason]),
diff --git a/src/mod_irc.erl b/src/mod_irc.erl
index 183088956..e0c658dea 100644
--- a/src/mod_irc.erl
+++ b/src/mod_irc.erl
@@ -33,7 +33,8 @@
%% API
-export([start_link/2, start/2, stop/1, export/1, import/1,
- import/3, closed_connection/3, get_connection_params/3]).
+ import/3, closed_connection/3, get_connection_params/3,
+ data_to_binary/2]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
@@ -46,6 +47,8 @@
-include("adhoc.hrl").
+-include("mod_irc.hrl").
+
-define(DEFAULT_IRC_ENCODING, <<"iso8859-15">>).
-define(DEFAULT_IRC_PORT, 6667).
@@ -58,27 +61,19 @@
[<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
<<"utf-8">>, <<"utf-8+latin-1">>]).
--type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
- {binary(), binary(), inet:port_number()} |
- {binary(), binary()} |
- {binary()}.
-
--record(irc_connection,
- {jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
- pid = self() :: pid()}).
-
--record(irc_custom,
- {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
- binary()},
- data = [] :: [{username, binary()} |
- {connections_params, [conn_param()]}]}).
-
-record(state, {host = <<"">> :: binary(),
server_host = <<"">> :: binary(),
access = all :: atom()}).
-define(PROCNAME, ejabberd_mod_irc).
+-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()}.
+
%%====================================================================
%% API
%%====================================================================
@@ -116,24 +111,18 @@ stop(Host) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
- ejabberd:start_app(p1_iconv),
+ ejabberd:start_app(iconv),
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"irc.@HOST@">>),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(irc_custom,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, irc_custom)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
Access = gen_mod:get_opt(access, Opts,
fun(A) when is_atom(A) -> A end,
all),
catch ets:new(irc_connection,
[named_table, public,
{keypos, #irc_connection.jid_server_host}]),
- ejabberd_router:register_route(MyHost),
+ ejabberd_router:register_route(MyHost, Host),
{ok,
#state{host = MyHost, server_host = Host,
access = Access}}.
@@ -216,7 +205,7 @@ do_route(Host, ServerHost, Access, From, To, Packet) ->
allow -> do_route1(Host, ServerHost, From, To, Packet);
_ ->
#xmlel{attrs = Attrs} = Packet,
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Access denied by service policy">>,
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang, ErrText)),
@@ -234,7 +223,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
#iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
sub_el = SubEl, lang = Lang} =
IQ ->
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
[],
[ServerHost, ?MODULE,
@@ -262,7 +251,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
#iq{type = get, xmlns = (?NS_DISCO_ITEMS) = XMLNS,
sub_el = SubEl, lang = Lang} =
IQ ->
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
case Node of
<<>> ->
ResIQ = IQ#iq{type = result,
@@ -304,8 +293,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
Lang)}]},
Res = jlib:iq_to_xml(ResIQ);
_ ->
- Res = jlib:make_error_reply(Packet,
- ?ERR_ITEM_NOT_FOUND)
+ 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 ->
@@ -319,7 +309,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
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,
+ #iq{type = set, xmlns = ?NS_COMMANDS, lang = Lang,
sub_el = SubEl} =
IQ ->
Request = adhoc:parse_request(IQ),
@@ -348,8 +338,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
true -> ok
end;
_ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_ITEM_NOT_FOUND),
+ 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 ->
@@ -407,12 +398,14 @@ do_route1(Host, ServerHost, From, To, 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
[] ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ Txt = <<"IRC connection not found">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err);
[R] ->
Pid = R#irc_connection.pid,
@@ -421,7 +414,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
ok
end;
_ ->
- Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
+ Txt = <<"Failed to parse chanserv">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end
end
@@ -516,7 +511,7 @@ find_xdata_el1([]) -> false;
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
children = SubEls}
| Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
#xmlel{name = Name, attrs = Attrs, children = SubEls};
_ -> find_xdata_el1(Els)
@@ -532,10 +527,11 @@ process_irc_register(ServerHost, Host, From, _To,
XDataEl = find_xdata_el(SubEl),
case XDataEl of
false ->
+ Txt1 = <<"No data form found">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]};
+ sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, Txt1)]};
#xmlel{attrs = Attrs} ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"cancel">> ->
IQ#iq{type = result,
sub_el =
@@ -546,10 +542,11 @@ process_irc_register(ServerHost, Host, From, _To,
XData = jlib:parse_xdata_submit(XDataEl),
case XData of
invalid ->
+ Txt2 = <<"Incorrect data form">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt2)]};
_ ->
- Node = str:tokens(xml:get_tag_attr_s(<<"node">>,
+ Node = str:tokens(fxml:get_tag_attr_s(<<"node">>,
SubEl),
<<"/">>),
case set_form(ServerHost, Host, From, Node, Lang,
@@ -567,11 +564,13 @@ process_irc_register(ServerHost, Host, From, _To,
end
end;
_ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ Txt3 = <<"Incorrect value of 'type' attribute">>,
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt3)]}
end
end;
get ->
- Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl),
<<"/">>),
case get_form(ServerHost, Host, From, Node, Lang) of
{result, Res} ->
@@ -587,49 +586,16 @@ process_irc_register(ServerHost, Host, From, _To,
get_data(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
- get_data(LServer, Host, From,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_data(_LServer, Host, From, mnesia) ->
- #jid{luser = LUser, lserver = LServer} = From,
- US = {LUser, LServer},
- case catch mnesia:dirty_read({irc_custom, {US, Host}})
- of
- {'EXIT', _Reason} -> error;
- [] -> empty;
- [#irc_custom{data = Data}] -> Data
- end;
-get_data(LServer, Host, From, riak) ->
- #jid{luser = LUser, lserver = LServer} = From,
- US = {LUser, LServer},
- case ejabberd_riak:get(irc_custom, irc_custom_schema(), {US, Host}) of
- {ok, #irc_custom{data = Data}} ->
- Data;
- {error, notfound} ->
- empty;
- _Err ->
- error
- end;
-get_data(LServer, Host, From, odbc) ->
- SJID =
- ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
- SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select data from irc_custom where jid='">>,
- SJID, <<"' and host='">>, SHost,
- <<"';">>])
- of
- {selected, [<<"data">>], [[SData]]} ->
- data_to_binary(From, ejabberd_odbc:decode_term(SData));
- {'EXIT', _} -> error;
- {selected, _, _} -> empty
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_data(LServer, Host, From).
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, ?ERR_INTERNAL_SERVER_ERROR};
+ error ->
+ Txt1 = <<"Database failure">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt1)};
empty -> {User, []};
Data -> get_username_and_connection_params(Data)
end,
@@ -731,39 +697,10 @@ get_form(_ServerHost, _Host, _, _, _Lang) ->
set_data(ServerHost, Host, From, Data) ->
LServer = jid:nameprep(ServerHost),
- set_data(LServer, Host, From, data_to_binary(From, Data),
- gen_mod:db_type(LServer, ?MODULE)).
-
-set_data(_LServer, Host, From, Data, mnesia) ->
- {LUser, LServer, _} = jid:tolower(From),
- US = {LUser, LServer},
- F = fun () ->
- mnesia:write(#irc_custom{us_host = {US, Host},
- data = Data})
- end,
- mnesia:transaction(F);
-set_data(LServer, Host, From, Data, riak) ->
- {LUser, LServer, _} = jid:tolower(From),
- US = {LUser, LServer},
- {atomic, ejabberd_riak:put(#irc_custom{us_host = {US, Host},
- data = Data},
- irc_custom_schema())};
-set_data(LServer, Host, From, Data, odbc) ->
- SJID =
- ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
- SHost = ejabberd_odbc:escape(Host),
- SData = ejabberd_odbc:encode_term(Data),
- F = fun () ->
- odbc_queries:update_t(<<"irc_custom">>,
- [<<"jid">>, <<"host">>, <<"data">>],
- [SJID, SHost, SData],
- [<<"jid='">>, SJID, <<"' and host='">>,
- SHost, <<"'">>]),
- ok
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
-
-set_form(ServerHost, Host, From, [], _Lang, XData) ->
+ 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
@@ -781,11 +718,11 @@ set_form(ServerHost, Host, From, [], _Lang, XData) ->
{connections_params, ConnectionsParams}])
of
{atomic, _} -> {result, []};
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Database failure">>)}
end;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Parse error">>)}
end;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Scan error">>)}
end;
_ -> {error, ?ERR_NOT_ACCEPTABLE}
end;
@@ -909,7 +846,9 @@ adhoc_join(From, To,
elements = [Form]});
true ->
case jlib:parse_xdata_submit(XData) of
- invalid -> {error, ?ERR_BAD_REQUEST};
+ invalid ->
+ Txt1 = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt1)};
Fields ->
Channel = case lists:keysearch(<<"channel">>, 1, Fields)
of
@@ -998,7 +937,8 @@ adhoc_register(ServerHost, From, To,
true ->
case jlib:parse_xdata_submit(XData) of
invalid ->
- Error = {error, ?ERR_BAD_REQUEST},
+ Txt1 = <<"Incorrect data form">>,
+ Error = {error, ?ERRT_BAD_REQUEST(Lang, Txt1)},
Username = false,
ConnectionsParams = false;
Fields ->
@@ -1021,7 +961,9 @@ adhoc_register(ServerHost, From, To,
{atomic, _} ->
adhoc:produce_response(Request,
#adhoc_response{status = completed});
- _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ _ ->
+ Txt2 = <<"Database failure">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt2)}
end;
true ->
Form = generate_adhoc_register_form(Lang, Username,
@@ -1297,66 +1239,17 @@ conn_params_to_list(Params) ->
Port, binary_to_list(P)}
end, Params).
-irc_custom_schema() ->
- {record_info(fields, irc_custom), #irc_custom{}}.
-
-update_table() ->
- Fields = record_info(fields, irc_custom),
- case mnesia:table_info(irc_custom, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- irc_custom, Fields, set,
- fun(#irc_custom{us_host = {_, H}}) -> H end,
- fun(#irc_custom{us_host = {{U, S}, H},
- data = Data} = R) ->
- JID = jid:make(U, S, <<"">>),
- R#irc_custom{us_host = {{iolist_to_binary(U),
- iolist_to_binary(S)},
- iolist_to_binary(H)},
- data = data_to_binary(JID, Data)}
- end);
- _ ->
- ?INFO_MSG("Recreating irc_custom table", []),
- mnesia:transform_table(irc_custom, ignore, Fields)
- end.
-
-export(_Server) ->
- [{irc_custom,
- fun(Host, #irc_custom{us_host = {{U, S}, IRCHost},
- data = Data}) ->
- case str:suffix(Host, IRCHost) of
- true ->
- SJID = ejabberd_odbc:escape(
- jid:to_string(
- jid:make(U, S, <<"">>))),
- SIRCHost = ejabberd_odbc:escape(IRCHost),
- SData = ejabberd_odbc:encode_term(Data),
- [[<<"delete from irc_custom where jid='">>, SJID,
- <<"' and host='">>, SIRCHost, <<"';">>],
- [<<"insert into irc_custom(jid, host, "
- "data) values ('">>,
- SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
- <<"');">>]];
- false ->
- []
- end
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
-import(_LServer) ->
- [{<<"select jid, host, data from irc_custom;">>,
- fun([SJID, IRCHost, SData]) ->
- #jid{luser = U, lserver = S} = jid:from_string(SJID),
- Data = ejabberd_odbc:decode_term(SData),
- #irc_custom{us_host = {{U, S}, IRCHost},
- data = Data}
- end}].
+import(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-import(_LServer, mnesia, #irc_custom{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, riak, #irc_custom{} = R) ->
- ejabberd_riak:put(R, irc_custom_schema());
-import(_, _, _) ->
- pass.
+import(LServer, DBType, Data) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, Data).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
@@ -1368,7 +1261,7 @@ mod_opt_type(_) ->
[access, db_type, default_encoding, host].
extract_ident(Packet) ->
- case xml:get_subtag(Packet, <<"headers">>) of
+ case fxml:get_subtag(Packet, <<"headers">>) of
{xmlel, _Name, _Attrs, Headers} ->
extract_header(<<"X-Irc-Ident">>, Headers);
_ ->
@@ -1376,7 +1269,7 @@ extract_ident(Packet) ->
end.
extract_ip_address(Packet) ->
- case xml:get_subtag(Packet, <<"headers">>) of
+ case fxml:get_subtag(Packet, <<"headers">>) of
{xmlel, _Name, _Attrs, Headers} ->
extract_header(<<"X-Forwarded-For">>, Headers);
_ ->
@@ -1384,7 +1277,7 @@ extract_ip_address(Packet) ->
end.
extract_header(HeaderName, [{xmlel, _Name, _Attrs, [{xmlcdata, Value}]} | Tail]) ->
- case xml:get_attr(<<"name">>, _Attrs) of
+ case fxml:get_attr(<<"name">>, _Attrs) of
{value, HeaderName} ->
binary_to_list(Value);
_ ->
diff --git a/src/mod_irc_connection.erl b/src/mod_irc_connection.erl
index ae709dc27..098c8c286 100644
--- a/src/mod_irc_connection.erl
+++ b/src/mod_irc_connection.erl
@@ -233,7 +233,7 @@ get_password_from_presence(#xmlel{name = <<"presence">>,
case lists:filter(fun (El) ->
case El of
#xmlel{name = <<"x">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC -> true;
_ -> false
end;
@@ -243,9 +243,9 @@ get_password_from_presence(#xmlel{name = <<"presence">>,
Els)
of
[ElXMUC | _] ->
- case xml:get_subtag(ElXMUC, <<"password">>) of
+ case fxml:get_subtag(ElXMUC, <<"password">>) of
#xmlel{name = <<"password">>} = PasswordTag ->
- {true, xml:get_tag_cdata(PasswordTag)};
+ {true, fxml:get_tag_cdata(PasswordTag)};
_ -> false
end;
_ -> false
@@ -261,7 +261,7 @@ handle_info({route_chan, Channel, Resource,
#xmlel{name = <<"presence">>, attrs = Attrs} =
Presence},
StateName, StateData) ->
- NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"unavailable">> ->
send_stanza_unavailable(Channel, StateData),
S1 = (?SEND((io_lib:format("PART #~s\r\n",
@@ -312,9 +312,9 @@ handle_info({route_chan, Channel, Resource,
handle_info({route_chan, Channel, Resource,
#xmlel{name = <<"message">>, attrs = Attrs} = El},
StateName, StateData) ->
- NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"groupchat">> ->
- case xml:get_path_s(El, [{elem, <<"subject">>}, cdata])
+ case fxml:get_path_s(El, [{elem, <<"subject">>}, cdata])
of
<<"">> ->
ejabberd_router:route(
@@ -325,7 +325,7 @@ handle_info({route_chan, Channel, Resource,
StateData#state.host,
StateData#state.nick),
StateData#state.user, El),
- Body = xml:get_path_s(El,
+ Body = fxml:get_path_s(El,
[{elem, <<"body">>},
cdata]),
case Body of
@@ -386,7 +386,7 @@ handle_info({route_chan, Channel, Resource,
when Type == <<"chat">>;
Type == <<"">>;
Type == <<"normal">> ->
- Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
+ Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]),
case Body of
<<"/quote ", Rest/binary>> ->
?SEND(<<Rest/binary, "\r\n">>);
@@ -481,9 +481,9 @@ handle_info({route_chan, _Channel, _Resource, _Packet},
handle_info({route_nick, Nick,
#xmlel{name = <<"message">>, attrs = Attrs} = El},
StateName, StateData) ->
- NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> ->
- Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
+ Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]),
case Body of
<<"/quote ", Rest/binary>> ->
?SEND(<<Rest/binary, "\r\n">>);
@@ -778,13 +778,13 @@ bounce_messages(Reason) ->
receive
{send_element, El} ->
#xmlel{attrs = Attrs} = El,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
_ ->
Err = jlib:make_error_reply(El, <<"502">>, Reason),
- From = jid:from_string(xml:get_attr_s(<<"from">>,
+ From = jid:from_string(fxml:get_attr_s(<<"from">>,
Attrs)),
- To = jid:from_string(xml:get_attr_s(<<"to">>,
+ To = jid:from_string(fxml:get_attr_s(<<"to">>,
Attrs)),
ejabberd_router:route(To, From, Err)
end,
@@ -1535,14 +1535,14 @@ iq_admin(StateData, Channel, From, To,
end.
process_iq_admin(StateData, Channel, set, SubEl) ->
- case xml:get_subtag(SubEl, <<"item">>) of
+ case fxml:get_subtag(SubEl, <<"item">>) of
false -> {error, ?ERR_BAD_REQUEST};
ItemEl ->
- Nick = xml:get_tag_attr_s(<<"nick">>, ItemEl),
- Affiliation = xml:get_tag_attr_s(<<"affiliation">>,
+ Nick = fxml:get_tag_attr_s(<<"nick">>, ItemEl),
+ Affiliation = fxml:get_tag_attr_s(<<"affiliation">>,
ItemEl),
- Role = xml:get_tag_attr_s(<<"role">>, ItemEl),
- Reason = xml:get_path_s(ItemEl,
+ Role = fxml:get_tag_attr_s(<<"role">>, ItemEl),
+ Reason = fxml:get_path_s(ItemEl,
[{elem, <<"reason">>}, cdata]),
process_admin(StateData, Channel, Nick, Affiliation,
Role, Reason)
diff --git a/src/mod_irc_mnesia.erl b/src/mod_irc_mnesia.erl
new file mode 100644
index 000000000..9f8117ad3
--- /dev/null
+++ b/src/mod_irc_mnesia.erl
@@ -0,0 +1,69 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_irc_mnesia).
+
+-behaviour(mod_irc).
+
+%% API
+-export([init/2, get_data/3, set_data/4, import/2]).
+
+-include("jlib.hrl").
+-include("mod_irc.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(irc_custom,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, irc_custom)}]),
+ update_table().
+
+get_data(_LServer, Host, From) ->
+ {U, S, _} = jid:tolower(From),
+ case catch mnesia:dirty_read({irc_custom, {{U, S}, Host}}) of
+ {'EXIT', _Reason} -> error;
+ [] -> empty;
+ [#irc_custom{data = Data}] -> Data
+ end.
+
+set_data(_LServer, Host, From, Data) ->
+ {U, S, _} = jid:tolower(From),
+ F = fun () ->
+ mnesia:write(#irc_custom{us_host = {{U, S}, Host},
+ data = Data})
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #irc_custom{} = R) ->
+ mnesia:dirty_write(R).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, irc_custom),
+ case mnesia:table_info(irc_custom, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ irc_custom, Fields, set,
+ fun(#irc_custom{us_host = {_, H}}) -> H end,
+ fun(#irc_custom{us_host = {{U, S}, H},
+ data = Data} = R) ->
+ JID = jid:make(U, S, <<"">>),
+ R#irc_custom{us_host = {{iolist_to_binary(U),
+ iolist_to_binary(S)},
+ iolist_to_binary(H)},
+ data = mod_irc:data_to_binary(JID, Data)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating irc_custom table", []),
+ mnesia:transform_table(irc_custom, ignore, Fields)
+ end.
diff --git a/src/mod_irc_riak.erl b/src/mod_irc_riak.erl
new file mode 100644
index 000000000..6ac7befdf
--- /dev/null
+++ b/src/mod_irc_riak.erl
@@ -0,0 +1,49 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_irc_riak).
+
+-behaviour(mod_irc).
+
+%% API
+-export([init/2, get_data/3, set_data/4, import/2]).
+
+-include("jlib.hrl").
+-include("mod_irc.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_data(_LServer, Host, From) ->
+ {U, S, _} = jid:tolower(From),
+ case ejabberd_riak:get(irc_custom, irc_custom_schema(), {{U, S}, Host}) of
+ {ok, #irc_custom{data = Data}} ->
+ Data;
+ {error, notfound} ->
+ empty;
+ _Err ->
+ error
+ end.
+
+set_data(_LServer, Host, From, Data) ->
+ {U, S, _} = jid:tolower(From),
+ {atomic, ejabberd_riak:put(#irc_custom{us_host = {{U, S}, Host},
+ data = Data},
+ irc_custom_schema())}.
+
+import(_LServer, #irc_custom{} = R) ->
+ ejabberd_riak:put(R, irc_custom_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+irc_custom_schema() ->
+ {record_info(fields, irc_custom), #irc_custom{}}.
diff --git a/src/mod_irc_sql.erl b/src/mod_irc_sql.erl
new file mode 100644
index 000000000..9a97d5728
--- /dev/null
+++ b/src/mod_irc_sql.erl
@@ -0,0 +1,91 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_irc_sql).
+
+-behaviour(mod_irc).
+
+%% API
+-export([init/2, get_data/3, set_data/4, import/1, import/2, export/1]).
+
+-include("jlib.hrl").
+-include("mod_irc.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_data(LServer, Host, From) ->
+ LJID = jid:tolower(jid:remove_resource(From)),
+ SJID = ejabberd_sql:escape(jid:to_string(LJID)),
+ SHost = ejabberd_sql:escape(Host),
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ [<<"select data from irc_custom where jid='">>,
+ SJID, <<"' and host='">>, SHost,
+ <<"';">>]) of
+ {selected, [<<"data">>], [[SData]]} ->
+ mod_irc:data_to_binary(From, ejabberd_sql:decode_term(SData));
+ {'EXIT', _} -> error;
+ {selected, _, _} -> empty
+ end.
+
+set_data(LServer, Host, From, Data) ->
+ LJID = jid:tolower(jid:remove_resource(From)),
+ SJID = ejabberd_sql:escape(jid:to_string(LJID)),
+ SHost = ejabberd_sql:escape(Host),
+ SData = ejabberd_sql:encode_term(Data),
+ F = fun () ->
+ sql_queries:update_t(<<"irc_custom">>,
+ [<<"jid">>, <<"host">>, <<"data">>],
+ [SJID, SHost, SData],
+ [<<"jid='">>, SJID, <<"' and host='">>,
+ SHost, <<"'">>]),
+ ok
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+export(_Server) ->
+ [{irc_custom,
+ fun(Host, #irc_custom{us_host = {{U, S}, IRCHost},
+ data = Data}) ->
+ case str:suffix(Host, IRCHost) of
+ true ->
+ SJID = ejabberd_sql:escape(
+ jid:to_string(
+ jid:make(U, S, <<"">>))),
+ SIRCHost = ejabberd_sql:escape(IRCHost),
+ SData = ejabberd_sql:encode_term(Data),
+ [[<<"delete from irc_custom where jid='">>, SJID,
+ <<"' and host='">>, SIRCHost, <<"';">>],
+ [<<"insert into irc_custom(jid, host, "
+ "data) values ('">>,
+ SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
+ <<"');">>]];
+ false ->
+ []
+ end
+ end}].
+
+import(_LServer) ->
+ [{<<"select jid, host, data from irc_custom;">>,
+ fun([SJID, IRCHost, SData]) ->
+ #jid{luser = U, lserver = S} = jid:from_string(SJID),
+ Data = ejabberd_sql:decode_term(SData),
+ #irc_custom{us_host = {{U, S}, IRCHost},
+ data = Data}
+ end}].
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_last.erl b/src/mod_last.erl
index d76b60491..1af1847b3 100644
--- a/src/mod_last.erl
+++ b/src/mod_last.erl
@@ -45,23 +45,21 @@
-include("jlib.hrl").
-include("mod_privacy.hrl").
+-include("mod_last.hrl").
--record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()},
- timestamp = 0 :: non_neg_integer(),
- status = <<"">> :: binary()}).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #last_activity{}) -> ok | pass.
+-callback get_last(binary(), binary()) ->
+ {ok, non_neg_integer(), binary()} | not_found | {error, any()}.
+-callback store_last_info(binary(), binary(), non_neg_integer(), binary()) ->
+ {atomic, any()}.
+-callback remove_user(binary(), binary()) -> {atomic, any()}.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(last_activity,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, last_activity)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_LAST, ?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
@@ -86,10 +84,11 @@ stop(Host) ->
%%%
process_local_iq(_From, _To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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,
@@ -123,10 +122,11 @@ now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
%%%
process_sm_iq(From, To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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,
@@ -153,62 +153,29 @@ process_sm_iq(From, To,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
end;
true ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ Txt = <<"Not subscribed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
end
end.
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
get_last(LUser, LServer) ->
- get_last(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_last(LUser, LServer).
-get_last(LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(last_activity,
- {LUser, LServer})
- of
- {'EXIT', Reason} -> {error, Reason};
- [] -> not_found;
- [#last_activity{timestamp = TimeStamp,
- status = Status}] ->
- {ok, TimeStamp, Status}
- end;
-get_last(LUser, LServer, riak) ->
- case ejabberd_riak:get(last_activity, last_activity_schema(),
- {LUser, LServer}) of
- {ok, #last_activity{timestamp = TimeStamp,
- status = Status}} ->
- {ok, TimeStamp, Status};
- {error, notfound} ->
- not_found;
- Err ->
- Err
- end;
-get_last(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_last(LServer, Username) of
- {selected, [<<"seconds">>, <<"state">>], []} ->
- not_found;
- {selected, [<<"seconds">>, <<"state">>],
- [[STimeStamp, Status]]} ->
- case catch jlib:binary_to_integer(STimeStamp) of
- TimeStamp when is_integer(TimeStamp) ->
- {ok, TimeStamp, Status};
- Reason -> {error, {invalid_timestamp, Reason}}
- end;
- Reason -> {error, {invalid_result, Reason}}
- end.
-
-get_last_iq(IQ, SubEl, LUser, LServer) ->
+get_last_iq(#iq{lang = Lang} = IQ, SubEl, 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, ?ERR_INTERNAL_SERVER_ERROR]};
+ sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
not_found ->
+ Txt = <<"No info about last activity found">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
+ sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]};
{ok, TimeStamp, Status} ->
TimeStamp2 = p1_time_compat:system_time(seconds),
Sec = TimeStamp2 - TimeStamp,
@@ -238,34 +205,8 @@ on_presence_update(User, Server, _Resource, Status) ->
store_last_info(User, Server, TimeStamp, Status) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- store_last_info(LUser, LServer, TimeStamp, Status,
- DBType).
-
-store_last_info(LUser, LServer, TimeStamp, Status,
- mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:write(#last_activity{us = US,
- timestamp = TimeStamp,
- status = Status})
- end,
- mnesia:transaction(F);
-store_last_info(LUser, LServer, TimeStamp, Status,
- riak) ->
- US = {LUser, LServer},
- {atomic, ejabberd_riak:put(#last_activity{us = US,
- timestamp = TimeStamp,
- status = Status},
- last_activity_schema())};
-store_last_info(LUser, LServer, TimeStamp, Status,
- odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- Seconds =
- ejabberd_odbc:escape(iolist_to_binary(integer_to_list(TimeStamp))),
- State = ejabberd_odbc:escape(Status),
- odbc_queries:set_last_t(LServer, Username, Seconds,
- State).
+ 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
@@ -278,72 +219,20 @@ get_last_info(LUser, LServer) ->
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- remove_user(LUser, LServer, DBType).
-
-remove_user(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () -> mnesia:delete({last_activity, US}) end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_last(LServer, Username);
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}.
-
-update_table() ->
- Fields = record_info(fields, last_activity),
- case mnesia:table_info(last_activity, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- last_activity, Fields, set,
- fun(#last_activity{us = {U, _}}) -> U end,
- fun(#last_activity{us = {U, S}, status = Status} = R) ->
- R#last_activity{us = {iolist_to_binary(U),
- iolist_to_binary(S)},
- status = iolist_to_binary(Status)}
- end);
- _ ->
- ?INFO_MSG("Recreating last_activity table", []),
- mnesia:transform_table(last_activity, ignore, Fields)
- end.
-
-last_activity_schema() ->
- {record_info(fields, last_activity), #last_activity{}}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
-export(_Server) ->
- [{last_activity,
- fun(Host, #last_activity{us = {LUser, LServer},
- timestamp = TimeStamp, status = Status})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- Seconds =
- ejabberd_odbc:escape(jlib:integer_to_binary(TimeStamp)),
- State = ejabberd_odbc:escape(Status),
- [[<<"delete from last where username='">>, Username, <<"';">>],
- [<<"insert into last(username, seconds, "
- "state) values ('">>,
- Username, <<"', '">>, Seconds, <<"', '">>, State,
- <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
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}].
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-import(_LServer, mnesia, #last_activity{} = LA) ->
- mnesia:dirty_write(LA);
-import(_LServer, riak, #last_activity{} = LA) ->
- ejabberd_riak:put(LA, last_activity_schema());
-import(_, _, _) ->
- pass.
+import(LServer, DBType, LA) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, LA).
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
diff --git a/src/mod_last_mnesia.erl b/src/mod_last_mnesia.erl
new file mode 100644
index 000000000..7a1610abb
--- /dev/null
+++ b/src/mod_last_mnesia.erl
@@ -0,0 +1,72 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_last_mnesia).
+-behaviour(mod_last).
+
+%% API
+-export([init/2, import/2, get_last/2, store_last_info/4, remove_user/2]).
+
+-include("mod_last.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(last_activity,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, last_activity)}]),
+ update_table().
+
+get_last(LUser, LServer) ->
+ case mnesia:dirty_read(last_activity, {LUser, LServer}) of
+ [] ->
+ not_found;
+ [#last_activity{timestamp = TimeStamp,
+ status = Status}] ->
+ {ok, TimeStamp, Status}
+ end.
+
+store_last_info(LUser, LServer, TimeStamp, Status) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:write(#last_activity{us = US,
+ timestamp = TimeStamp,
+ status = Status})
+ end,
+ mnesia:transaction(F).
+
+remove_user(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () -> mnesia:delete({last_activity, US}) end,
+ mnesia:transaction(F).
+
+import(_LServer, #last_activity{} = LA) ->
+ mnesia:dirty_write(LA).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, last_activity),
+ case mnesia:table_info(last_activity, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ last_activity, Fields, set,
+ fun(#last_activity{us = {U, _}}) -> U end,
+ fun(#last_activity{us = {U, S}, status = Status} = R) ->
+ R#last_activity{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ status = iolist_to_binary(Status)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating last_activity table", []),
+ mnesia:transform_table(last_activity, ignore, Fields)
+ end.
diff --git a/src/mod_last_riak.erl b/src/mod_last_riak.erl
new file mode 100644
index 000000000..d25a3a156
--- /dev/null
+++ b/src/mod_last_riak.erl
@@ -0,0 +1,53 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_last_riak).
+-behaviour(mod_last).
+
+%% API
+-export([init/2, import/2, get_last/2, store_last_info/4, remove_user/2]).
+
+-include("mod_last.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_last(LUser, LServer) ->
+ case ejabberd_riak:get(last_activity, last_activity_schema(),
+ {LUser, LServer}) of
+ {ok, #last_activity{timestamp = TimeStamp,
+ status = Status}} ->
+ {ok, TimeStamp, Status};
+ {error, notfound} ->
+ not_found;
+ Err ->
+ Err
+ end.
+
+store_last_info(LUser, LServer, TimeStamp, Status) ->
+ US = {LUser, LServer},
+ {atomic, ejabberd_riak:put(#last_activity{us = US,
+ timestamp = TimeStamp,
+ status = Status},
+ last_activity_schema())}.
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}.
+
+import(_LServer, #last_activity{} = LA) ->
+ ejabberd_riak:put(LA, last_activity_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+last_activity_schema() ->
+ {record_info(fields, last_activity), #last_activity{}}.
diff --git a/src/mod_last_sql.erl b/src/mod_last_sql.erl
new file mode 100644
index 000000000..5f67b14fc
--- /dev/null
+++ b/src/mod_last_sql.erl
@@ -0,0 +1,75 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_last_sql).
+-behaviour(mod_last).
+
+%% API
+-export([init/2, get_last/2, store_last_info/4, remove_user/2,
+ import/1, import/2, export/1]).
+
+-include("mod_last.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_last(LUser, LServer) ->
+ case catch sql_queries:get_last(LServer, LUser) of
+ {selected, []} ->
+ not_found;
+ {selected, [{TimeStamp, Status}]} ->
+ {ok, TimeStamp, Status};
+ Reason ->
+ ?ERROR_MSG("failed to get last for user ~s@~s: ~p",
+ [LUser, LServer, Reason]),
+ {error, {invalid_result, Reason}}
+ end.
+
+store_last_info(LUser, LServer, TimeStamp, Status) ->
+ sql_queries:set_last_t(LServer, LUser, 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},
+ timestamp = TimeStamp, status = Status})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ Seconds =
+ ejabberd_sql:escape(jlib:integer_to_binary(TimeStamp)),
+ State = ejabberd_sql:escape(Status),
+ [[<<"delete from last where username='">>, Username, <<"';">>],
+ [<<"insert into last(username, seconds, "
+ "state) values ('">>,
+ Username, <<"', '">>, Seconds, <<"', '">>, State,
+ <<"');">>]];
+ (_Host, _R) ->
+ []
+ 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
+%%%===================================================================
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index d6e4e9368..f84519a08 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -35,41 +35,37 @@
-export([user_send_packet/4, user_receive_packet/5,
process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
- remove_user/2, remove_user/3, mod_opt_type/1, muc_process_iq/4,
+ remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4,
muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
- get_commands_spec/0]).
+ get_commands_spec/0, msg_to_el/4]).
--include_lib("stdlib/include/ms_transform.hrl").
-include("jlib.hrl").
-include("logger.hrl").
-include("mod_muc_room.hrl").
-include("ejabberd_commands.hrl").
+-include("mod_mam.hrl").
-define(DEF_PAGE_SIZE, 50).
-define(MAX_PAGE_SIZE, 250).
--define(BIN_GREATER_THAN(A, B),
- ((A > B andalso byte_size(A) == byte_size(B))
- orelse byte_size(A) > byte_size(B))).
--define(BIN_LESS_THAN(A, B),
- ((A < B andalso byte_size(A) == byte_size(B))
- orelse byte_size(A) < byte_size(B))).
-
--record(archive_msg,
- {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2',
- id = <<>> :: binary() | '_',
- timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_' | '$1',
- peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined,
- bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
- packet = #xmlel{} :: xmlel() | '_',
- nick = <<"">> :: binary(),
- type = chat :: chat | groupchat}).
-
--record(archive_prefs,
- {us = {<<"">>, <<"">>} :: {binary(), binary()},
- default = never :: never | always | roster,
- always = [] :: [ljid()],
- never = [] :: [ljid()]}).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback remove_user(binary(), binary()) -> any().
+-callback remove_room(binary(), binary(), binary()) -> any().
+-callback delete_old_messages(binary() | global,
+ erlang:timestamp(),
+ all | chat | groupchat) -> any().
+-callback extended_fields() -> [xmlel()].
+-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) ->
+ {[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
%%%===================================================================
%%% API
@@ -77,9 +73,9 @@
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- DBType = gen_mod:db_type(Host, Opts),
- init_db(DBType, Host),
- init_cache(DBType, Opts),
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
+ init_cache(Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
@@ -120,18 +116,7 @@ start(Host, Opts) ->
ejabberd_commands:register_commands(get_commands_spec()),
ok.
-init_db(mnesia, _Host) ->
- mnesia:create_table(archive_msg,
- [{disc_only_copies, [node()]},
- {type, bag},
- {attributes, record_info(fields, archive_msg)}]),
- mnesia:create_table(archive_prefs,
- [{disc_only_copies, [node()]},
- {attributes, record_info(fields, archive_prefs)}]);
-init_db(_, _) ->
- ok.
-
-init_cache(_DBType, Opts) ->
+init_cache(Opts) ->
MaxSize = gen_mod:get_opt(cache_size, Opts,
fun(I) when is_integer(I), I>0 -> I end,
1000),
@@ -179,24 +164,14 @@ stop(Host) ->
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- remove_user(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
-remove_user(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:delete({archive_msg, US}),
- mnesia:delete({archive_prefs, US})
- end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- SUser = ejabberd_odbc:escape(LUser),
- ejabberd_odbc:sql_query(
- LServer,
- [<<"delete from archive where username='">>, SUser, <<"';">>]),
- ejabberd_odbc:sql_query(
- LServer,
- [<<"delete from archive_prefs where username='">>, SUser, <<"';">>]).
+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).
user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
LUser = JID#jid.luser,
@@ -287,7 +262,7 @@ muc_process_iq(#iq{type = set,
sub_el = #xmlel{name = <<"query">>,
attrs = Attrs} = SubEl} = IQ,
MUCState, From, To) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ 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));
_ ->
@@ -297,7 +272,7 @@ muc_process_iq(#iq{type = get,
sub_el = #xmlel{name = <<"query">>,
attrs = Attrs} = SubEl} = IQ,
MUCState, From, To) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ 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 ->
@@ -310,8 +285,8 @@ muc_process_iq(IQ, _MUCState, _From, _To) ->
IQ.
get_xdata_fields(SubEl) ->
- case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
- xml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
+ 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{}} ->
@@ -343,10 +318,10 @@ message_is_archived(false, C2SState, Peer,
if_enabled ->
get_prefs(LUser, LServer);
on_request ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
cache_tab:lookup(archive_prefs, {LUser, LServer},
fun() ->
- get_prefs(LUser, LServer, DBType)
+ Mod:get_prefs(LUser, LServer)
end);
never ->
error
@@ -365,21 +340,19 @@ delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
Diff = Days * 24 * 60 * 60 * 1000000,
TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff),
Type = jlib:binary_to_atom(TypeBin),
- {Results, _} =
- lists:foldl(fun(Host, {Results, MnesiaDone}) ->
- case {gen_mod:db_type(Host, ?MODULE), MnesiaDone} of
- {mnesia, true} ->
- {Results, true};
- {mnesia, false} ->
- Res = delete_old_messages(TimeStamp, Type,
- global, mnesia),
- {[Res|Results], true};
- {DBType, _} ->
- Res = delete_old_messages(TimeStamp, Type,
- Host, DBType),
- {[Res|Results], MnesiaDone}
- end
- end, {[], false}, ?MYHOSTS),
+ DBTypes = lists:usort(
+ lists:map(
+ fun(Host) ->
+ case gen_mod:db_type(Host, ?MODULE) of
+ sql -> {sql, Host};
+ Other -> {Other, global}
+ end
+ end, ?MYHOSTS)),
+ Results = lists:map(
+ fun({DBType, ServerHost}) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:delete_old_messages(ServerHost, TimeStamp, Type)
+ end, DBTypes),
case lists:filter(fun(Res) -> Res /= ok end, Results) of
[] -> ok;
[NotOk|_] -> NotOk
@@ -387,27 +360,12 @@ delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
delete_old_messages(_TypeBin, _Days) ->
unsupported_type.
-delete_old_messages(TimeStamp, Type, global, mnesia) ->
- MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS,
- type = MsgType} = Msg)
- when MsgTS < TimeStamp,
- MsgType == Type orelse Type == all ->
- Msg
- end),
- OldMsgs = mnesia:dirty_select(archive_msg, MS),
- lists:foreach(fun(Rec) ->
- ok = mnesia:dirty_delete_object(Rec)
- end, OldMsgs);
-delete_old_messages(_TimeStamp, _Type, _Host, _DBType) ->
- %% TODO
- not_implemented.
-
%%%===================================================================
%%% Internal functions
%%%===================================================================
process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
- NS = case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ NS = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MAM_0 ->
?NS_MAM_0;
_ ->
@@ -427,15 +385,9 @@ process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"text-single">>},
{<<"var">>, <<"end">>}]}],
- Fields = case gen_mod:db_type(LServer, ?MODULE) of
- odbc ->
- WithText = #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, <<"text-single">>},
- {<<"var">>, <<"withtext">>}]},
- [WithText|CommonFields];
- _ ->
- CommonFields
- end,
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ ExtendedFields = Mod:extended_fields(),
+ Fields = ExtendedFields ++ CommonFields,
Form = #xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
children = Fields},
@@ -447,8 +399,8 @@ process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
% Preference setting (both v0.2 & v0.3)
process_iq(#jid{luser = LUser, lserver = LServer},
#jid{lserver = LServer},
- #iq{type = set, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) ->
- try {case xml:get_tag_attr_s(<<"default">>, SubEl) of
+ #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
@@ -461,14 +413,17 @@ process_iq(#jid{luser = LUser, lserver = LServer},
(_, {A, N}) ->
{A, N}
end, {[], []}, SubEl#xmlel.children)} of
- {Default, {Always, Never}} ->
- case write_prefs(LUser, LServer, LServer, Default,
- lists:usort(Always), lists:usort(Never)) of
+ {Default, {Always0, Never0}} ->
+ Always = lists:usort(Always0),
+ Never = lists:usort(Never0),
+ case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
ok ->
- IQ#iq{type = result, sub_el = []};
+ 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, ?ERR_INTERNAL_SERVER_ERROR]}
+ sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
end
catch _:_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
@@ -477,21 +432,11 @@ process_iq(#jid{luser = LUser, lserver = LServer},
#jid{lserver = LServer},
#iq{type = get, sub_el = #xmlel{name = <<"prefs">>}} = IQ) ->
Prefs = get_prefs(LUser, LServer),
- Default = jlib:atom_to_binary(Prefs#archive_prefs.default),
- JFun = fun(L) ->
- [#xmlel{name = <<"jid">>,
- children = [{xmlcdata, jid:to_string(J)}]}
- || J <- L]
- end,
- Always = #xmlel{name = <<"always">>,
- children = JFun(Prefs#archive_prefs.always)},
- Never = #xmlel{name = <<"never">>,
- children = JFun(Prefs#archive_prefs.never)},
- IQ#iq{type = result,
- sub_el = [#xmlel{name = <<"prefs">>,
- attrs = [{<<"xmlns">>, IQ#iq.xmlns},
- {<<"default">>, Default}],
- children = [Always, Never]}]};
+ 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]}.
@@ -524,16 +469,13 @@ process_iq(LServer, #jid{luser = LUser} = From, To, IQ, SubEl, Fs, MsgType) ->
{_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 = xml:get_tag_attr_s(<<"xmlns">>, SubEl),
+ 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,
- #state{config = #config{members_only = MembersOnly}} = MUCState,
- From, To, Fs) ->
- case not MembersOnly orelse
- mod_muc_room:is_occupant_or_admin(From, MUCState) of
+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),
@@ -548,13 +490,13 @@ muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ,
parse_query_v0_2(Query) ->
lists:flatmap(
fun (#xmlel{name = <<"start">>} = El) ->
- [{<<"start">>, [xml:get_tag_cdata(El)]}];
+ [{<<"start">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"end">>} = El) ->
- [{<<"end">>, [xml:get_tag_cdata(El)]}];
+ [{<<"end">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"with">>} = El) ->
- [{<<"with">>, [xml:get_tag_cdata(El)]}];
+ [{<<"with">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"withtext">>} = El) ->
- [{<<"withtext">>, [xml:get_tag_cdata(El)]}];
+ [{<<"withtext">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"set">>}) ->
[{<<"set">>, Query}];
(_) ->
@@ -562,7 +504,7 @@ parse_query_v0_2(Query) ->
end, Query#xmlel.children).
should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
- case xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
+ case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
<<"error">> ->
false;
<<"groupchat">> ->
@@ -578,7 +520,7 @@ should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
no_store ->
false;
none ->
- case xml:get_subtag_cdata(Pkt, <<"body">>) of
+ case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
%% Empty body
false;
@@ -596,7 +538,7 @@ strip_my_archived_tag(Pkt, LServer) ->
fun(#xmlel{name = Tag, attrs = Attrs})
when Tag == <<"archived">>; Tag == <<"stanza-id">> ->
case catch jid:nameprep(
- xml:get_attr_s(
+ fxml:get_attr_s(
<<"by">>, Attrs)) of
LServer ->
false;
@@ -612,9 +554,9 @@ strip_x_jid_tags(Pkt) ->
NewEls = lists:filter(
fun(#xmlel{name = <<"x">>} = XEl) ->
not lists:any(fun(ItemEl) ->
- xml:get_tag_attr(<<"jid">>, ItemEl)
+ fxml:get_tag_attr(<<"jid">>, ItemEl)
/= false
- end, xml:get_subtags(XEl, <<"item">>));
+ end, fxml:get_subtags(XEl, <<"item">>));
(_) ->
true
end, Pkt#xmlel.children),
@@ -650,7 +592,7 @@ should_archive_peer(C2SState,
end.
should_archive_muc(Pkt) ->
- case xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
+ case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
<<"groupchat">> ->
case check_store_hint(Pkt) of
store ->
@@ -658,9 +600,9 @@ should_archive_muc(Pkt) ->
no_store ->
false;
none ->
- case xml:get_subtag_cdata(Pkt, <<"body">>) of
+ case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
- case xml:get_subtag_cdata(Pkt, <<"subject">>) of
+ case fxml:get_subtag_cdata(Pkt, <<"subject">>) of
<<>> ->
false;
_ ->
@@ -688,23 +630,23 @@ check_store_hint(Pkt) ->
end.
has_store_hint(Message) ->
- xml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS)
/= false.
has_no_store_hint(Message) ->
- xml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS)
/= false orelse
- xml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS)
/= false orelse
- xml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS)
/= false orelse
- xml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS)
/= false.
is_resent(Pkt, LServer) ->
- case xml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of
+ case fxml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of
#xmlel{attrs = Attrs} ->
- case xml:get_attr(<<"by">>, Attrs) of
+ case fxml:get_attr(<<"by">>, Attrs) of
{value, LServer} ->
true;
_ ->
@@ -714,13 +656,19 @@ is_resent(Pkt, LServer) ->
false
end.
+may_enter_room(From,
+ #state{config = #config{members_only = false}} = MUCState) ->
+ mod_muc_room:get_affiliation(From, MUCState) /= outcast;
+may_enter_room(From, MUCState) ->
+ mod_muc_room:is_occupant_or_admin(From, MUCState).
+
store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
Prefs = get_prefs(LUser, LServer),
case should_archive_peer(C2SState, Prefs, Peer) of
true ->
US = {LUser, LServer},
- store(Pkt, LServer, US, chat, Peer, <<"">>, Dir,
- gen_mod:db_type(LServer, ?MODULE));
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:store(Pkt, LServer, US, chat, Peer, <<"">>, Dir);
false ->
pass
end.
@@ -730,101 +678,26 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
true ->
LServer = MUCState#state.server_host,
{U, S, _} = jid:tolower(RoomJID),
- store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv,
- gen_mod:db_type(LServer, ?MODULE));
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv);
false ->
pass
end.
-store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir, mnesia) ->
- LPeer = {PUser, PServer, _} = jid:tolower(Peer),
- TS = p1_time_compat:timestamp(),
- ID = jlib:integer_to_binary(now_to_usec(TS)),
- case mnesia:dirty_write(
- #archive_msg{us = {LUser, LServer},
- id = ID,
- timestamp = TS,
- peer = LPeer,
- bare_peer = {PUser, PServer, <<>>},
- type = Type,
- nick = Nick,
- packet = Pkt}) of
- ok ->
- {ok, ID};
- Err ->
- Err
- end;
-store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, odbc) ->
- TSinteger = p1_time_compat:system_time(micro_seconds),
- ID = TS = jlib:integer_to_binary(TSinteger),
- SUser = case Type of
- chat -> LUser;
- groupchat -> jid:to_string({LUser, LHost, <<>>})
- end,
- BarePeer = jid:to_string(
- jid:tolower(
- jid:remove_resource(Peer))),
- LPeer = jid:to_string(
- jid:tolower(Peer)),
- XML = xml:element_to_binary(Pkt),
- Body = xml:get_subtag_cdata(Pkt, <<"body">>),
- case ejabberd_odbc:sql_query(
- LServer,
- [<<"insert into archive (username, timestamp, "
- "peer, bare_peer, xml, txt, kind, nick) values (">>,
- <<"'">>, ejabberd_odbc:escape(SUser), <<"', ">>,
- <<"'">>, TS, <<"', ">>,
- <<"'">>, ejabberd_odbc:escape(LPeer), <<"', ">>,
- <<"'">>, ejabberd_odbc:escape(BarePeer), <<"', ">>,
- <<"'">>, ejabberd_odbc:escape(XML), <<"', ">>,
- <<"'">>, ejabberd_odbc:escape(Body), <<"', ">>,
- <<"'">>, jlib:atom_to_binary(Type), <<"', ">>,
- <<"'">>, ejabberd_odbc:escape(Nick), <<"');">>]) of
- {updated, _} ->
- {ok, ID};
- Err ->
- Err
- end.
-
write_prefs(LUser, LServer, Host, Default, Always, Never) ->
- DBType = case gen_mod:db_type(Host, ?MODULE) of
- odbc -> {odbc, Host};
- DB -> DB
- end,
Prefs = #archive_prefs{us = {LUser, LServer},
default = Default,
always = Always,
never = Never},
+ Mod = gen_mod:db_mod(Host, ?MODULE),
cache_tab:dirty_insert(
archive_prefs, {LUser, LServer}, Prefs,
- fun() -> write_prefs(LUser, LServer, Prefs, DBType) end).
-
-write_prefs(_LUser, _LServer, Prefs, mnesia) ->
- mnesia:dirty_write(Prefs);
-write_prefs(LUser, _LServer, #archive_prefs{default = Default,
- never = Never,
- always = Always},
- {odbc, Host}) ->
- SUser = ejabberd_odbc:escape(LUser),
- SDefault = erlang:atom_to_binary(Default, utf8),
- SAlways = ejabberd_odbc:encode_term(Always),
- SNever = ejabberd_odbc:encode_term(Never),
- case update(Host, <<"archive_prefs">>,
- [<<"username">>, <<"def">>, <<"always">>, <<"never">>],
- [SUser, SDefault, SAlways, SNever],
- [<<"username='">>, SUser, <<"'">>]) of
- {updated, _} ->
- ok;
- Err ->
- Err
- end.
+ fun() -> Mod:write_prefs(LUser, LServer, Prefs, Host) end).
get_prefs(LUser, LServer) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
Res = cache_tab:lookup(archive_prefs, {LUser, LServer},
- fun() -> get_prefs(LUser, LServer,
- DBType)
- end),
+ fun() -> Mod:get_prefs(LUser, LServer) end),
case Res of
{ok, Prefs} ->
Prefs;
@@ -846,30 +719,21 @@ get_prefs(LUser, LServer) ->
end
end.
-get_prefs(LUser, LServer, mnesia) ->
- case mnesia:dirty_read(archive_prefs, {LUser, LServer}) of
- [Prefs] ->
- {ok, Prefs};
- _ ->
- error
- end;
-get_prefs(LUser, LServer, odbc) ->
- case ejabberd_odbc:sql_query(
- LServer,
- [<<"select def, always, never from archive_prefs ">>,
- <<"where username='">>,
- ejabberd_odbc:escape(LUser), <<"';">>]) of
- {selected, _, [[SDefault, SAlways, SNever]]} ->
- Default = erlang:binary_to_existing_atom(SDefault, utf8),
- Always = ejabberd_odbc:decode_term(SAlways),
- Never = ejabberd_odbc:decode_term(SNever),
- {ok, #archive_prefs{us = {LUser, LServer},
- default = Default,
- always = Always,
- never = Never}};
- _ ->
- error
- end.
+prefs_el(Default, Always, Never, NS) ->
+ Default1 = jlib:atom_to_binary(Default),
+ JFun = fun(L) ->
+ [#xmlel{name = <<"jid">>,
+ children = [{xmlcdata, jid:to_string(J)}]}
+ || J <- L]
+ end,
+ Always1 = #xmlel{name = <<"always">>,
+ children = JFun(Always)},
+ Never1 = #xmlel{name = <<"never">>,
+ children = JFun(Never)},
+ #xmlel{name = <<"prefs">>,
+ attrs = [{<<"xmlns">>, NS},
+ {<<"default">>, Default1}],
+ children = [Always1, Never1]}.
maybe_activate_mam(LUser, LServer) ->
ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE,
@@ -878,11 +742,10 @@ maybe_activate_mam(LUser, LServer) ->
false),
case ActivateOpt of
true ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
Res = cache_tab:lookup(archive_prefs, {LUser, LServer},
fun() ->
- get_prefs(LUser, LServer,
- gen_mod:db_type(LServer,
- ?MODULE))
+ Mod:get_prefs(LUser, LServer)
end),
case Res of
{ok, _Prefs} ->
@@ -900,31 +763,22 @@ maybe_activate_mam(LUser, LServer) ->
end.
select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) ->
- DBType = case gen_mod:db_type(LServer, ?MODULE) of
- odbc -> {odbc, LServer};
- DB -> DB
- end,
- select_and_send(LServer, From, To, Start, End, With, RSM, IQ,
- MsgType, DBType).
-
-select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType, DBType) ->
{Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End,
- With, RSM, MsgType, DBType),
+ With, RSM, MsgType),
SortedMsgs = lists:keysort(2, Msgs),
send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
-select_and_start(LServer, From, To, Start, End, With, RSM, MsgType, DBType) ->
+select_and_start(LServer, From, To, Start, End, With, RSM, MsgType) ->
case MsgType of
chat ->
- select(LServer, From, Start, End, With, RSM, MsgType, DBType);
+ select(LServer, From, From, Start, End, With, RSM, MsgType);
{groupchat, _Role, _MUCState} ->
- select(LServer, To, Start, End, With, RSM, MsgType, DBType)
+ select(LServer, From, To, Start, End, With, RSM, MsgType)
end.
-select(_LServer, JidRequestor, Start, End, _With, RSM,
+select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
{groupchat, _Role, #state{config = #config{mam = false},
- history = History}} = MsgType,
- _DBType) ->
+ history = History}} = MsgType) ->
#lqueue{len = L, queue = Q} = History,
{Msgs0, _} =
lists:mapfoldl(
@@ -941,8 +795,8 @@ select(_LServer, JidRequestor, Start, End, _With, RSM,
peer = undefined,
nick = Nick,
packet = Pkt},
- MsgType,
- JidRequestor)}], I+1};
+ MsgType, JidRequestor, JidArchive)}],
+ I+1};
false ->
{[], I+1}
end
@@ -958,108 +812,49 @@ select(_LServer, JidRequestor, Start, End, _With, RSM,
_ ->
{Msgs, true, L}
end;
-select(_LServer, #jid{luser = LUser, lserver = LServer} = JidRequestor,
- Start, End, With, RSM, MsgType, mnesia) ->
- MS = make_matchspec(LUser, LServer, Start, End, With),
- Msgs = mnesia:dirty_select(archive_msg, MS),
- SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
- {FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM),
- Count = length(Msgs),
- {lists:map(
- fun(Msg) ->
- {Msg#archive_msg.id,
- jlib:binary_to_integer(Msg#archive_msg.id),
- msg_to_el(Msg, MsgType, JidRequestor)}
- end, FilteredMsgs), IsComplete, Count};
-select(LServer, #jid{luser = LUser} = JidRequestor,
- Start, End, With, RSM, MsgType, {odbc, Host}) ->
- User = case MsgType of
- chat -> LUser;
- {groupchat, _Role, _MUCState} -> jid:to_string(JidRequestor)
- end,
- {Query, CountQuery} = make_sql_query(User, LServer,
- Start, End, With, RSM),
- % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
- % reasonable limit on how many stanzas may be pushed to a client in one
- % request. If a query returns a number of stanzas greater than this limit
- % and the client did not specify a limit using RSM then the server should
- % return a policy-violation error to the client." We currently don't do this
- % for v0.2 requests, but we do limit #rsm_in.max for v0.3 and newer.
- case {ejabberd_odbc:sql_query(Host, Query),
- ejabberd_odbc:sql_query(Host, CountQuery)} of
- {{selected, _, Res}, {selected, _, [[Count]]}} ->
- {Max, Direction} = case RSM of
- #rsm_in{max = M, direction = D} -> {M, D};
- _ -> {undefined, undefined}
- end,
- {Res1, IsComplete} =
- if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
- if Direction == before ->
- {lists:nthtail(1, Res), false};
- true ->
- {lists:sublist(Res, Max), false}
- end;
- true ->
- {Res, true}
- end,
- {lists:flatmap(
- fun([TS, XML, PeerBin, Kind, Nick]) ->
- try
- #xmlel{} = El = xml_stream:parse_element(XML),
- Now = usec_to_now(jlib:binary_to_integer(TS)),
- PeerJid = jid:tolower(jid:from_string(PeerBin)),
- T = case Kind of
- <<"">> -> chat;
- null -> chat;
- _ -> jlib:binary_to_atom(Kind)
- end,
- [{TS, jlib:binary_to_integer(TS),
- msg_to_el(#archive_msg{timestamp = Now,
- packet = El,
- type = T,
- nick = Nick,
- peer = PeerJid},
- MsgType,
- JidRequestor)}]
- catch _:Err ->
- ?ERROR_MSG("failed to parse data from SQL: ~p. "
- "The data was: "
- "timestamp = ~s, xml = ~s, "
- "peer = ~s, kind = ~s, nick = ~s",
- [Err, TS, XML, PeerBin, Kind, Nick]),
- []
- end
- end, Res1), IsComplete, jlib:binary_to_integer(Count)};
- _ ->
- {[], false, 0}
- end.
+select(LServer, From, From, Start, End, With, RSM, MsgType) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:select(LServer, From, From, Start, End, With, RSM, MsgType).
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
- MsgType, #jid{lserver = LServer} = JidRequestor) ->
- Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, Peer, MsgType, Nick),
+ 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 = [xml:replace_tag_attr(
+ 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,
- Peer, {groupchat, Role, _MUCState}, Nick) ->
- Items = case Role of
- moderator when Peer /= undefined ->
+maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
+ Peer, {groupchat, Role,
+ #state{config = #config{anonymous = Anon}}},
+ Nick) ->
+ ExposeJID = case {Peer, JidRequestor} of
+ {undefined, _JidRequestor} ->
+ false;
+ {{U, S, _R}, #jid{luser = U, lserver = S}} ->
+ true;
+ {_Peer, _JidRequestor} when not Anon; Role == moderator ->
+ true;
+ {_Peer, _JidRequestor} ->
+ false
+ end,
+ Items = case ExposeJID of
+ true ->
[#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
children =
[#xmlel{name = <<"item">>,
attrs = [{<<"jid">>,
jid:to_string(Peer)}]}]}];
- _ ->
+ false ->
[]
end,
Pkt1 = Pkt#xmlel{children = Items ++ Els},
- Pkt2 = jlib:replace_from(jid:replace_resource(JidRequestor, Nick), Pkt1),
+ Pkt2 = jlib:replace_from(jid:replace_resource(JidArchive, Nick), Pkt1),
jlib:remove_attr(<<"to">>, Pkt2);
-maybe_update_from_to(Pkt, _JidRequestor, _Peer, chat, _Nick) ->
+maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) ->
Pkt.
is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) ->
@@ -1095,8 +890,8 @@ is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) ->
end.
send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
- QID = xml:get_tag_attr_s(<<"queryid">>, SubEl),
- NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl),
+ QID = fxml:get_tag_attr_s(<<"queryid">>, SubEl),
+ NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
QIDAttr = if QID /= <<>> ->
[{<<"queryid">>, QID}];
true ->
@@ -1135,7 +930,6 @@ send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
ignore
end.
-
make_rsm_out([], _, Count, Attrs, NS) ->
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
true -> <<"fin">>
@@ -1152,32 +946,6 @@ make_rsm_out([{FirstID, _, _}|_] = Msgs, _, Count, Attrs, NS) ->
#rsm_out{first = FirstID, count = Count,
last = LastID})}].
-filter_by_rsm(Msgs, none) ->
- {Msgs, true};
-filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
- {[], true};
-filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) ->
- NewMsgs = case Direction of
- aft when ID /= <<"">> ->
- lists:filter(
- fun(#archive_msg{id = I}) ->
- ?BIN_GREATER_THAN(I, ID)
- end, Msgs);
- before when ID /= <<"">> ->
- lists:foldl(
- fun(#archive_msg{id = I} = Msg, Acc)
- when ?BIN_LESS_THAN(I, ID) ->
- [Msg|Acc];
- (_, Acc) ->
- Acc
- end, [], Msgs);
- before when ID == <<"">> ->
- lists:reverse(Msgs);
- _ ->
- Msgs
- end,
- filter_by_max(NewMsgs, Max).
-
filter_by_max(Msgs, undefined) ->
{Msgs, true};
filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
@@ -1206,118 +974,6 @@ match_rsm(Now, #rsm_in{id = ID, direction = before}) when ID /= <<"">> ->
match_rsm(_Now, _) ->
true.
-make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) ->
- ets:fun2ms(
- fun(#archive_msg{timestamp = TS,
- us = US,
- bare_peer = BPeer} = Msg)
- when Start =< TS, End >= TS,
- US == {LUser, LServer},
- BPeer == With ->
- Msg
- end);
-make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) ->
- ets:fun2ms(
- fun(#archive_msg{timestamp = TS,
- us = US,
- peer = Peer} = Msg)
- when Start =< TS, End >= TS,
- US == {LUser, LServer},
- Peer == With ->
- Msg
- end);
-make_matchspec(LUser, LServer, Start, End, none) ->
- ets:fun2ms(
- fun(#archive_msg{timestamp = TS,
- us = US,
- peer = Peer} = Msg)
- when Start =< TS, End >= TS,
- US == {LUser, LServer} ->
- Msg
- end).
-
-make_sql_query(User, _LServer, Start, End, With, RSM) ->
- {Max, Direction, ID} = case RSM of
- #rsm_in{} ->
- {RSM#rsm_in.max,
- RSM#rsm_in.direction,
- RSM#rsm_in.id};
- none ->
- {none, none, <<>>}
- end,
- LimitClause = if is_integer(Max), Max >= 0 ->
- [<<" limit ">>, jlib:integer_to_binary(Max+1)];
- true ->
- []
- end,
- WithClause = case With of
- {text, <<>>} ->
- [];
- {text, Txt} ->
- [<<" and match (txt) against ('">>,
- ejabberd_odbc:escape(Txt), <<"')">>];
- {_, _, <<>>} ->
- [<<" and bare_peer='">>,
- ejabberd_odbc:escape(jid:to_string(With)),
- <<"'">>];
- {_, _, _} ->
- [<<" and peer='">>,
- ejabberd_odbc:escape(jid:to_string(With)),
- <<"'">>];
- none ->
- []
- end,
- PageClause = case catch jlib:binary_to_integer(ID) of
- I when is_integer(I), I >= 0 ->
- case Direction of
- before ->
- [<<" AND timestamp < ">>, ID];
- aft ->
- [<<" AND timestamp > ">>, ID];
- _ ->
- []
- end;
- _ ->
- []
- end,
- StartClause = case Start of
- {_, _, _} ->
- [<<" and timestamp >= ">>,
- jlib:integer_to_binary(now_to_usec(Start))];
- _ ->
- []
- end,
- EndClause = case End of
- {_, _, _} ->
- [<<" and timestamp <= ">>,
- jlib:integer_to_binary(now_to_usec(End))];
- _ ->
- []
- end,
- SUser = ejabberd_odbc:escape(User),
-
- Query = [<<"SELECT timestamp, xml, peer, kind, nick"
- " FROM archive WHERE username='">>,
- SUser, <<"'">>, WithClause, StartClause, EndClause,
- PageClause],
-
- QueryPage =
- case Direction of
- before ->
- % ID can be empty because of
- % XEP-0059: Result Set Management
- % 2.5 Requesting the Last Page in a Result Set
- [<<"SELECT timestamp, xml, peer, kind, nick FROM (">>, Query,
- <<" ORDER BY timestamp DESC ">>,
- LimitClause, <<") AS t ORDER BY timestamp ASC;">>];
- _ ->
- [Query, <<" ORDER BY timestamp ASC ">>,
- LimitClause, <<";">>]
- end,
- {QueryPage,
- [<<"SELECT COUNT(*) FROM archive WHERE username='">>,
- SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
-
now_to_usec({MSec, Sec, USec}) ->
(MSec*1000000 + Sec)*1000000 + USec.
@@ -1336,35 +992,13 @@ datetime_to_now(DateTime, USecs) ->
get_jids(Els) ->
lists:flatmap(
fun(#xmlel{name = <<"jid">>} = El) ->
- J = jid:from_string(xml:get_tag_cdata(El)),
+ J = jid:from_string(fxml:get_tag_cdata(El)),
[jid:tolower(jid:remove_resource(J)),
jid:tolower(J)];
(_) ->
[]
end, Els).
-update(LServer, Table, Fields, Vals, Where) ->
- UPairs = lists:zipwith(fun (A, B) ->
- <<A/binary, "='", B/binary, "'">>
- end,
- Fields, Vals),
- case ejabberd_odbc:sql_query(LServer,
- [<<"update ">>, Table, <<" set ">>,
- join(UPairs, <<", ">>), <<" where ">>, Where,
- <<";">>])
- of
- {updated, 1} -> {updated, 1};
- _ ->
- ejabberd_odbc:sql_query(LServer,
- [<<"insert into ">>, Table, <<"(">>,
- join(Fields, <<", ">>), <<") values ('">>,
- join(Vals, <<"', '">>), <<"');">>])
- end.
-
-%% Almost a copy of string:join/2.
-join([], _Sep) -> [];
-join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
-
get_commands_spec() ->
[#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
desc = "Delete MAM messages older than DAYS",
@@ -1383,7 +1017,12 @@ mod_opt_type(cache_life_time) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(cache_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
-mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(db_type) ->
+ fun(sql) -> sql;
+ (odbc) -> sql;
+ (internal) -> mnesia;
+ (mnesia) -> mnesia
+ end;
mod_opt_type(default) ->
fun (always) -> always;
(never) -> never;
diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl
new file mode 100644
index 000000000..007ef5eba
--- /dev/null
+++ b/src/mod_mam_mnesia.erl
@@ -0,0 +1,178 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_mam_mnesia).
+
+-behaviour(mod_mam).
+
+%% 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]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("jlib.hrl").
+-include("mod_mam.hrl").
+
+-define(BIN_GREATER_THAN(A, B),
+ ((A > B andalso byte_size(A) == byte_size(B))
+ orelse byte_size(A) > byte_size(B))).
+-define(BIN_LESS_THAN(A, B),
+ ((A < B andalso byte_size(A) == byte_size(B))
+ orelse byte_size(A) < byte_size(B))).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(archive_msg,
+ [{disc_only_copies, [node()]},
+ {type, bag},
+ {attributes, record_info(fields, archive_msg)}]),
+ mnesia:create_table(archive_prefs,
+ [{disc_only_copies, [node()]},
+ {attributes, record_info(fields, archive_prefs)}]).
+
+remove_user(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:delete({archive_msg, US}),
+ mnesia:delete({archive_prefs, US})
+ end,
+ mnesia:transaction(F).
+
+remove_room(_LServer, LName, LHost) ->
+ remove_user(LName, LHost).
+
+delete_old_messages(global, TimeStamp, Type) ->
+ MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS,
+ type = MsgType} = Msg)
+ when MsgTS < TimeStamp,
+ MsgType == Type orelse Type == all ->
+ Msg
+ end),
+ OldMsgs = mnesia:dirty_select(archive_msg, MS),
+ lists:foreach(fun(Rec) ->
+ ok = mnesia:dirty_delete_object(Rec)
+ end, OldMsgs).
+
+extended_fields() ->
+ [].
+
+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)),
+ case mnesia:dirty_write(
+ #archive_msg{us = {LUser, LServer},
+ id = ID,
+ timestamp = TS,
+ peer = LPeer,
+ bare_peer = {PUser, PServer, <<>>},
+ type = Type,
+ nick = Nick,
+ packet = Pkt}) of
+ ok ->
+ {ok, ID};
+ Err ->
+ Err
+ end.
+
+write_prefs(_LUser, _LServer, Prefs, _ServerHost) ->
+ mnesia:dirty_write(Prefs).
+
+get_prefs(LUser, LServer) ->
+ case mnesia:dirty_read(archive_prefs, {LUser, LServer}) of
+ [Prefs] ->
+ {ok, Prefs};
+ _ ->
+ error
+ end.
+
+select(_LServer, JidRequestor,
+ #jid{luser = LUser, lserver = LServer} = JidArchive,
+ Start, End, With, RSM, MsgType) ->
+ MS = make_matchspec(LUser, LServer, Start, End, With),
+ Msgs = mnesia:dirty_select(archive_msg, MS),
+ SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
+ {FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM),
+ Count = length(Msgs),
+ {lists:map(
+ fun(Msg) ->
+ {Msg#archive_msg.id,
+ jlib:binary_to_integer(Msg#archive_msg.id),
+ mod_mam:msg_to_el(Msg, MsgType, JidRequestor, JidArchive)}
+ end, FilteredMsgs), IsComplete, Count}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+now_to_usec({MSec, Sec, USec}) ->
+ (MSec*1000000 + Sec)*1000000 + USec.
+
+make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) ->
+ ets:fun2ms(
+ fun(#archive_msg{timestamp = TS,
+ us = US,
+ bare_peer = BPeer} = Msg)
+ when Start =< TS, End >= TS,
+ US == {LUser, LServer},
+ BPeer == With ->
+ Msg
+ end);
+make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) ->
+ ets:fun2ms(
+ fun(#archive_msg{timestamp = TS,
+ us = US,
+ peer = Peer} = Msg)
+ when Start =< TS, End >= TS,
+ US == {LUser, LServer},
+ Peer == With ->
+ Msg
+ end);
+make_matchspec(LUser, LServer, Start, End, none) ->
+ ets:fun2ms(
+ fun(#archive_msg{timestamp = TS,
+ us = US,
+ peer = Peer} = Msg)
+ when Start =< TS, End >= TS,
+ US == {LUser, LServer} ->
+ Msg
+ end).
+
+filter_by_rsm(Msgs, none) ->
+ {Msgs, true};
+filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
+ {[], true};
+filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) ->
+ NewMsgs = case Direction of
+ aft when ID /= <<"">> ->
+ lists:filter(
+ fun(#archive_msg{id = I}) ->
+ ?BIN_GREATER_THAN(I, ID)
+ end, Msgs);
+ before when ID /= <<"">> ->
+ lists:foldl(
+ fun(#archive_msg{id = I} = Msg, Acc)
+ when ?BIN_LESS_THAN(I, ID) ->
+ [Msg|Acc];
+ (_, Acc) ->
+ Acc
+ end, [], Msgs);
+ before when ID == <<"">> ->
+ lists:reverse(Msgs);
+ _ ->
+ Msgs
+ end,
+ filter_by_max(NewMsgs, Max).
+
+filter_by_max(Msgs, undefined) ->
+ {Msgs, true};
+filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
+ {lists:sublist(Msgs, Len), length(Msgs) =< Len};
+filter_by_max(_Msgs, _Junk) ->
+ {[], true}.
diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl
new file mode 100644
index 000000000..69fdf3238
--- /dev/null
+++ b/src/mod_mam_sql.erl
@@ -0,0 +1,309 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_mam_sql).
+
+-behaviour(mod_mam).
+
+%% 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]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("jlib.hrl").
+-include("mod_mam.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+remove_user(LUser, LServer) ->
+ SUser = ejabberd_sql:escape(LUser),
+ ejabberd_sql:sql_query(
+ LServer,
+ [<<"delete from archive where username='">>, SUser, <<"';">>]),
+ ejabberd_sql:sql_query(
+ LServer,
+ [<<"delete from archive_prefs where username='">>, SUser, <<"';">>]).
+
+remove_room(LServer, LName, LHost) ->
+ LUser = jid:to_string({LName, LHost, <<>>}),
+ remove_user(LUser, LServer).
+
+delete_old_messages(ServerHost, TimeStamp, Type) ->
+ TypeClause = if Type == all -> <<"">>;
+ true -> [<<" and kind='">>, jlib:atom_to_binary(Type), <<"'">>]
+ end,
+ TS = integer_to_binary(now_to_usec(TimeStamp)),
+ ejabberd_sql:sql_query(
+ ServerHost, [<<"delete from archive where timestamp<">>,
+ TS, TypeClause, <<";">>]),
+ ok.
+
+extended_fields() ->
+ [#xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"text-single">>},
+ {<<"var">>, <<"withtext">>}]}].
+
+store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
+ TSinteger = p1_time_compat:system_time(micro_seconds),
+ ID = TS = jlib:integer_to_binary(TSinteger),
+ SUser = case Type of
+ chat -> LUser;
+ groupchat -> jid:to_string({LUser, LHost, <<>>})
+ end,
+ BarePeer = jid:to_string(
+ jid:tolower(
+ jid:remove_resource(Peer))),
+ LPeer = jid:to_string(
+ jid:tolower(Peer)),
+ XML = fxml:element_to_binary(Pkt),
+ Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
+ case ejabberd_sql:sql_query(
+ LServer,
+ [<<"insert into archive (username, timestamp, "
+ "peer, bare_peer, xml, txt, kind, nick) values (">>,
+ <<"'">>, ejabberd_sql:escape(SUser), <<"', ">>,
+ <<"'">>, TS, <<"', ">>,
+ <<"'">>, ejabberd_sql:escape(LPeer), <<"', ">>,
+ <<"'">>, ejabberd_sql:escape(BarePeer), <<"', ">>,
+ <<"'">>, ejabberd_sql:escape(XML), <<"', ">>,
+ <<"'">>, ejabberd_sql:escape(Body), <<"', ">>,
+ <<"'">>, jlib:atom_to_binary(Type), <<"', ">>,
+ <<"'">>, ejabberd_sql:escape(Nick), <<"');">>]) of
+ {updated, _} ->
+ {ok, ID};
+ Err ->
+ Err
+ end.
+
+write_prefs(LUser, _LServer, #archive_prefs{default = Default,
+ never = Never,
+ always = Always},
+ ServerHost) ->
+ SUser = ejabberd_sql:escape(LUser),
+ SDefault = erlang:atom_to_binary(Default, utf8),
+ SAlways = ejabberd_sql:encode_term(Always),
+ SNever = ejabberd_sql:encode_term(Never),
+ case update(ServerHost, <<"archive_prefs">>,
+ [<<"username">>, <<"def">>, <<"always">>, <<"never">>],
+ [SUser, SDefault, SAlways, SNever],
+ [<<"username='">>, SUser, <<"'">>]) of
+ {updated, _} ->
+ ok;
+ Err ->
+ Err
+ end.
+
+get_prefs(LUser, LServer) ->
+ case ejabberd_sql:sql_query(
+ LServer,
+ [<<"select def, always, never from archive_prefs ">>,
+ <<"where username='">>,
+ ejabberd_sql:escape(LUser), <<"';">>]) of
+ {selected, _, [[SDefault, SAlways, SNever]]} ->
+ Default = erlang:binary_to_existing_atom(SDefault, utf8),
+ Always = ejabberd_sql:decode_term(SAlways),
+ Never = ejabberd_sql:decode_term(SNever),
+ {ok, #archive_prefs{us = {LUser, LServer},
+ default = Default,
+ always = Always,
+ never = Never}};
+ _ ->
+ error
+ end.
+
+select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
+ Start, End, With, 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),
+ % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
+ % reasonable limit on how many stanzas may be pushed to a client in one
+ % request. If a query returns a number of stanzas greater than this limit
+ % and the client did not specify a limit using RSM then the server should
+ % return a policy-violation error to the client." We currently don't do this
+ % for v0.2 requests, but we do limit #rsm_in.max for v0.3 and newer.
+ case {ejabberd_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,
+ {Res1, IsComplete} =
+ if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
+ if Direction == before ->
+ {lists:nthtail(1, Res), false};
+ true ->
+ {lists:sublist(Res, Max), false}
+ end;
+ true ->
+ {Res, true}
+ end,
+ {lists:flatmap(
+ fun([TS, XML, PeerBin, Kind, Nick]) ->
+ try
+ #xmlel{} = El = fxml_stream:parse_element(XML),
+ Now = usec_to_now(jlib:binary_to_integer(TS)),
+ PeerJid = jid:tolower(jid:from_string(PeerBin)),
+ T = case Kind of
+ <<"">> -> chat;
+ null -> chat;
+ _ -> jlib:binary_to_atom(Kind)
+ end,
+ [{TS, jlib:binary_to_integer(TS),
+ mod_mam:msg_to_el(#archive_msg{timestamp = Now,
+ packet = El,
+ type = T,
+ nick = Nick,
+ peer = PeerJid},
+ MsgType, JidRequestor, JidArchive)}]
+ catch _:Err ->
+ ?ERROR_MSG("failed to parse data from SQL: ~p. "
+ "The data was: "
+ "timestamp = ~s, xml = ~s, "
+ "peer = ~s, kind = ~s, nick = ~s",
+ [Err, TS, XML, PeerBin, Kind, Nick]),
+ []
+ end
+ end, Res1), IsComplete, jlib:binary_to_integer(Count)};
+ _ ->
+ {[], false, 0}
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+now_to_usec({MSec, Sec, USec}) ->
+ (MSec*1000000 + Sec)*1000000 + USec.
+
+usec_to_now(Int) ->
+ Secs = Int div 1000000,
+ USec = Int rem 1000000,
+ MSec = Secs div 1000000,
+ Sec = Secs rem 1000000,
+ {MSec, Sec, USec}.
+
+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,
+ ODBCType = ejabberd_config:get_option(
+ {sql_type, LServer},
+ ejabberd_sql:opt_type(sql_type)),
+ LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql ->
+ [<<" limit ">>, jlib:integer_to_binary(Max+1)];
+ true ->
+ []
+ end,
+ TopClause = if is_integer(Max), Max >= 0, ODBCType == mssql ->
+ [<<" TOP ">>, jlib:integer_to_binary(Max+1)];
+ true ->
+ []
+ end,
+ WithClause = case With of
+ {text, <<>>} ->
+ [];
+ {text, Txt} ->
+ [<<" and match (txt) against ('">>,
+ ejabberd_sql:escape(Txt), <<"')">>];
+ {_, _, <<>>} ->
+ [<<" and bare_peer='">>,
+ ejabberd_sql:escape(jid:to_string(With)),
+ <<"'">>];
+ {_, _, _} ->
+ [<<" and peer='">>,
+ ejabberd_sql:escape(jid:to_string(With)),
+ <<"'">>];
+ none ->
+ []
+ end,
+ PageClause = case catch jlib:binary_to_integer(ID) of
+ I when is_integer(I), I >= 0 ->
+ case Direction of
+ before ->
+ [<<" AND timestamp < ">>, ID];
+ aft ->
+ [<<" AND timestamp > ">>, ID];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end,
+ StartClause = case Start of
+ {_, _, _} ->
+ [<<" and timestamp >= ">>,
+ jlib:integer_to_binary(now_to_usec(Start))];
+ _ ->
+ []
+ end,
+ EndClause = case End of
+ {_, _, _} ->
+ [<<" and timestamp <= ">>,
+ jlib:integer_to_binary(now_to_usec(End))];
+ _ ->
+ []
+ end,
+ SUser = ejabberd_sql:escape(User),
+
+ Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick"
+ " FROM archive WHERE username='">>,
+ SUser, <<"'">>, WithClause, StartClause, EndClause,
+ PageClause],
+
+ QueryPage =
+ case Direction of
+ before ->
+ % ID can be empty because of
+ % XEP-0059: Result Set Management
+ % 2.5 Requesting the Last Page in a Result Set
+ [<<"SELECT timestamp, xml, peer, kind, nick FROM (">>, Query,
+ <<" ORDER BY timestamp DESC ">>,
+ LimitClause, <<") AS t ORDER BY timestamp ASC;">>];
+ _ ->
+ [Query, <<" ORDER BY timestamp ASC ">>,
+ LimitClause, <<";">>]
+ end,
+ {QueryPage,
+ [<<"SELECT COUNT(*) FROM archive WHERE username='">>,
+ SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
+
+update(LServer, Table, Fields, Vals, Where) ->
+ UPairs = lists:zipwith(fun (A, B) ->
+ <<A/binary, "='", B/binary, "'">>
+ end,
+ Fields, Vals),
+ case ejabberd_sql:sql_query(LServer,
+ [<<"update ">>, Table, <<" set ">>,
+ join(UPairs, <<", ">>), <<" where ">>, Where,
+ <<";">>])
+ of
+ {updated, 1} -> {updated, 1};
+ _ ->
+ ejabberd_sql:sql_query(LServer,
+ [<<"insert into ">>, Table, <<"(">>,
+ join(Fields, <<", ">>), <<") values ('">>,
+ join(Vals, <<"', '">>), <<"');">>])
+ end.
+
+%% Almost a copy of string:join/2.
+join([], _Sep) -> [];
+join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl
index 3e64da952..c175fcb8e 100644
--- a/src/mod_metrics.erl
+++ b/src/mod_metrics.erl
@@ -39,7 +39,7 @@
s2s_send_packet, s2s_receive_packet,
remove_user, register_user]).
--export([start/2, stop/1, send_metrics/4]).
+-export([start/2, stop/1, send_metrics/4, opt_type/1]).
-export([offline_message_hook/3,
sm_register_connection_hook/3, sm_remove_connection_hook/3,
@@ -123,3 +123,6 @@ send_metrics(Host, Probe, Peer, Port) ->
Error ->
?WARNING_MSG("can not open udp socket to grapherl: ~p", [Error])
end.
+
+opt_type(_) ->
+ [].
diff --git a/src/mod_mix.erl b/src/mod_mix.erl
new file mode 100644
index 000000000..c0835b74e
--- /dev/null
+++ b/src/mod_mix.erl
@@ -0,0 +1,348 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 2 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_mix).
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+%% API
+-export([start_link/2, start/2, stop/1, process_iq/3,
+ disco_items/5, disco_identity/5, disco_info/5,
+ disco_features/5, mod_opt_type/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-include("logger.hrl").
+-include("jlib.hrl").
+-include("pubsub.hrl").
+
+-define(PROCNAME, ejabberd_mod_mix).
+-define(NODES, [?NS_MIX_NODES_MESSAGES,
+ ?NS_MIX_NODES_PRESENCE,
+ ?NS_MIX_NODES_PARTICIPANTS,
+ ?NS_MIX_NODES_SUBJECT,
+ ?NS_MIX_NODES_CONFIG]).
+
+-record(state, {server_host :: binary(),
+ host :: binary()}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+start_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ temporary, 5000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, ChildSpec).
+
+stop(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ supervisor:terminate_child(ejabberd_sup, Proc),
+ supervisor:delete_child(ejabberd_sup, Proc),
+ ok.
+
+disco_features(_Acc, _From, _To, _Node, _Lang) ->
+ {result, [?NS_MIX_0]}.
+
+disco_items(_Acc, _From, To, _Node, _Lang) when To#jid.luser /= <<"">> ->
+ To_s = jid:to_string(jid:remove_resource(To)),
+ {result, [#xmlel{name = <<"item">>,
+ attrs = [{<<"jid">>, To_s},
+ {<<"node">>, Node}]} || Node <- ?NODES]};
+disco_items(_Acc, _From, _To, _Node, _Lang) ->
+ {result, []}.
+
+disco_identity(Acc, _From, To, _Node, _Lang) when To#jid.luser == <<"">> ->
+ Acc ++ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"name">>, <<"MIX service">>},
+ {<<"type">>, <<"text">>}]}];
+disco_identity(Acc, _From, _To, _Node, _Lang) ->
+ Acc ++ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"type">>, <<"mix">>}]}].
+
+disco_info(_Acc, _From, To, _Node, _Lang) when is_atom(To) ->
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"result">>}],
+ children = [#xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata,
+ ?NS_MIX_SERVICEINFO_0}]}]}]}];
+disco_info(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+process_iq(From, To,
+ #iq{type = set, sub_el = #xmlel{name = <<"join">>} = SubEl} = IQ) ->
+ Nodes = lists:flatmap(
+ fun(#xmlel{name = <<"subscribe">>, attrs = Attrs}) ->
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
+ case lists:member(Node, ?NODES) of
+ true -> [Node];
+ false -> []
+ end;
+ (_) ->
+ []
+ end, SubEl#xmlel.children),
+ case subscribe_nodes(From, To, Nodes) of
+ {result, _} ->
+ case publish_participant(From, To) of
+ {result, _} ->
+ LFrom_s = jid:to_string(jid:tolower(jid:remove_resource(From))),
+ Subscribe = [#xmlel{name = <<"subscribe">>,
+ attrs = [{<<"node">>, Node}]} || Node <- Nodes],
+ IQ#iq{type = result,
+ sub_el = [#xmlel{name = <<"join">>,
+ attrs = [{<<"jid">>, LFrom_s},
+ {<<"xmlns">>, ?NS_MIX_0}],
+ children = Subscribe}]};
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+process_iq(From, To,
+ #iq{type = set, sub_el = #xmlel{name = <<"leave">>} = SubEl} = IQ) ->
+ case delete_participant(From, To) of
+ {result, _} ->
+ case unsubscribe_nodes(From, To, ?NODES) of
+ {result, _} ->
+ IQ#iq{type = result, sub_el = []};
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+process_iq(_From, _To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
+ Txt = <<"Unsupported MIX query">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+init([ServerHost, Opts]) ->
+ Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ ConfigTab = gen_mod:get_module_proc(Host, config),
+ ets:new(ConfigTab, [named_table]),
+ ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
+ ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_ITEMS, mod_disco,
+ process_local_iq_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_INFO, mod_disco,
+ process_local_iq_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_ITEMS, mod_disco,
+ process_local_iq_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_INFO, mod_disco,
+ process_local_iq_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_MIX_0, ?MODULE, process_iq, IQDisc),
+ ejabberd_router:register_route(Host, ServerHost),
+ {ok, #state{server_host = ServerHost, host = Host}}.
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({route, From, To, Packet}, State) ->
+ case catch do_route(State, From, To, Packet) of
+ {'EXIT', _} = Err ->
+ try
+ ?ERROR_MSG("failed to route packet ~p from '~s' to '~s': ~p",
+ [Packet, jid:to_string(From), jid:to_string(To), Err]),
+ ErrPkt = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
+ ejabberd_router:route_error(To, From, ErrPkt, Packet)
+ catch _:_ ->
+ ok
+ end;
+ _ ->
+ ok
+ end,
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, #state{host = Host}) ->
+ ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
+ ejabberd_router:unregister_route(Host),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+do_route(_State, From, To, #xmlel{name = <<"iq">>} = Packet) ->
+ if To#jid.luser == <<"">> ->
+ ejabberd_local:process_iq(From, To, Packet);
+ true ->
+ ejabberd_sm:process_iq(From, To, Packet)
+ end;
+do_route(_State, From, To, #xmlel{name = <<"presence">>} = Packet)
+ when To#jid.luser /= <<"">> ->
+ case fxml:get_tag_attr_s(<<"type">>, Packet) of
+ <<"unavailable">> ->
+ delete_presence(From, To);
+ _ ->
+ ok
+ end;
+do_route(_State, _From, _To, _Packet) ->
+ ok.
+
+subscribe_nodes(From, To, Nodes) ->
+ LTo = jid:tolower(jid:remove_resource(To)),
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ From_s = jid:to_string(LFrom),
+ lists:foldl(
+ fun(_Node, {error, _} = Err) ->
+ Err;
+ (Node, {result, _}) ->
+ case mod_pubsub:subscribe_node(LTo, Node, From, From_s, []) of
+ {error, _} = Err ->
+ case is_item_not_found(Err) of
+ true ->
+ case mod_pubsub:create_node(
+ LTo, To#jid.lserver, Node, LFrom, <<"mix">>) of
+ {result, _} ->
+ mod_pubsub:subscribe_node(LTo, Node, From, From_s, []);
+ Error ->
+ Error
+ end;
+ false ->
+ Err
+ end;
+ {result, _} = Result ->
+ Result
+ end
+ end, {result, []}, Nodes).
+
+unsubscribe_nodes(From, To, Nodes) ->
+ LTo = jid:tolower(jid:remove_resource(To)),
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ From_s = jid:to_string(LFrom),
+ lists:foldl(
+ fun(_Node, {error, _} = Err) ->
+ Err;
+ (Node, {result, _} = Result) ->
+ case mod_pubsub:unsubscribe_node(LTo, Node, From, From_s, <<"">>) of
+ {error, _} = Err ->
+ case is_not_subscribed(Err) of
+ true -> Result;
+ _ -> Err
+ end;
+ {result, _} = Res ->
+ Res
+ end
+ end, {result, []}, Nodes).
+
+publish_participant(From, To) ->
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ LTo = jid:tolower(jid:remove_resource(To)),
+ Participant = #xmlel{name = <<"participant">>,
+ attrs = [{<<"xmlns">>, ?NS_MIX_0},
+ {<<"jid">>, jid:to_string(LFrom)}]},
+ ItemID = p1_sha:sha(jid:to_string(LFrom)),
+ mod_pubsub:publish_item(
+ LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS,
+ From, ItemID, [Participant]).
+
+delete_presence(From, To) ->
+ LFrom = jid:tolower(From),
+ LTo = jid:tolower(jid:remove_resource(To)),
+ case mod_pubsub:get_items(LTo, ?NS_MIX_NODES_PRESENCE) of
+ Items when is_list(Items) ->
+ lists:foreach(
+ fun(#pubsub_item{modification = {_, LJID},
+ itemid = {ItemID, _}}) when LJID == LFrom ->
+ delete_item(From, To, ?NS_MIX_NODES_PRESENCE, ItemID);
+ (_) ->
+ ok
+ end, Items);
+ _ ->
+ ok
+ end.
+
+delete_participant(From, To) ->
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ ItemID = p1_sha:sha(jid:to_string(LFrom)),
+ delete_presence(From, To),
+ delete_item(From, To, ?NS_MIX_NODES_PARTICIPANTS, ItemID).
+
+delete_item(From, To, Node, ItemID) ->
+ LTo = jid:tolower(jid:remove_resource(To)),
+ case mod_pubsub:delete_item(
+ LTo, Node, From, ItemID, true) of
+ {result, _} = Res ->
+ Res;
+ {error, _} = Err ->
+ case is_item_not_found(Err) of
+ true -> {result, []};
+ false -> Err
+ end
+ end.
+
+is_item_not_found({error, ErrEl}) ->
+ case fxml:get_subtag_with_xmlns(
+ ErrEl, <<"item-not-found">>, ?NS_STANZAS) of
+ #xmlel{} -> true;
+ _ -> false
+ end.
+
+is_not_subscribed({error, ErrEl}) ->
+ case fxml:get_subtag_with_xmlns(
+ ErrEl, <<"not-subscribed">>, ?NS_PUBSUB_ERRORS) of
+ #xmlel{} -> true;
+ _ -> false
+ end.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(_) -> [host, iqdisc].
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index a64a00326..6aa186318 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -48,6 +48,7 @@
export/1,
import/1,
import/3,
+ opts_to_binary/1,
can_use_nick/4]).
-export([init/1, handle_call/3, handle_cast/2,
@@ -72,6 +73,17 @@
-define(MAX_ROOMS_DISCOITEMS, 100).
+-type muc_room_opts() :: [{atom(), any()}].
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #muc_room{} | #muc_registered{}) -> ok | pass.
+-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()}.
+-callback can_use_nick(binary(), binary(), jid(), binary()) -> boolean().
+-callback get_rooms(binary(), binary()) -> [#muc_room{}].
+-callback get_nick(binary(), binary(), jid()) -> binary() | error.
+-callback set_nick(binary(), binary(), jid(), binary()) -> {atomic, ok | false}.
+
%%====================================================================
%% API
%%====================================================================
@@ -125,96 +137,24 @@ create_room(Host, Name, From, Nick, Opts) ->
store_room(ServerHost, Host, Name, Opts) ->
LServer = jid:nameprep(ServerHost),
- store_room(LServer, Host, Name, Opts,
- gen_mod:db_type(LServer, ?MODULE)).
-
-store_room(_LServer, Host, Name, Opts, mnesia) ->
- F = fun () ->
- mnesia:write(#muc_room{name_host = {Name, Host},
- opts = Opts})
- end,
- mnesia:transaction(F);
-store_room(_LServer, Host, Name, Opts, riak) ->
- {atomic, ejabberd_riak:put(#muc_room{name_host = {Name, Host},
- opts = Opts},
- muc_room_schema())};
-store_room(LServer, Host, Name, Opts, odbc) ->
- SName = ejabberd_odbc:escape(Name),
- SHost = ejabberd_odbc:escape(Host),
- SOpts = ejabberd_odbc:encode_term(Opts),
- F = fun () ->
- odbc_queries:update_t(<<"muc_room">>,
- [<<"name">>, <<"host">>, <<"opts">>],
- [SName, SHost, SOpts],
- [<<"name='">>, SName, <<"' and host='">>,
- SHost, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:store_room(LServer, Host, Name, Opts).
restore_room(ServerHost, Host, Name) ->
LServer = jid:nameprep(ServerHost),
- restore_room(LServer, Host, Name,
- gen_mod:db_type(LServer, ?MODULE)).
-
-restore_room(_LServer, Host, Name, mnesia) ->
- case catch mnesia:dirty_read(muc_room, {Name, Host}) of
- [#muc_room{opts = Opts}] -> Opts;
- _ -> error
- end;
-restore_room(_LServer, Host, Name, riak) ->
- case ejabberd_riak:get(muc_room, muc_room_schema(), {Name, Host}) of
- {ok, #muc_room{opts = Opts}} -> Opts;
- _ -> error
- end;
-restore_room(LServer, Host, Name, odbc) ->
- SName = ejabberd_odbc:escape(Name),
- SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select opts from muc_room where name='">>,
- SName, <<"' and host='">>, SHost,
- <<"';">>])
- of
- {selected, [<<"opts">>], [[Opts]]} ->
- opts_to_binary(ejabberd_odbc:decode_term(Opts));
- _ -> error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:restore_room(LServer, Host, Name).
forget_room(ServerHost, Host, Name) ->
LServer = jid:nameprep(ServerHost),
- forget_room(LServer, Host, Name,
- gen_mod:db_type(LServer, ?MODULE)).
-
-forget_room(LServer, Host, Name, mnesia) ->
remove_room_mam(LServer, Host, Name),
- F = fun () -> mnesia:delete({muc_room, {Name, Host}})
- end,
- mnesia:transaction(F);
-forget_room(LServer, Host, Name, riak) ->
- remove_room_mam(LServer, Host, Name),
- {atomic, ejabberd_riak:delete(muc_room, {Name, Host})};
-forget_room(LServer, Host, Name, odbc) ->
- remove_room_mam(LServer, Host, Name),
- SName = ejabberd_odbc:escape(Name),
- SHost = ejabberd_odbc:escape(Host),
- F = fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from muc_room where name='">>,
- SName, <<"' and host='">>, SHost,
- <<"';">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:forget_room(LServer, Host, Name).
remove_room_mam(LServer, Host, Name) ->
case gen_mod:is_loaded(LServer, mod_mam) of
true ->
- U = jid:nodeprep(Name),
- S = jid:nameprep(Host),
- DBType = gen_mod:db_type(LServer, mod_mam),
- if DBType == odbc ->
- mod_mam:remove_user(jid:to_string({U, S, <<>>}),
- LServer, DBType);
- true ->
- mod_mam:remove_user(U, S, DBType)
- end;
+ mod_mam:remove_room(LServer, Name, Host);
false ->
ok
end.
@@ -222,7 +162,7 @@ remove_room_mam(LServer, Host, Name) ->
process_iq_disco_items(Host, From, To,
#iq{lang = Lang} = IQ) ->
Rsm = jlib:rsm_decode(IQ),
- DiscoNode = xml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el),
+ DiscoNode = fxml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el),
Res = IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
@@ -233,48 +173,8 @@ process_iq_disco_items(Host, From, To,
can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false;
can_use_nick(ServerHost, Host, JID, Nick) ->
LServer = jid:nameprep(ServerHost),
- can_use_nick(LServer, Host, JID, Nick,
- gen_mod:db_type(LServer, ?MODULE)).
-
-can_use_nick(_LServer, Host, JID, Nick, mnesia) ->
- {LUser, LServer, _} = jid:tolower(JID),
- LUS = {LUser, LServer},
- case catch mnesia:dirty_select(muc_registered,
- [{#muc_registered{us_host = '$1',
- nick = Nick, _ = '_'},
- [{'==', {element, 2, '$1'}, Host}],
- ['$_']}])
- of
- {'EXIT', _Reason} -> true;
- [] -> true;
- [#muc_registered{us_host = {U, _Host}}] -> U == LUS
- end;
-can_use_nick(LServer, Host, JID, Nick, riak) ->
- {LUser, LServer, _} = jid:tolower(JID),
- LUS = {LUser, LServer},
- case ejabberd_riak:get_by_index(muc_registered,
- muc_registered_schema(),
- <<"nick_host">>, {Nick, Host}) of
- {ok, []} ->
- true;
- {ok, [#muc_registered{us_host = {U, _Host}}]} ->
- U == LUS;
- {error, _} ->
- true
- end;
-can_use_nick(LServer, Host, JID, Nick, odbc) ->
- SJID =
- jid:to_string(jid:tolower(jid:remove_resource(JID))),
- SNick = ejabberd_odbc:escape(Nick),
- SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select jid from muc_registered ">>,
- <<"where nick='">>, SNick,
- <<"' and host='">>, SHost, <<"';">>])
- of
- {selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1;
- _ -> true
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:can_use_nick(LServer, Host, JID, Nick).
%%====================================================================
%% gen_server callbacks
@@ -283,21 +183,8 @@ can_use_nick(LServer, Host, JID, Nick, odbc) ->
init([Host, Opts]) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"conference.@HOST@">>),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(muc_room,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, muc_room)}]),
- mnesia:create_table(muc_registered,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, muc_registered)}]),
- update_tables(MyHost),
- mnesia:add_table_index(muc_registered, nick);
- _ ->
- ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, [{host, MyHost}|Opts]),
mnesia:create_table(muc_online_room,
[{ram_copies, [node()]},
{attributes, record_info(fields, muc_online_room)}]),
@@ -373,7 +260,7 @@ init([Host, Opts]) ->
RoomShaper = gen_mod:get_opt(room_shaper, Opts,
fun(A) when is_atom(A) -> A end,
none),
- ejabberd_router:register_route(MyHost),
+ ejabberd_router:register_route(MyHost, Host),
load_permanent_rooms(MyHost, Host,
{Access, AccessCreate, AccessAdmin, AccessPersistent},
HistorySize, RoomShaper),
@@ -451,7 +338,7 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts);
_ ->
#xmlel{attrs = Attrs} = Packet,
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Access denied by service policy">>,
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang, ErrText)),
@@ -557,18 +444,18 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
_ -> ok
end;
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
_ ->
case acl:match_rule(ServerHost, AccessAdmin, From)
of
allow ->
- Msg = xml:get_path_s(Packet,
+ Msg = fxml:get_path_s(Packet,
[{elem, <<"body">>},
cdata]),
broadcast_service_message(Host, Msg);
_ ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText =
<<"Only service administrators are allowed "
"to send service messages">>,
@@ -581,7 +468,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
<<"presence">> -> ok
end;
_ ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
@@ -593,11 +480,12 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
_ ->
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
case {Name, Type} of
{<<"presence">>, <<"">>} ->
case check_user_can_create_room(ServerHost,
- AccessCreate, From, Room) of
+ AccessCreate, From, Room) and
+ check_create_roomid(ServerHost, Room) of
true ->
{ok, Pid} = start_new_room(Host, ServerHost, Access,
Room, HistorySize,
@@ -606,14 +494,14 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
mod_muc_room:route(Pid, From, Nick, Packet),
ok;
false ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Room creation is denied by service policy">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
ejabberd_router:route(To, From, Err)
end;
_ ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ 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)),
@@ -628,56 +516,26 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
end.
check_user_can_create_room(ServerHost, AccessCreate,
- From, RoomID) ->
+ From, _RoomID) ->
case acl:match_rule(ServerHost, AccessCreate, From) of
- allow ->
- byte_size(RoomID) =<
- gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id,
- fun(infinity) -> infinity;
- (I) when is_integer(I), I>0 -> I
- end, infinity);
+ allow -> true;
_ -> false
end.
+check_create_roomid(ServerHost, RoomID) ->
+ Max = gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I), I>0 -> I
+ end, infinity),
+ Regexp = gen_mod:get_module_opt(ServerHost, ?MODULE, regexp_room_id,
+ fun iolist_to_binary/1, ""),
+ (byte_size(RoomID) =< Max) and
+ (re:run(RoomID, Regexp, [unicode, {capture, none}]) == match).
+
get_rooms(ServerHost, Host) ->
LServer = jid:nameprep(ServerHost),
- get_rooms(LServer, Host,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_rooms(_LServer, Host, mnesia) ->
- case catch mnesia:dirty_select(muc_room,
- [{#muc_room{name_host = {'_', Host},
- _ = '_'},
- [], ['$_']}])
- of
- {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
- Rs -> Rs
- end;
-get_rooms(_LServer, Host, riak) ->
- case ejabberd_riak:get(muc_room, muc_room_schema()) of
- {ok, Rs} ->
- lists:filter(
- fun(#muc_room{name_host = {_, H}}) ->
- Host == H
- end, Rs);
- _Err ->
- []
- end;
-get_rooms(LServer, Host, odbc) ->
- SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select name, opts from muc_room ">>,
- <<"where host='">>, SHost, <<"';">>])
- of
- {selected, [<<"name">>, <<"opts">>], RoomOpts} ->
- lists:map(fun ([Room, Opts]) ->
- #muc_room{name_host = {Room, Host},
- opts = opts_to_binary(
- ejabberd_odbc:decode_term(Opts))}
- end,
- RoomOpts);
- Err -> ?ERROR_MSG("failed to get rooms: ~p", [Err]), []
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_rooms(LServer, Host).
load_permanent_rooms(Host, ServerHost, Access,
HistorySize, RoomShaper) ->
@@ -867,41 +725,8 @@ iq_get_unique(From) ->
get_nick(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
- get_nick(LServer, Host, From,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_nick(_LServer, Host, From, mnesia) ->
- {LUser, LServer, _} = jid:tolower(From),
- LUS = {LUser, LServer},
- case catch mnesia:dirty_read(muc_registered,
- {LUS, Host})
- of
- {'EXIT', _Reason} -> error;
- [] -> error;
- [#muc_registered{nick = Nick}] -> Nick
- end;
-get_nick(LServer, Host, From, riak) ->
- {LUser, LServer, _} = jid:tolower(From),
- US = {LUser, LServer},
- case ejabberd_riak:get(muc_registered,
- muc_registered_schema(),
- {US, Host}) of
- {ok, #muc_registered{nick = Nick}} -> Nick;
- {error, _} -> error
- end;
-get_nick(LServer, Host, From, odbc) ->
- SJID =
- ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
- SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select nick from muc_registered where "
- "jid='">>,
- SJID, <<"' and host='">>, SHost,
- <<"';">>])
- of
- {selected, [<<"nick">>], [[Nick]]} -> Nick;
- _ -> error
- end.
+ 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,
@@ -940,107 +765,8 @@ iq_get_register_info(ServerHost, Host, From, Lang) ->
set_nick(ServerHost, Host, From, Nick) ->
LServer = jid:nameprep(ServerHost),
- set_nick(LServer, Host, From, Nick,
- gen_mod:db_type(LServer, ?MODULE)).
-
-set_nick(_LServer, Host, From, Nick, mnesia) ->
- {LUser, LServer, _} = jid:tolower(From),
- LUS = {LUser, LServer},
- F = fun () ->
- case Nick of
- <<"">> ->
- mnesia:delete({muc_registered, {LUS, Host}}), ok;
- _ ->
- Allow = case mnesia:select(muc_registered,
- [{#muc_registered{us_host =
- '$1',
- nick = Nick,
- _ = '_'},
- [{'==', {element, 2, '$1'},
- Host}],
- ['$_']}])
- of
- [] -> true;
- [#muc_registered{us_host = {U, _Host}}] ->
- U == LUS
- end,
- if Allow ->
- mnesia:write(#muc_registered{us_host = {LUS, Host},
- nick = Nick}),
- ok;
- true -> false
- end
- end
- end,
- mnesia:transaction(F);
-set_nick(LServer, Host, From, Nick, riak) ->
- {LUser, LServer, _} = jid:tolower(From),
- LUS = {LUser, LServer},
- {atomic,
- case Nick of
- <<"">> ->
- ejabberd_riak:delete(muc_registered, {LUS, Host});
- _ ->
- Allow = case ejabberd_riak:get_by_index(
- muc_registered,
- muc_registered_schema(),
- <<"nick_host">>, {Nick, Host}) of
- {ok, []} ->
- true;
- {ok, [#muc_registered{us_host = {U, _Host}}]} ->
- U == LUS;
- {error, _} ->
- false
- end,
- if Allow ->
- ejabberd_riak:put(#muc_registered{us_host = {LUS, Host},
- nick = Nick},
- muc_registered_schema(),
- [{'2i', [{<<"nick_host">>,
- {Nick, Host}}]}]);
- true ->
- false
- end
- end};
-set_nick(LServer, Host, From, Nick, odbc) ->
- JID =
- jid:to_string(jid:tolower(jid:remove_resource(From))),
- SJID = ejabberd_odbc:escape(JID),
- SNick = ejabberd_odbc:escape(Nick),
- SHost = ejabberd_odbc:escape(Host),
- F = fun () ->
- case Nick of
- <<"">> ->
- ejabberd_odbc:sql_query_t([<<"delete from muc_registered where ">>,
- <<"jid='">>, SJID,
- <<"' and host='">>, Host,
- <<"';">>]),
- ok;
- _ ->
- Allow = case
- ejabberd_odbc:sql_query_t([<<"select jid from muc_registered ">>,
- <<"where nick='">>,
- SNick,
- <<"' and host='">>,
- SHost, <<"';">>])
- of
- {selected, [<<"jid">>], [[J]]} -> J == JID;
- _ -> true
- end,
- if Allow ->
- odbc_queries:update_t(<<"muc_registered">>,
- [<<"jid">>, <<"host">>,
- <<"nick">>],
- [SJID, SHost, SNick],
- [<<"jid='">>, SJID,
- <<"' and host='">>, SHost,
- <<"'">>]),
- ok;
- true -> false
- end
- end
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_nick(LServer, Host, From, Nick).
iq_set_register_info(ServerHost, Host, From, Nick,
Lang) ->
@@ -1050,24 +776,28 @@ iq_set_register_info(ServerHost, Host, From, Nick,
ErrText = <<"That nickname is registered by another "
"person">>,
{error, ?ERRT_CONFLICT(Lang, ErrText)};
- _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ _ ->
+ Txt = <<"Database failure">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)}
end.
process_iq_register_set(ServerHost, Host, From, SubEl,
Lang) ->
#xmlel{children = Els} = SubEl,
- case xml:get_subtag(SubEl, <<"remove">>) of
+ case fxml:get_subtag(SubEl, <<"remove">>) of
false ->
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>} = XEl] ->
- case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
- xml:get_tag_attr_s(<<"type">>, XEl)}
+ case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
+ fxml:get_tag_attr_s(<<"type">>, XEl)}
of
{?NS_XDATA, <<"cancel">>} -> {result, []};
{?NS_XDATA, <<"submit">>} ->
XData = jlib:parse_xdata_submit(XEl),
case XData of
- invalid -> {error, ?ERR_BAD_REQUEST};
+ invalid ->
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
_ ->
case lists:keysearch(<<"nick">>, 1, XData) of
{value, {_, [Nick]}} when Nick /= <<"">> ->
@@ -1182,118 +912,17 @@ opts_to_binary(Opts) ->
Opt
end, Opts).
-update_tables(Host) ->
- update_muc_room_table(Host),
- update_muc_registered_table(Host).
-
-muc_room_schema() ->
- {record_info(fields, muc_room), #muc_room{}}.
-
-muc_registered_schema() ->
- {record_info(fields, muc_registered), #muc_registered{}}.
-
-update_muc_room_table(_Host) ->
- Fields = record_info(fields, muc_room),
- case mnesia:table_info(muc_room, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- muc_room, Fields, set,
- fun(#muc_room{name_host = {N, _}}) -> N end,
- fun(#muc_room{name_host = {N, H},
- opts = Opts} = R) ->
- R#muc_room{name_host = {iolist_to_binary(N),
- iolist_to_binary(H)},
- opts = opts_to_binary(Opts)}
- end);
- _ ->
- ?INFO_MSG("Recreating muc_room table", []),
- mnesia:transform_table(muc_room, ignore, Fields)
- end.
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
-update_muc_registered_table(_Host) ->
- Fields = record_info(fields, muc_registered),
- case mnesia:table_info(muc_registered, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- muc_registered, Fields, set,
- fun(#muc_registered{us_host = {_, H}}) -> H end,
- fun(#muc_registered{us_host = {{U, S}, H},
- nick = Nick} = R) ->
- R#muc_registered{us_host = {{iolist_to_binary(U),
- iolist_to_binary(S)},
- iolist_to_binary(H)},
- nick = iolist_to_binary(Nick)}
- end);
- _ ->
- ?INFO_MSG("Recreating muc_registered table", []),
- mnesia:transform_table(muc_registered, ignore, Fields)
- end.
+import(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-export(_Server) ->
- [{muc_room,
- fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
- case str:suffix(Host, RoomHost) of
- true ->
- SName = ejabberd_odbc:escape(Name),
- SRoomHost = ejabberd_odbc:escape(RoomHost),
- SOpts = ejabberd_odbc:encode_term(Opts),
- [[<<"delete from muc_room where name='">>, SName,
- <<"' and host='">>, SRoomHost, <<"';">>],
- [<<"insert into muc_room(name, host, opts) ",
- "values (">>,
- <<"'">>, SName, <<"', '">>, SRoomHost,
- <<"', '">>, SOpts, <<"');">>]];
- false ->
- []
- end
- end},
- {muc_registered,
- fun(Host, #muc_registered{us_host = {{U, S}, RoomHost},
- nick = Nick}) ->
- case str:suffix(Host, RoomHost) of
- true ->
- SJID = ejabberd_odbc:escape(
- jid:to_string(
- jid:make(U, S, <<"">>))),
- SNick = ejabberd_odbc:escape(Nick),
- SRoomHost = ejabberd_odbc:escape(RoomHost),
- [[<<"delete from muc_registered where jid='">>,
- SJID, <<"' and host='">>, SRoomHost, <<"';">>],
- [<<"insert into muc_registered(jid, host, "
- "nick) values ('">>,
- SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
- <<"');">>]];
- false ->
- []
- end
- end}].
-
-import(_LServer) ->
- [{<<"select name, host, opts from muc_room;">>,
- fun([Name, RoomHost, SOpts]) ->
- Opts = opts_to_binary(ejabberd_odbc:decode_term(SOpts)),
- #muc_room{name_host = {Name, RoomHost}, opts = Opts}
- 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(_LServer, mnesia, #muc_room{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, mnesia, #muc_registered{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, riak, #muc_room{} = R) ->
- ejabberd_riak:put(R, muc_room_schema());
-import(_LServer, riak,
- #muc_registered{us_host = {_, Host}, nick = Nick} = R) ->
- ejabberd_riak:put(R, muc_registered_schema(),
- [{'2i', [{<<"nick_host">>, {Nick, Host}}]}]);
-import(_, _, _) ->
- pass.
+import(LServer, DBType, Data) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, Data).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
@@ -1317,6 +946,8 @@ mod_opt_type(max_room_id) ->
fun (infinity) -> infinity;
(I) when is_integer(I), I > 0 -> I
end;
+mod_opt_type(regexp_room_id) ->
+ fun iolist_to_binary/1;
mod_opt_type(max_room_name) ->
fun (infinity) -> infinity;
(I) when is_integer(I), I > 0 -> I
@@ -1342,7 +973,7 @@ mod_opt_type(user_presence_shaper) ->
mod_opt_type(_) ->
[access, access_admin, access_create, access_persistent,
db_type, default_room_options, history_size, host,
- max_room_desc, max_room_id, max_room_name,
+ max_room_desc, max_room_id, max_room_name, regexp_room_id,
max_user_conferences, max_users,
max_users_admin_threshold, max_users_presence,
min_message_interval, min_presence_interval,
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index c08757375..5fbda4f28 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -178,7 +178,8 @@ muc_online_rooms(ServerHost) ->
MUCHost = find_host(ServerHost),
Rooms = ets:tab2list(muc_online_room),
lists:foldl(
- fun({_, {Roomname, Host}, _}, Results) ->
+ fun(Room, Results) ->
+ {Roomname, Host} = Room#muc_online_room.name_host,
case MUCHost of
global ->
[<<Roomname/binary, "@", Host/binary>> | Results];
@@ -279,7 +280,7 @@ get_sort_query(Q) ->
get_sort_query2(Q) ->
{value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q),
- Integer = list_to_integer(binary_to_list(String)),
+ Integer = jlib:binary_to_integer(String),
case Integer >= 0 of
true -> {ok, {normal, Integer}};
false -> {ok, {reverse, abs(Integer)}}
@@ -395,7 +396,9 @@ prepare_room_info(Room_info) ->
%% @spec (Name::binary(), Host::binary(), ServerHost::binary()) ->
%% ok | error
%% @doc Create a room immediately with the default options.
-create_room(Name, Host, ServerHost) ->
+create_room(Name1, Host1, ServerHost) ->
+ Name = jid:nodeprep(Name1),
+ Host = jid:nodeprep(Host1),
%% Get the default room options from the muc configuration
DefRoomOpts = gen_mod:get_module_opt(ServerHost, mod_muc,
@@ -471,7 +474,7 @@ destroy_room({N, H, SH}) ->
%% The file encoding must be UTF-8
destroy_rooms_file(Filename) ->
- {ok, F} = file:open(Filename, [read]),
+ {ok, F} = file:open(Filename, [read, binary]),
RJID = read_room(F),
Rooms = read_rooms(F, RJID, []),
file:close(F),
@@ -499,23 +502,16 @@ read_room(F) ->
%% This function is quite rudimentary
%% and may not be accurate
split_roomjid(RoomJID) ->
- [Name, Host] = string:tokens(RoomJID, "@"),
- [_MUC_service_name | ServerHostList] = string:tokens(Host, "."),
- ServerHost = join(ServerHostList, "."),
- {list_to_binary(Name), list_to_binary(Host), list_to_binary(ServerHost)}.
-
-%% This function is copied from string:join/2 in Erlang/OTP R12B-1
-%% Note that string:join/2 is not implemented in Erlang/OTP R11B
-join([H|T], Sep) ->
- H ++ lists:concat([Sep ++ X || X <- T]).
-
+ [Name, Host] = binary:split(RoomJID, <<"@">>),
+ [_MUC_service_name, ServerHost] = binary:split(Host, <<".">>),
+ {Name, Host, ServerHost}.
%%----------------------------
%% Create Rooms in File
%%----------------------------
create_rooms_file(Filename) ->
- {ok, F} = file:open(Filename, [read]),
+ {ok, F} = file:open(Filename, [read, binary]),
RJID = read_room(F),
Rooms = read_rooms(F, RJID, []),
file:close(F),
@@ -692,29 +688,32 @@ send_direct_invitation(RoomName, RoomService, Password, Reason, UsersString) ->
RoomJid = jid:make(RoomName, RoomService, <<"">>),
RoomString = jid:to_string(RoomJid),
XmlEl = build_invitation(Password, Reason, RoomString),
- UsersStrings = get_users_to_invite(RoomJid, binary_to_list(UsersString)),
- [send_direct_invitation(RoomJid, jid:from_string(list_to_binary(UserStrings)), XmlEl)
+ UsersStrings = get_users_to_invite(RoomJid, UsersString),
+ [send_direct_invitation(RoomJid, UserStrings, XmlEl)
|| UserStrings <- UsersStrings],
timer:sleep(1000),
ok.
get_users_to_invite(RoomJid, UsersString) ->
- UsersStrings = string:tokens(UsersString, ":"),
+ UsersStrings = binary:split(UsersString, <<":">>, [global]),
OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
RoomJid#jid.lserver),
OccupantsJids = [jid:from_string(JidString)
|| {JidString, _Nick, _} <- OccupantsTuples],
- lists:filter(
- fun(UserString) ->
- UserJid = jid:from_string(list_to_binary(UserString)),
- %% [{"badlop@localhost/work","badlop","moderator"}]
- lists:all(fun(OccupantJid) ->
- UserJid#jid.luser /= OccupantJid#jid.luser
- orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
- end,
- OccupantsJids)
- end,
- UsersStrings).
+ lists:filtermap(
+ fun(UserString) ->
+ UserJid = jid:from_string(UserString),
+ Val = lists:all(fun(OccupantJid) ->
+ UserJid#jid.luser /= OccupantJid#jid.luser
+ orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
+ end,
+ OccupantsJids),
+ case Val of
+ true -> {true, UserJid};
+ _ -> false
+ end
+ end,
+ UsersStrings).
build_invitation(Password, Reason, RoomString) ->
PasswordAttrList = case Password of
@@ -825,8 +824,12 @@ get_room_options(Pid) ->
get_options(Config).
get_options(Config) ->
- Fields = record_info(fields, config),
- [config | Values] = tuple_to_list(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_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
+ (V) -> V end, ValuesRaw),
lists:zip(Fields, Values).
%%----------------------------
@@ -882,12 +885,19 @@ make_opts(StateData) ->
Config = StateData#state.config,
[
{title, Config#config.title},
+ {vcard, Config#config.vcard},
+ {voice_request_min_interval, Config#config.voice_request_min_interval},
{allow_change_subj, Config#config.allow_change_subj},
{allow_query_users, Config#config.allow_query_users},
{allow_private_messages, Config#config.allow_private_messages},
+ {allow_private_messages_from_visitors, Config#config.allow_private_messages_from_visitors},
+ {allow_visitor_status, Config#config.allow_visitor_status},
+ {allow_visitor_nickchange, Config#config.allow_visitor_nickchange},
+ {allow_voice_requests, Config#config.allow_voice_requests},
{public, Config#config.public},
{public_list, Config#config.public_list},
{persistent, Config#config.persistent},
+ {mam, Config#config.mam},
{moderated, Config#config.moderated},
{members_by_default, Config#config.members_by_default},
{members_only, Config#config.members_only},
@@ -895,6 +905,8 @@ make_opts(StateData) ->
{password_protected, Config#config.password_protected},
{password, Config#config.password},
{anonymous, Config#config.anonymous},
+ {captcha_protected, Config#config.captcha_protected},
+ {description, Config#config.description},
{logging, Config#config.logging},
{max_users, Config#config.max_users},
{affiliations, ?DICT:to_list(StateData#state.affiliations)},
diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl
index 4d8e3965c..1f32f6f9b 100644
--- a/src/mod_muc_log.erl
+++ b/src/mod_muc_log.erl
@@ -193,15 +193,15 @@ 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 {xml:get_subtag(Packet, <<"subject">>),
- xml:get_subtag(Packet, <<"body">>)}
+ case {fxml:get_subtag(Packet, <<"subject">>),
+ fxml:get_subtag(Packet, <<"body">>)}
of
{false, false} -> ok;
{false, SubEl} ->
- Message = {body, xml:get_tag_cdata(SubEl)},
+ Message = {body, fxml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State);
{SubEl, _} ->
- Message = {subject, xml:get_tag_cdata(SubEl)},
+ Message = {subject, fxml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State)
end;
true -> ok
@@ -1201,13 +1201,13 @@ fjoin(FileList) ->
list_to_binary(filename:join([binary_to_list(File) || File <- FileList])).
has_no_permanent_store_hint(Packet) ->
- xml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS)
=/= false orelse
- xml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS)
=/= false orelse
- xml:get_subtag_with_xmlns(Packet, <<"no-permanent-store">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-store">>, ?NS_HINTS)
=/= false orelse
- xml:get_subtag_with_xmlns(Packet, <<"no-permanent-storage">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-storage">>, ?NS_HINTS)
=/= false.
mod_opt_type(access_log) ->
diff --git a/src/mod_muc_mnesia.erl b/src/mod_muc_mnesia.erl
new file mode 100644
index 000000000..e3ae36978
--- /dev/null
+++ b/src/mod_muc_mnesia.erl
@@ -0,0 +1,163 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_muc_mnesia).
+
+-behaviour(mod_muc).
+
+%% API
+-export([init/2, import/2, store_room/4, restore_room/3, forget_room/3,
+ can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]).
+
+-include("mod_muc.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, Opts) ->
+ MyHost = proplists:get_value(host, Opts),
+ mnesia:create_table(muc_room,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, muc_room)}]),
+ mnesia:create_table(muc_registered,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, muc_registered)}]),
+ update_tables(MyHost),
+ mnesia:add_table_index(muc_registered, nick).
+
+store_room(_LServer, Host, Name, Opts) ->
+ F = fun () ->
+ mnesia:write(#muc_room{name_host = {Name, Host},
+ opts = Opts})
+ end,
+ mnesia:transaction(F).
+
+restore_room(_LServer, Host, Name) ->
+ case catch mnesia:dirty_read(muc_room, {Name, Host}) of
+ [#muc_room{opts = Opts}] -> Opts;
+ _ -> error
+ end.
+
+forget_room(_LServer, Host, Name) ->
+ F = fun () -> mnesia:delete({muc_room, {Name, Host}})
+ end,
+ mnesia:transaction(F).
+
+can_use_nick(_LServer, Host, JID, Nick) ->
+ {LUser, LServer, _} = jid:tolower(JID),
+ LUS = {LUser, LServer},
+ case catch mnesia:dirty_select(muc_registered,
+ [{#muc_registered{us_host = '$1',
+ nick = Nick, _ = '_'},
+ [{'==', {element, 2, '$1'}, Host}],
+ ['$_']}])
+ of
+ {'EXIT', _Reason} -> true;
+ [] -> true;
+ [#muc_registered{us_host = {U, _Host}}] -> U == LUS
+ end.
+
+get_rooms(_LServer, Host) ->
+ mnesia:dirty_select(muc_room,
+ [{#muc_room{name_host = {'_', Host},
+ _ = '_'},
+ [], ['$_']}]).
+
+get_nick(_LServer, Host, From) ->
+ {LUser, LServer, _} = jid:tolower(From),
+ LUS = {LUser, LServer},
+ case mnesia:dirty_read(muc_registered, {LUS, Host}) of
+ [] -> error;
+ [#muc_registered{nick = Nick}] -> Nick
+ end.
+
+set_nick(_LServer, Host, From, Nick) ->
+ {LUser, LServer, _} = jid:tolower(From),
+ LUS = {LUser, LServer},
+ F = fun () ->
+ case Nick of
+ <<"">> ->
+ mnesia:delete({muc_registered, {LUS, Host}}),
+ ok;
+ _ ->
+ Allow = case mnesia:select(
+ muc_registered,
+ [{#muc_registered{us_host =
+ '$1',
+ nick = Nick,
+ _ = '_'},
+ [{'==', {element, 2, '$1'},
+ Host}],
+ ['$_']}]) of
+ [] -> true;
+ [#muc_registered{us_host = {U, _Host}}] ->
+ U == LUS
+ end,
+ if Allow ->
+ mnesia:write(#muc_registered{
+ us_host = {LUS, Host},
+ nick = Nick}),
+ ok;
+ true ->
+ false
+ end
+ end
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #muc_room{} = R) ->
+ mnesia:dirty_write(R);
+import(_LServer, #muc_registered{} = R) ->
+ mnesia:dirty_write(R).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_tables(Host) ->
+ update_muc_room_table(Host),
+ update_muc_registered_table(Host).
+
+update_muc_room_table(_Host) ->
+ Fields = record_info(fields, muc_room),
+ case mnesia:table_info(muc_room, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ muc_room, Fields, set,
+ fun(#muc_room{name_host = {N, _}}) -> N end,
+ fun(#muc_room{name_host = {N, H},
+ opts = Opts} = R) ->
+ R#muc_room{name_host = {iolist_to_binary(N),
+ iolist_to_binary(H)},
+ opts = mod_muc:opts_to_binary(Opts)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating muc_room table", []),
+ mnesia:transform_table(muc_room, ignore, Fields)
+ end.
+
+update_muc_registered_table(_Host) ->
+ Fields = record_info(fields, muc_registered),
+ case mnesia:table_info(muc_registered, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ muc_registered, Fields, set,
+ fun(#muc_registered{us_host = {_, H}}) -> H end,
+ fun(#muc_registered{us_host = {{U, S}, H},
+ nick = Nick} = R) ->
+ R#muc_registered{us_host = {{iolist_to_binary(U),
+ iolist_to_binary(S)},
+ iolist_to_binary(H)},
+ nick = iolist_to_binary(Nick)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating muc_registered table", []),
+ mnesia:transform_table(muc_registered, ignore, Fields)
+ end.
diff --git a/src/mod_muc_riak.erl b/src/mod_muc_riak.erl
new file mode 100644
index 000000000..bc6e5959a
--- /dev/null
+++ b/src/mod_muc_riak.erl
@@ -0,0 +1,117 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_muc_riak).
+
+-behaviour(mod_muc).
+
+%% API
+-export([init/2, import/2, store_room/4, restore_room/3, forget_room/3,
+ can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]).
+
+-include("mod_muc.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+store_room(_LServer, Host, Name, Opts) ->
+ {atomic, ejabberd_riak:put(#muc_room{name_host = {Name, Host},
+ opts = Opts},
+ muc_room_schema())}.
+
+restore_room(_LServer, Host, Name) ->
+ case ejabberd_riak:get(muc_room, muc_room_schema(), {Name, Host}) of
+ {ok, #muc_room{opts = Opts}} -> Opts;
+ _ -> error
+ end.
+
+forget_room(_LServer, Host, Name) ->
+ {atomic, ejabberd_riak:delete(muc_room, {Name, Host})}.
+
+can_use_nick(LServer, Host, JID, Nick) ->
+ {LUser, LServer, _} = jid:tolower(JID),
+ LUS = {LUser, LServer},
+ case ejabberd_riak:get_by_index(muc_registered,
+ muc_registered_schema(),
+ <<"nick_host">>, {Nick, Host}) of
+ {ok, []} ->
+ true;
+ {ok, [#muc_registered{us_host = {U, _Host}}]} ->
+ U == LUS;
+ {error, _} ->
+ true
+ end.
+
+get_rooms(_LServer, Host) ->
+ case ejabberd_riak:get(muc_room, muc_room_schema()) of
+ {ok, Rs} ->
+ lists:filter(
+ fun(#muc_room{name_host = {_, H}}) ->
+ Host == H
+ end, Rs);
+ _Err ->
+ []
+ end.
+
+get_nick(LServer, Host, From) ->
+ {LUser, LServer, _} = jid:tolower(From),
+ US = {LUser, LServer},
+ case ejabberd_riak:get(muc_registered,
+ muc_registered_schema(),
+ {US, Host}) of
+ {ok, #muc_registered{nick = Nick}} -> Nick;
+ {error, _} -> error
+ end.
+
+set_nick(LServer, Host, From, Nick) ->
+ {LUser, LServer, _} = jid:tolower(From),
+ LUS = {LUser, LServer},
+ {atomic,
+ case Nick of
+ <<"">> ->
+ ejabberd_riak:delete(muc_registered, {LUS, Host});
+ _ ->
+ Allow = case ejabberd_riak:get_by_index(
+ muc_registered,
+ muc_registered_schema(),
+ <<"nick_host">>, {Nick, Host}) of
+ {ok, []} ->
+ true;
+ {ok, [#muc_registered{us_host = {U, _Host}}]} ->
+ U == LUS;
+ {error, _} ->
+ false
+ end,
+ if Allow ->
+ ejabberd_riak:put(#muc_registered{us_host = {LUS, Host},
+ nick = Nick},
+ muc_registered_schema(),
+ [{'2i', [{<<"nick_host">>,
+ {Nick, Host}}]}]);
+ true ->
+ false
+ end
+ end}.
+
+import(_LServer, #muc_room{} = R) ->
+ ejabberd_riak:put(R, muc_room_schema());
+import(_LServer, #muc_registered{us_host = {_, Host}, nick = Nick} = R) ->
+ ejabberd_riak:put(R, muc_registered_schema(),
+ [{'2i', [{<<"nick_host">>, {Nick, Host}}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+muc_room_schema() ->
+ {record_info(fields, muc_room), #muc_room{}}.
+
+muc_registered_schema() ->
+ {record_info(fields, muc_registered), #muc_registered{}}.
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index d19bc5f3d..c9c785757 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -143,12 +143,12 @@ normal_state({route, From, <<"">>,
children = Els} =
Packet},
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
is_user_allowed_message_nonparticipant(From, StateData)
of
true ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"groupchat">> ->
Activity = get_user_activity(From, StateData),
Now = p1_time_compat:system_time(micro_seconds),
@@ -252,7 +252,7 @@ normal_state({route, From, <<"">>,
IsVoiceApprovement = is_voice_approvement(Els) and
not is_visitor(From, StateData),
if IsInvitation ->
- case catch check_invitation(From, Els, Lang, StateData)
+ case catch check_invitation(From, Packet, Lang, StateData)
of
{error, Error} ->
Err = jlib:make_error_reply(Packet, Error),
@@ -394,7 +394,7 @@ normal_state({route, From, <<"">>,
{next_state, normal_state, StateData}
end;
_ ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
_ ->
handle_roommessage_from_nonparticipant(Packet, Lang,
@@ -432,10 +432,12 @@ normal_state({route, From, <<"">>,
?NS_MUC_OWNER ->
process_iq_owner(From, Type, Lang, SubEl, StateData);
?NS_DISCO_INFO ->
- case xml:get_attr(<<"node">>, Attrs) of
- false -> process_iq_disco_info(From, Type, Lang, StateData);
- {value, _} -> {error, ?ERR_SERVICE_UNAVAILABLE}
- end;
+ 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 ->
@@ -509,8 +511,8 @@ normal_state({route, From, Nick,
normal_state({route, From, ToNick,
#xmlel{name = <<"message">>, attrs = Attrs} = Packet},
StateData) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case decide_fate_message(Type, Packet, From, StateData)
of
{expulse_sender, Reason} ->
@@ -568,7 +570,7 @@ normal_state({route, From, ToNick,
FromNick),
X = #xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_MUC_USER}]},
- PrivMsg = xml:append_subtags(Packet, [X]),
+ PrivMsg = fxml:append_subtags(Packet, [X]),
[ejabberd_router:route(FromNickJID, ToJID, PrivMsg)
|| ToJID <- ToJIDs];
true ->
@@ -607,8 +609,8 @@ normal_state({route, From, ToNick,
normal_state({route, From, ToNick,
#xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
- StanzaId = xml:get_attr_s(<<"id">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ StanzaId = fxml:get_attr_s(<<"id">>, Attrs),
case {(StateData#state.config)#config.allow_query_users,
is_user_online_iq(StanzaId, From, StateData)}
of
@@ -817,8 +819,9 @@ handle_info({captcha_failed, From}, normal_state,
of
{ok, {Nick, Packet}} ->
Robots = (?DICT):erase(From, StateData#state.robots),
- Err = jlib:make_error_reply(Packet,
- ?ERR_NOT_AUTHORIZED),
+ 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),
@@ -884,7 +887,7 @@ route(Pid, From, ToNick, Packet) ->
process_groupchat_message(From,
#xmlel{name = <<"message">>, attrs = Attrs} = Packet,
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
is_user_allowed_message_nonparticipant(From, StateData)
of
@@ -936,7 +939,7 @@ process_groupchat_message(From,
drop ->
{next_state, normal_state, StateData};
NewPacket1 ->
- NewPacket = xml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}),
+ NewPacket = fxml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}),
send_multiple(jid:replace_resource(StateData#state.jid,
FromNick),
StateData#state.server_host,
@@ -1016,7 +1019,7 @@ get_participant_data(From, StateData) ->
process_presence(From, Nick,
#xmlel{name = <<"presence">>, attrs = Attrs0} = Packet0,
StateData) ->
- Type0 = xml:get_attr_s(<<"type">>, Attrs0),
+ Type0 = fxml:get_attr_s(<<"type">>, Attrs0),
IsOnline = is_user_online(From, StateData),
if Type0 == <<"">>;
IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) ->
@@ -1029,8 +1032,8 @@ process_presence(From, Nick,
drop ->
{next_state, normal_state, StateData};
#xmlel{attrs = Attrs} = Packet ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
StateData1 = case Type of
<<"unavailable">> ->
NewPacket = case
@@ -1047,12 +1050,12 @@ process_presence(From, Nick,
{ok, [_, _ | _]} -> ok;
_ -> send_new_presence(From, NewState, StateData)
end,
- Reason = case xml:get_subtag(NewPacket,
+ Reason = case fxml:get_subtag(NewPacket,
<<"status">>)
of
false -> <<"">>;
Status_el ->
- xml:get_tag_cdata(Status_el)
+ fxml:get_tag_cdata(Status_el)
end,
remove_online_user(From, NewState, Reason);
<<"error">> ->
@@ -1087,7 +1090,7 @@ process_presence(From, Nick,
From, Err),
StateData;
{true, _, _} ->
- Lang = xml:get_attr_s(<<"xml:lang">>,
+ Lang = fxml:get_attr_s(<<"xml:lang">>,
Attrs),
ErrText =
<<"That nickname is already in use by another "
@@ -1304,7 +1307,7 @@ get_error_condition(Packet) ->
end.
get_error_condition2(Packet) ->
- #xmlel{children = EEls} = xml:get_subtag(Packet,
+ #xmlel{children = EEls} = fxml:get_subtag(Packet,
<<"error">>),
[Condition] = [Name
|| #xmlel{name = Name,
@@ -1655,7 +1658,7 @@ filter_presence(#xmlel{name = <<"presence">>,
case El of
{xmlcdata, _} -> false;
#xmlel{attrs = Attrs1} ->
- XMLNS = xml:get_attr_s(<<"xmlns">>,
+ XMLNS = fxml:get_attr_s(<<"xmlns">>,
Attrs1),
NS_MUC = ?NS_MUC,
Size = byte_size(NS_MUC),
@@ -1743,11 +1746,11 @@ higher_presence(Pres1, Pres2) ->
Pri1 > Pri2.
get_priority_from_presence(PresencePacket) ->
- case xml:get_subtag(PresencePacket, <<"priority">>) of
+ case fxml:get_subtag(PresencePacket, <<"priority">>) of
false -> 0;
SubEl ->
case catch
- jlib:binary_to_integer(xml:get_tag_cdata(SubEl))
+ jlib:binary_to_integer(fxml:get_tag_cdata(SubEl))
of
P when is_integer(P) -> P;
_ -> 0
@@ -1781,7 +1784,7 @@ nick_collision(User, Nick, StateData) ->
add_new_user(From, Nick,
#xmlel{attrs = Attrs, children = Els} = Packet,
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
MaxUsers = get_max_users(StateData),
MaxAdminUsers = MaxUsers +
get_max_users_admin_threshold(StateData),
@@ -1807,6 +1810,22 @@ add_new_user(From, Nick,
StateData#state.host, From, Nick),
get_default_role(Affiliation, StateData)}
of
+ {false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers ->
+ Txt = <<"Too many users in this conference">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jid:replace_resource(StateData#state.jid, Nick),
+ From, Err),
+ StateData;
+ {false, _, _, _} when NConferences >= MaxConferences ->
+ Txt = <<"You have joined too many conferences">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jid:replace_resource(StateData#state.jid, Nick),
+ From, Err),
+ StateData;
{false, _, _, _} ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
@@ -1856,7 +1875,7 @@ add_new_user(From, Nick,
add_online_user(From, Nick, Role,
StateData)),
send_existing_presences(From, NewState),
- send_new_presence(From, NewState, StateData),
+ send_initial_presence(From, NewState, StateData),
Shift = count_stanza_shift(Nick, Els, NewState),
case send_history(From, Shift, NewState) of
true -> ok;
@@ -1879,7 +1898,7 @@ add_new_user(From, Nick,
From, Err),
StateData;
captcha_required ->
- SID = xml:get_attr_s(<<"id">>, Attrs),
+ SID = fxml:get_attr_s(<<"id">>, Attrs),
RoomJID = StateData#state.jid,
To = jid:replace_resource(RoomJID, Nick),
Limiter = {From#jid.luser, From#jid.lserver},
@@ -1979,11 +1998,11 @@ check_captcha(Affiliation, From, StateData) ->
extract_password([]) -> false;
extract_password([#xmlel{attrs = Attrs} = El | Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC ->
- case xml:get_subtag(El, <<"password">>) of
+ case fxml:get_subtag(El, <<"password">>) of
false -> false;
- SubEl -> xml:get_tag_cdata(SubEl)
+ SubEl -> fxml:get_tag_cdata(SubEl)
end;
_ -> extract_password(Els)
end;
@@ -2057,9 +2076,9 @@ calc_shift(MaxSize, Size, Shift, [S | TSizes]) ->
extract_history([], _Type) -> false;
extract_history([#xmlel{attrs = Attrs} = El | Els],
Type) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC ->
- AttrVal = xml:get_path_s(El,
+ AttrVal = fxml:get_path_s(El,
[{elem, <<"history">>}, {attr, Type}]),
case Type of
<<"since">> ->
@@ -2090,6 +2109,9 @@ presence_broadcast_allowed(JID, StateData) ->
Role = get_role(JID, StateData),
lists:member(Role, (StateData#state.config)#config.presence_broadcast).
+send_initial_presence(NJID, StateData, OldStateData) ->
+ send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
+
send_update_presence(JID, StateData, OldStateData) ->
send_update_presence(JID, <<"">>, StateData, OldStateData).
@@ -2117,20 +2139,25 @@ send_update_presence1(JID, Reason, StateData, OldStateData) ->
end
end,
lists:foreach(fun (J) ->
- send_new_presence(J, Reason, StateData, OldStateData)
+ send_new_presence1(J, Reason, false, StateData,
+ OldStateData)
end,
LJIDs).
send_new_presence(NJID, StateData, OldStateData) ->
- send_new_presence(NJID, <<"">>, StateData, OldStateData).
+ send_new_presence(NJID, <<"">>, false, StateData, OldStateData).
send_new_presence(NJID, Reason, StateData, OldStateData) ->
+ send_new_presence(NJID, Reason, false, StateData, OldStateData).
+
+send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
case is_room_overcrowded(StateData) of
true -> ok;
- false -> send_new_presence1(NJID, Reason, StateData, OldStateData)
+ false -> send_new_presence1(NJID, Reason, IsInitialPresence, StateData,
+ OldStateData)
end.
-send_new_presence1(NJID, Reason, StateData, OldStateData) ->
+send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
LNJID = jid:tolower(NJID),
#user{nick = Nick} = (?DICT):fetch(LNJID, StateData#state.users),
LJID = find_jid_by_nick(Nick, StateData),
@@ -2187,46 +2214,9 @@ send_new_presence1(NJID, Reason, StateData, OldStateData) ->
children =
[{xmlcdata, Reason}]}]
end,
- Status = case StateData#state.just_created of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"201">>}],
- children = []}];
- false -> []
- end,
- Status2 = case
- (StateData#state.config)#config.anonymous
- == false
- andalso NJID == Info#user.jid
- of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"100">>}],
- children = []}
- | Status];
- false -> Status
- end,
- Status3 = case NJID == Info#user.jid of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"110">>}],
- children = []}
- | Status2];
- false -> Status2
- end,
- Status4 = case (StateData#state.config)#config.logging of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"170">>}],
- children = []}
- | Status3];
- false -> Status3
- end,
- Packet = xml:append_subtags(Presence,
+ StatusEls = status_els(IsInitialPresence, NJID, Info,
+ StateData),
+ Packet = fxml:append_subtags(Presence,
[#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>,
@@ -2240,7 +2230,7 @@ send_new_presence1(NJID, Reason, StateData, OldStateData) ->
children
=
ItemEls}
- | Status4]}]),
+ | StatusEls]}]),
ejabberd_router:route(jid:replace_resource(StateData#state.jid,
Nick),
Info#user.jid, Packet)
@@ -2289,7 +2279,7 @@ send_existing_presences1(ToJID, StateData) ->
{<<"role">>,
role_to_list(FromRole)}]
end,
- Packet = xml:append_subtags(Presence,
+ Packet = fxml:append_subtags(Presence,
[#xmlel{name =
<<"x">>,
attrs =
@@ -2420,7 +2410,7 @@ send_nick_changing(JID, OldNick, StateData,
<<"303">>}],
children =
[]}|Status110]}]},
- Packet2 = xml:append_subtags(Presence,
+ Packet2 = fxml:append_subtags(Presence,
[#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>,
@@ -2450,6 +2440,40 @@ send_nick_changing(JID, OldNick, StateData,
end,
(?DICT):to_list(StateData#state.users)).
+status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) ->
+ Status = case IsInitialPresence of
+ true ->
+ S1 = case StateData#state.just_created of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"201">>}],
+ children = []}];
+ false -> []
+ end,
+ S2 = case (StateData#state.config)#config.anonymous of
+ true -> S1;
+ false ->
+ [#xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"100">>}],
+ children = []} | S1]
+ end,
+ S3 = case (StateData#state.config)#config.logging of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"170">>}],
+ children = []} | S2];
+ false -> S2
+ end,
+ S3;
+ false -> []
+ end,
+ [#xmlel{name = <<"status">>,
+ attrs =
+ [{<<"code">>,
+ <<"110">>}],
+ children = []} | Status];
+status_els(_IsInitialPresence, _JID, _Info, _StateData) -> [].
+
lqueue_new(Max) ->
#lqueue{queue = queue:new(), len = 0, max = Max}.
@@ -2474,7 +2498,7 @@ lqueue_to_list(#lqueue{queue = Q1}) ->
add_message_to_history(FromNick, FromJID, Packet, StateData) ->
- HaveSubject = case xml:get_subtag(Packet, <<"subject">>)
+ HaveSubject = case fxml:get_subtag(Packet, <<"subject">>)
of
false -> false;
_ -> true
@@ -2491,7 +2515,7 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) ->
Addresses = #xmlel{name = <<"addresses">>,
attrs = [{<<"xmlns">>, ?NS_ADDRESS}],
children = [Address]},
- xml:append_subtags(Packet, [Addresses])
+ fxml:append_subtags(Packet, [Addresses])
end,
TSPacket = jlib:add_delay_info(AddrPacket, StateData#state.jid, TimeStamp),
SPacket =
@@ -2530,9 +2554,9 @@ send_subject(JID, #state{subject_author = Nick} = StateData) ->
Packet).
check_subject(Packet) ->
- case xml:get_subtag(Packet, <<"subject">>) of
+ case fxml:get_subtag(Packet, <<"subject">>) of
false -> false;
- SubjEl -> xml:get_tag_cdata(SubjEl)
+ SubjEl -> fxml:get_tag_cdata(SubjEl)
end.
can_change_subject(Role, StateData) ->
@@ -2549,22 +2573,27 @@ process_iq_admin(From, set, Lang, SubEl, StateData) ->
#xmlel{children = Items} = SubEl,
process_admin_items_set(From, Items, Lang, StateData);
process_iq_admin(From, get, Lang, SubEl, StateData) ->
- case xml:get_subtag(SubEl, <<"item">>) of
- false -> {error, ?ERR_BAD_REQUEST};
+ 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 xml:get_tag_attr(<<"role">>, Item) of
+ case fxml:get_tag_attr(<<"role">>, Item) of
false ->
- case xml:get_tag_attr(<<"affiliation">>, Item) of
- false -> {error, ?ERR_BAD_REQUEST};
+ 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 (SAffiliation == member)) ->
+ ((FAffiliation == member) and not
+ (StateData#state.config)#config.anonymous) ->
Items = items_with_affiliation(SAffiliation,
StateData),
{result, Items, StateData};
@@ -2577,7 +2606,9 @@ process_iq_admin(From, get, Lang, SubEl, StateData) ->
end;
{value, StrRole} ->
case catch list_to_role(StrRole) of
- {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ {'EXIT', _} ->
+ Txt = <<"Incorrect value of 'role' attribute">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
SRole ->
if FRole == moderator ->
Items = items_with_role(SRole, StateData),
@@ -2748,7 +2779,7 @@ find_changed_items(UJID, UAffiliation, URole,
[#xmlel{name = <<"item">>, attrs = Attrs} = Item
| Items],
Lang, StateData, Res) ->
- TJID = case xml:get_attr(<<"jid">>, Attrs) of
+ TJID = case fxml:get_attr(<<"jid">>, Attrs) of
{value, S} ->
case jid:from_string(S) of
error ->
@@ -2761,7 +2792,7 @@ find_changed_items(UJID, UAffiliation, URole,
J -> {value, [J]}
end;
_ ->
- case xml:get_attr(<<"nick">>, Attrs) of
+ case fxml:get_attr(<<"nick">>, Attrs) of
{value, N} ->
case find_jids_by_nick(N, StateData) of
false ->
@@ -2774,17 +2805,21 @@ find_changed_items(UJID, UAffiliation, URole,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
J -> {value, J}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ 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 xml:get_attr(<<"role">>, Attrs) of
+ case fxml:get_attr(<<"role">>, Attrs) of
false ->
- case xml:get_attr(<<"affiliation">>, Attrs) of
- false -> {error, ?ERR_BAD_REQUEST};
+ 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', _} ->
@@ -2824,7 +2859,7 @@ find_changed_items(UJID, UAffiliation, URole,
Items, Lang, StateData,
Res);
true ->
- Reason = xml:get_path_s(Item,
+ Reason = fxml:get_path_s(Item,
[{elem, <<"reason">>},
cdata]),
MoreRes = [{jid:remove_resource(Jidx),
@@ -2833,7 +2868,9 @@ find_changed_items(UJID, UAffiliation, URole,
find_changed_items(UJID, UAffiliation, URole,
Items, Lang, StateData,
[MoreRes | Res]);
- false -> {error, ?ERR_NOT_ALLOWED}
+ false ->
+ Txt3 = <<"Changing role/affiliation is not allowed">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt3)}
end
end
end;
@@ -2871,7 +2908,7 @@ find_changed_items(UJID, UAffiliation, URole,
find_changed_items(UJID, UAffiliation, URole, Items,
Lang, StateData, Res);
true ->
- Reason = xml:get_path_s(Item,
+ Reason = fxml:get_path_s(Item,
[{elem, <<"reason">>},
cdata]),
MoreRes = [{Jidx, role, SRole, Reason}
@@ -2879,7 +2916,9 @@ find_changed_items(UJID, UAffiliation, URole,
find_changed_items(UJID, UAffiliation, URole, Items,
Lang, StateData,
[MoreRes | Res]);
- _ -> {error, ?ERR_NOT_ALLOWED}
+ _ ->
+ Txt4 = <<"Changing role/affiliation is not allowed">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt4)}
end
end
end;
@@ -3123,10 +3162,10 @@ process_iq_owner(From, set, Lang, SubEl, StateData) ->
case FAffiliation of
owner ->
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>} = XEl] ->
- case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
- xml:get_tag_attr_s(<<"type">>, XEl)}
+ case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
+ fxml:get_tag_attr_s(<<"type">>, XEl)}
of
{?NS_XDATA, <<"cancel">>} -> {result, [], StateData};
{?NS_XDATA, <<"submit">>} ->
@@ -3137,10 +3176,12 @@ process_iq_owner(From, set, Lang, SubEl, StateData) ->
andalso
is_password_settings_correct(XEl, StateData)
of
- true -> set_config(XEl, StateData);
+ true -> set_config(XEl, StateData, Lang);
false -> {error, ?ERR_NOT_ACCEPTABLE}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ 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",
@@ -3160,11 +3201,13 @@ process_iq_owner(From, get, Lang, SubEl, StateData) ->
case FAffiliation of
owner ->
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[] -> get_config(Lang, StateData, From);
[Item] ->
- case xml:get_tag_attr(<<"affiliation">>, Item) of
- false -> {error, ?ERR_BAD_REQUEST};
+ 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', _} ->
@@ -3636,10 +3679,10 @@ get_config(Lang, StateData, From) ->
children = Res}],
StateData}.
-set_config(XEl, StateData) ->
+set_config(XEl, StateData, Lang) ->
XData = jlib:parse_xdata_submit(XEl),
case XData of
- invalid -> {error, ?ERR_BAD_REQUEST};
+ invalid -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
_ ->
case set_xoption(XData, StateData#state.config) of
#config{} = Config ->
@@ -3669,14 +3712,20 @@ set_config(XEl, StateData) ->
<<"1">> -> set_xoption(Opts, Config#config{Opt = true});
<<"true">> ->
set_xoption(Opts, Config#config{Opt = true});
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Value of '~s' should be boolean">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, ErrTxt)}
end).
-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});
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, ErrTxt)}
end).
-define(SET_STRING_XOPT(Opt, Val),
@@ -3729,7 +3778,10 @@ set_xoption([{<<"allow_private_messages_from_visitors">>,
<<"nobody">> ->
?SET_STRING_XOPT(allow_private_messages_from_visitors,
nobody);
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Value of 'allow_private_messages_from_visitors' "
+ "should be anyone|moderators|nobody">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)}
end;
set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>,
[Val]}
@@ -3797,7 +3849,10 @@ set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts],
end
end, {false, false, false}, Vals),
case Roles of
- error -> {error, ?ERR_BAD_REQUEST};
+ error ->
+ Txt = <<"Value of 'muc#roomconfig_presencebroadcast' should "
+ "be moderator|participant|visitor">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)};
{M, P, V} ->
Res =
if M -> [moderator]; true -> [] end ++
@@ -3825,7 +3880,10 @@ set_xoption([{<<"muc#roomconfig_whois">>, [Val]}
<<"anyone">> ->
?SET_BOOL_XOPT(anonymous,
(iolist_to_binary(integer_to_list(0))));
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Value of 'muc#roomconfig_whois' should be "
+ "moderators|anyone">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)}
end;
set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]}
| Opts],
@@ -3848,8 +3906,10 @@ set_xoption([{<<"muc#roomconfig_captcha_whitelist">>,
?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs);
set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config) ->
set_xoption(Opts, Config);
-set_xoption([_ | _Opts], _Config) ->
- {error, ?ERR_BAD_REQUEST}.
+set_xoption([{Opt, _Vals} | _Opts], _Config) ->
+ Txt = <<"Unknown option '~s'">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, ErrTxt)}.
change_config(Config, StateData) ->
send_config_change_info(Config, StateData),
@@ -4122,8 +4182,9 @@ destroy_room(DEl, StateData) ->
false -> ?FEATURE(Fiffalse)
end).
-process_iq_disco_info(_From, set, _Lang, _StateData) ->
- {error, ?ERR_NOT_ALLOWED};
+process_iq_disco_info(_From, set, 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) ->
Config = StateData#state.config,
{result,
@@ -4193,9 +4254,10 @@ iq_disco_info_extras(Lang, StateData) ->
<<"muc#roominfo_occupants">>,
(iolist_to_binary(integer_to_list(Len))))]}].
-process_iq_disco_items(_From, set, _Lang, _StateData) ->
- {error, ?ERR_NOT_ALLOWED};
-process_iq_disco_items(From, get, _Lang, StateData) ->
+process_iq_disco_items(_From, set, 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) ->
case (StateData#state.config)#config.public_list of
true ->
{result, get_mucroom_disco_items(StateData), StateData};
@@ -4203,23 +4265,31 @@ process_iq_disco_items(From, get, _Lang, StateData) ->
case is_occupant_or_admin(From, StateData) of
true ->
{result, get_mucroom_disco_items(StateData), StateData};
- _ -> {error, ?ERR_FORBIDDEN}
+ _ ->
+ Txt = <<"Only occupants or administrators can perform this query">>,
+ {error, ?ERRT_FORBIDDEN(Lang, Txt)}
end
end.
-process_iq_captcha(_From, get, _Lang, _SubEl,
+process_iq_captcha(_From, get, Lang, _SubEl,
_StateData) ->
- {error, ?ERR_NOT_ALLOWED};
-process_iq_captcha(_From, set, _Lang, SubEl,
+ Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
+process_iq_captcha(_From, set, Lang, SubEl,
StateData) ->
case ejabberd_captcha:process_reply(SubEl) of
ok -> {result, [], StateData};
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ {error, malformed} ->
+ Txt = <<"Incorrect CAPTCHA submit">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
+ _ ->
+ Txt = <<"The CAPTCHA verification has failed">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end.
process_iq_vcard(_From, get, _Lang, _SubEl, StateData) ->
#state{config = #config{vcard = VCardRaw}} = StateData,
- case xml_stream:parse_element(VCardRaw) of
+ case fxml_stream:parse_element(VCardRaw) of
#xmlel{children = VCardEls} ->
{result, VCardEls, StateData};
{error, _} ->
@@ -4228,7 +4298,7 @@ process_iq_vcard(_From, get, _Lang, _SubEl, StateData) ->
process_iq_vcard(From, set, Lang, SubEl, StateData) ->
case get_affiliation(From, StateData) of
owner ->
- VCardRaw = xml:element_to_binary(SubEl),
+ VCardRaw = fxml:element_to_binary(SubEl),
Config = StateData#state.config,
NewConfig = Config#config{vcard = VCardRaw},
change_config(NewConfig, StateData);
@@ -4287,7 +4357,7 @@ is_voice_request(Els) ->
lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
El,
false) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
case jlib:parse_xdata_submit(El) of
[_ | _] = Fields ->
@@ -4370,7 +4440,7 @@ is_voice_approvement(Els) ->
lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
El,
false) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
case jlib:parse_xdata_submit(El) of
[_ | _] = Fs ->
@@ -4424,9 +4494,9 @@ is_invitation(Els) ->
lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
El,
false) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC_USER ->
- case xml:get_subtag(El, <<"invite">>) of
+ case fxml:get_subtag(El, <<"invite">>) of
false -> false;
_ -> true
end;
@@ -4436,37 +4506,42 @@ is_invitation(Els) ->
end,
false, Els).
-check_invitation(From, Els, Lang, StateData) ->
+check_invitation(From, Packet, Lang, StateData) ->
FAffiliation = get_affiliation(From, StateData),
CanInvite =
(StateData#state.config)#config.allow_user_invites
orelse
FAffiliation == admin orelse FAffiliation == owner,
- InviteEl = case xml:remove_cdata(Els) of
- [#xmlel{name = <<"x">>, children = Els1} = XEl] ->
- case xml:get_tag_attr_s(<<"xmlns">>, XEl) of
- ?NS_MUC_USER -> ok;
- _ -> throw({error, ?ERR_BAD_REQUEST})
- end,
- case xml:remove_cdata(Els1) of
- [#xmlel{name = <<"invite">>} = InviteEl1] -> InviteEl1;
- _ -> throw({error, ?ERR_BAD_REQUEST})
- end;
- _ -> throw({error, ?ERR_BAD_REQUEST})
+ InviteEl = 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_subtag(XEl, <<"invite">>) of
+ false ->
+ Txt2 = <<"No 'invite' element found">>,
+ throw({error, ?ERRT_BAD_REQUEST(Lang, Txt2)});
+ InviteEl1 ->
+ InviteEl1
+ end
end,
JID = case
- jid:from_string(xml:get_tag_attr_s(<<"to">>,
+ jid:from_string(fxml:get_tag_attr_s(<<"to">>,
InviteEl))
of
- error -> throw({error, ?ERR_JID_MALFORMED});
+ error ->
+ Txt = <<"Incorrect value of 'to' attribute">>,
+ throw({error, ?ERRT_JID_MALFORMED(Lang, Txt)});
JID1 -> JID1
end,
case CanInvite of
- false -> throw({error, ?ERR_NOT_ALLOWED});
+ false ->
+ Txt3 = <<"Invitations are not allowed in this conference">>,
+ throw({error, ?ERRT_NOT_ALLOWED(Lang, Txt3)});
true ->
- Reason = xml:get_path_s(InviteEl,
+ Reason = fxml:get_path_s(InviteEl,
[{elem, <<"reason">>}, cdata]),
- ContinueEl = case xml:get_path_s(InviteEl,
+ ContinueEl = case fxml:get_path_s(InviteEl,
[{elem, <<"continue">>}])
of
<<>> -> [];
@@ -4556,10 +4631,10 @@ handle_roommessage_from_nonparticipant(Packet, Lang,
%% because it crashes when the packet is not a decline message.
check_decline_invitation(Packet) ->
#xmlel{name = <<"message">>} = Packet,
- XEl = xml:get_subtag(Packet, <<"x">>),
- (?NS_MUC_USER) = xml:get_tag_attr_s(<<"xmlns">>, XEl),
- DEl = xml:get_subtag(XEl, <<"decline">>),
- ToString = xml:get_tag_attr_s(<<"to">>, DEl),
+ 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}}.
@@ -4646,7 +4721,7 @@ tab_count_user(JID) ->
end.
element_size(El) ->
- byte_size(xml:element_to_binary(El)).
+ byte_size(fxml:element_to_binary(El)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Multicast
diff --git a/src/mod_muc_sql.erl b/src/mod_muc_sql.erl
new file mode 100644
index 000000000..55628d43e
--- /dev/null
+++ b/src/mod_muc_sql.erl
@@ -0,0 +1,202 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_muc_sql).
+
+-behaviour(mod_muc).
+
+%% 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]).
+
+-include("jlib.hrl").
+-include("mod_muc.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+store_room(LServer, Host, Name, Opts) ->
+ SName = ejabberd_sql:escape(Name),
+ SHost = ejabberd_sql:escape(Host),
+ SOpts = ejabberd_sql:encode_term(Opts),
+ F = fun () ->
+ sql_queries:update_t(<<"muc_room">>,
+ [<<"name">>, <<"host">>, <<"opts">>],
+ [SName, SHost, SOpts],
+ [<<"name='">>, SName, <<"' and host='">>,
+ SHost, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+restore_room(LServer, Host, Name) ->
+ SName = ejabberd_sql:escape(Name),
+ SHost = ejabberd_sql:escape(Host),
+ case catch ejabberd_sql:sql_query(LServer,
+ [<<"select opts from muc_room where name='">>,
+ SName, <<"' and host='">>, SHost,
+ <<"';">>]) of
+ {selected, [<<"opts">>], [[Opts]]} ->
+ mod_muc:opts_to_binary(ejabberd_sql:decode_term(Opts));
+ _ ->
+ error
+ end.
+
+forget_room(LServer, Host, Name) ->
+ SName = ejabberd_sql:escape(Name),
+ SHost = ejabberd_sql:escape(Host),
+ F = fun () ->
+ ejabberd_sql:sql_query_t([<<"delete from muc_room where name='">>,
+ SName, <<"' and host='">>, SHost,
+ <<"';">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+can_use_nick(LServer, Host, JID, Nick) ->
+ SJID = jid:to_string(jid:tolower(jid:remove_resource(JID))),
+ SNick = ejabberd_sql:escape(Nick),
+ SHost = ejabberd_sql:escape(Host),
+ case catch ejabberd_sql:sql_query(LServer,
+ [<<"select jid from muc_registered ">>,
+ <<"where nick='">>, SNick,
+ <<"' and host='">>, SHost, <<"';">>]) of
+ {selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1;
+ _ -> true
+ end.
+
+get_rooms(LServer, Host) ->
+ SHost = ejabberd_sql:escape(Host),
+ case catch ejabberd_sql:sql_query(LServer,
+ [<<"select name, opts from muc_room ">>,
+ <<"where host='">>, SHost, <<"';">>]) of
+ {selected, [<<"name">>, <<"opts">>], RoomOpts} ->
+ lists:map(
+ fun([Room, Opts]) ->
+ #muc_room{name_host = {Room, Host},
+ opts = mod_muc:opts_to_binary(
+ ejabberd_sql:decode_term(Opts))}
+ end, RoomOpts);
+ Err ->
+ ?ERROR_MSG("failed to get rooms: ~p", [Err]),
+ []
+ end.
+
+get_nick(LServer, Host, From) ->
+ SJID = ejabberd_sql:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
+ SHost = ejabberd_sql:escape(Host),
+ case catch ejabberd_sql:sql_query(LServer,
+ [<<"select nick from muc_registered where "
+ "jid='">>,
+ SJID, <<"' and host='">>, SHost,
+ <<"';">>]) of
+ {selected, [<<"nick">>], [[Nick]]} -> Nick;
+ _ -> error
+ end.
+
+set_nick(LServer, Host, From, Nick) ->
+ JID = jid:to_string(jid:tolower(jid:remove_resource(From))),
+ SJID = ejabberd_sql:escape(JID),
+ SNick = ejabberd_sql:escape(Nick),
+ SHost = ejabberd_sql:escape(Host),
+ F = fun () ->
+ case Nick of
+ <<"">> ->
+ ejabberd_sql:sql_query_t(
+ [<<"delete from muc_registered where ">>,
+ <<"jid='">>, SJID,
+ <<"' and host='">>, Host,
+ <<"';">>]),
+ ok;
+ _ ->
+ Allow = case ejabberd_sql:sql_query_t(
+ [<<"select jid from muc_registered ">>,
+ <<"where nick='">>,
+ SNick,
+ <<"' and host='">>,
+ SHost, <<"';">>]) of
+ {selected, [<<"jid">>], [[J]]} -> J == JID;
+ _ -> true
+ end,
+ if Allow ->
+ sql_queries:update_t(<<"muc_registered">>,
+ [<<"jid">>, <<"host">>,
+ <<"nick">>],
+ [SJID, SHost, SNick],
+ [<<"jid='">>, SJID,
+ <<"' and host='">>, SHost,
+ <<"'">>]),
+ ok;
+ true ->
+ false
+ end
+ end
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+export(_Server) ->
+ [{muc_room,
+ fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
+ case str:suffix(Host, RoomHost) of
+ true ->
+ SName = ejabberd_sql:escape(Name),
+ SRoomHost = ejabberd_sql:escape(RoomHost),
+ SOpts = ejabberd_sql:encode_term(Opts),
+ [[<<"delete from muc_room where name='">>, SName,
+ <<"' and host='">>, SRoomHost, <<"';">>],
+ [<<"insert into muc_room(name, host, opts) ",
+ "values (">>,
+ <<"'">>, SName, <<"', '">>, SRoomHost,
+ <<"', '">>, SOpts, <<"');">>]];
+ false ->
+ []
+ end
+ end},
+ {muc_registered,
+ fun(Host, #muc_registered{us_host = {{U, S}, RoomHost},
+ nick = Nick}) ->
+ case str:suffix(Host, RoomHost) of
+ true ->
+ SJID = ejabberd_sql:escape(
+ jid:to_string(
+ jid:make(U, S, <<"">>))),
+ SNick = ejabberd_sql:escape(Nick),
+ SRoomHost = ejabberd_sql:escape(RoomHost),
+ [[<<"delete from muc_registered where jid='">>,
+ SJID, <<"' and host='">>, SRoomHost, <<"';">>],
+ [<<"insert into muc_registered(jid, host, "
+ "nick) values ('">>,
+ SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
+ <<"');">>]];
+ false ->
+ []
+ 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.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl
index 96ebcb6f2..83520c0be 100644
--- a/src/mod_multicast.erl
+++ b/src/mod_multicast.erl
@@ -151,7 +151,7 @@ init([LServerS, Opts]) ->
try_start_loop(),
create_pool(),
ejabberd_router_multicast:register_route(LServerS),
- ejabberd_router:register_route(LServiceS),
+ ejabberd_router:register_route(LServiceS, LServerS),
{ok,
#state{lservice = LServiceS, lserver = LServerS,
access = Access, service_limits = SLimits}}.
@@ -233,7 +233,7 @@ handle_iq(From, To, #xmlel{attrs = Attrs} = Packet, State) ->
ejabberd_router:route(To, From, Err);
reply ->
LServiceS = jts(To),
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"result">> ->
process_iqreply_result(From, LServiceS, Packet, State);
<<"error">> ->
@@ -395,7 +395,7 @@ act_groups(FromJID, Packet_stripped, AAttrs, LServiceS,
perform(From, Packet, AAttrs, _,
{route_single, Group}) ->
[route_packet(From, ToUser, Packet, AAttrs,
- Group#group.addresses)
+ Group#group.others, Group#group.addresses)
|| ToUser <- Group#group.dests];
perform(From, Packet, AAttrs, _,
{{route_multicast, JID, RLimits}, Group}) ->
@@ -436,17 +436,17 @@ check_access(LServerS, Access, From) ->
%%%-------------------------
strip_addresses_element(Packet) ->
- case xml:get_subtag(Packet, <<"addresses">>) of
+ case fxml:get_subtag(Packet, <<"addresses">>) of
#xmlel{name = <<"addresses">>, attrs = AAttrs,
children = Addresses} ->
- case xml:get_attr_s(<<"xmlns">>, AAttrs) of
+ case fxml:get_attr_s(<<"xmlns">>, AAttrs) of
?NS_ADDRESS ->
#xmlel{name = Name, attrs = Attrs, children = Els} =
Packet,
Els_stripped = lists:keydelete(<<"addresses">>, 2, Els),
Packet_stripped = #xmlel{name = Name, attrs = Attrs,
children = Els_stripped},
- {ok, Packet_stripped, AAttrs, xml:remove_cdata(Addresses)};
+ {ok, Packet_stripped, AAttrs, fxml:remove_cdata(Addresses)};
_ -> throw(ewxmlns)
end;
_ -> throw(eadsele)
@@ -460,10 +460,10 @@ split_addresses_todeliver(Addresses) ->
lists:partition(fun (XML) ->
case XML of
#xmlel{name = <<"address">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"delivered">>, Attrs) of
+ case fxml:get_attr_s(<<"delivered">>, Attrs) of
<<"true">> -> false;
_ ->
- Type = xml:get_attr_s(<<"type">>,
+ Type = fxml:get_attr_s(<<"type">>,
Attrs),
case Type of
<<"to">> -> true;
@@ -499,10 +499,10 @@ check_limit_dests(SLimits, FromJID, Packet,
convert_dest_record(XMLs) ->
lists:map(fun (XML) ->
- case xml:get_tag_attr_s(<<"jid">>, XML) of
+ case fxml:get_tag_attr_s(<<"jid">>, XML) of
<<"">> -> #dest{jid_string = none, full_xml = XML};
JIDS ->
- Type = xml:get_tag_attr_s(<<"type">>, XML),
+ Type = fxml:get_tag_attr_s(<<"type">>, XML),
JIDJ = stj(JIDS),
#dest{jid_string = JIDS, jid_jid = JIDJ,
type = Type, full_xml = XML}
@@ -525,7 +525,7 @@ split_dests_jid(Dests) ->
Dests).
report_not_jid(From, Packet, Dests) ->
- Dests2 = [xml:element_to_binary(Dest#dest.full_xml)
+ Dests2 = [fxml:element_to_binary(Dest#dest.full_xml)
|| Dest <- Dests],
[route_error(From, From, Packet, jid_malformed,
<<"This service can not process the address: ",
@@ -634,13 +634,13 @@ decide_action_group(Group) ->
%%% Route packet
%%%-------------------------
-route_packet(From, ToDest, Packet, AAttrs, Addresses) ->
+route_packet(From, ToDest, Packet, AAttrs, Others, Addresses) ->
Dests = case ToDest#dest.type of
<<"bcc">> -> [];
_ -> [ToDest]
end,
route_packet2(From, ToDest#dest.jid_string, Dests,
- Packet, AAttrs, Addresses).
+ Packet, AAttrs, {Others, Addresses}).
route_packet_multicast(From, ToS, Packet, AAttrs, Dests,
Addresses, Limits) ->
@@ -666,6 +666,8 @@ route_packet2(From, ToS, Dests, Packet, AAttrs,
ToJID = stj(ToS),
ejabberd_router:route(From, ToJID, Packet2).
+append_dests(_Dests, {Others, Addresses}) ->
+ Addresses++Others;
append_dests([], Addresses) -> Addresses;
append_dests([Dest | Dests], Addresses) ->
append_dests(Dests, [Dest#dest.full_xml | Addresses]).
@@ -734,8 +736,8 @@ process_iqreply_error(From, LServiceS, _Packet) ->
process_iqreply_result(From, LServiceS, Packet, State) ->
#xmlel{name = <<"query">>, attrs = Attrs2,
children = Els2} =
- xml:get_subtag(Packet, <<"query">>),
- case xml:get_attr_s(<<"xmlns">>, Attrs2) of
+ fxml:get_subtag(Packet, <<"query">>),
+ case fxml:get_attr_s(<<"xmlns">>, Attrs2) of
?NS_DISCO_INFO ->
process_discoinfo_result(From, LServiceS, Els2, State);
?NS_DISCO_ITEMS ->
@@ -763,7 +765,7 @@ process_discoinfo_result2(From, FromS, LServiceS, Els,
fun(XML) ->
case XML of
#xmlel{name = <<"feature">>, attrs = Attrs} ->
- (?NS_ADDRESS) == xml:get_attr_s(<<"var">>, Attrs);
+ (?NS_ADDRESS) == fxml:get_attr_s(<<"var">>, Attrs);
_ -> false
end
end,
@@ -807,10 +809,10 @@ get_limits_els(Els) ->
#xmlel{name = <<"x">>, attrs = Attrs,
children = SubEls} ->
case ((?NS_XDATA) ==
- xml:get_attr_s(<<"xmlns">>, Attrs))
+ fxml:get_attr_s(<<"xmlns">>, Attrs))
and
(<<"result">> ==
- xml:get_attr_s(<<"type">>, Attrs))
+ fxml:get_attr_s(<<"type">>, Attrs))
of
true -> get_limits_fields(SubEls) ++ R;
false -> R
@@ -826,11 +828,11 @@ get_limits_fields(Fields) ->
#xmlel{name = <<"field">>,
attrs = Attrs} ->
(<<"FORM_TYPE">> ==
- xml:get_attr_s(<<"var">>,
+ fxml:get_attr_s(<<"var">>,
Attrs))
and
(<<"hidden">> ==
- xml:get_attr_s(<<"type">>,
+ fxml:get_attr_s(<<"type">>,
Attrs));
_ -> false
end
@@ -848,8 +850,8 @@ get_limits_values(Values) ->
children = SubEls} ->
[#xmlel{name = <<"value">>, children = SubElsV}] =
SubEls,
- Number = xml:get_cdata(SubElsV),
- Name = xml:get_attr_s(<<"var">>, Attrs),
+ Number = fxml:get_cdata(SubElsV),
+ Name = fxml:get_attr_s(<<"var">>, Attrs),
[{jlib:binary_to_atom(Name),
jlib:binary_to_integer(Number)}
| R];
@@ -870,7 +872,7 @@ process_discoitems_result(From, LServiceS, Els) ->
fun(XML, Res) ->
case XML of
#xmlel{name = <<"item">>, attrs = Attrs} ->
- SJID = xml:get_attr_s(<<"jid">>, Attrs),
+ SJID = fxml:get_attr_s(<<"jid">>, Attrs),
case jid:from_string(SJID) of
#jid{luser = <<"">>,
lresource = <<"">>} ->
@@ -912,8 +914,9 @@ received_awaiter(JID, Waiter, LServiceS) ->
From = Waiter#waiter.sender,
Packet = Waiter#waiter.packet,
AAttrs = Waiter#waiter.aattrs,
+ Others = Group#group.others,
Addresses = Waiter#waiter.addresses,
- [route_packet(From, ToUser, Packet, AAttrs, Addresses)
+ [route_packet(From, ToUser, Packet, AAttrs, Others, Addresses)
|| ToUser <- Group#group.dests];
true ->
send_query_info(RServer, LServiceS),
@@ -1196,7 +1199,7 @@ to_binary(A) -> list_to_binary(hd(io_lib:format("~p", [A]))).
route_error(From, To, Packet, ErrType, ErrText) ->
#xmlel{attrs = Attrs} = Packet,
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
Reply = make_reply(ErrType, Lang, ErrText),
Err = jlib:make_error_reply(Packet, Reply),
ejabberd_router:route(From, To, Err).
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 1b1627e87..356d89a67 100644
--- a/src/mod_offline.erl
+++ b/src/mod_offline.erl
@@ -27,6 +27,7 @@
-author('alexey@process-one.net').
+-protocol({xep, 13, '1.2'}).
-protocol({xep, 22, '1.4'}).
-protocol({xep, 23, '1.3'}).
-protocol({xep, 160, '1.0'}).
@@ -37,15 +38,18 @@
-behaviour(gen_mod).
--export([count_offline_messages/2]).
-
-export([start/2,
start_link/2,
stop/1,
store_packet/3,
+ store_offline_msg/5,
resend_offline_messages/2,
pop_offline_messages/3,
get_sm_features/5,
+ get_sm_identity/5,
+ get_sm_items/5,
+ get_info/5,
+ handle_offline_query/3,
remove_expired_messages/1,
remove_old_messages/2,
remove_user/2,
@@ -53,7 +57,9 @@
import/3,
export/1,
get_queue_length/2,
+ count_offline_messages/2,
get_offline_els/2,
+ find_x_expire/2,
webadmin_page/3,
webadmin_user/4,
webadmin_user_parse_query/5]).
@@ -62,6 +68,8 @@
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1]).
+-deprecated({get_queue_length,2}).
+
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -80,6 +88,25 @@
%% default value for the maximum number of user messages
-define(MAX_USER_MESSAGES, infinity).
+-type us() :: {binary(), binary()}.
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #offline_msg{}) -> ok | pass.
+-callback store_messages(binary(), us(), [#offline_msg{}],
+ non_neg_integer(), non_neg_integer()) ->
+ {atomic, any()}.
+-callback pop_messages(binary(), binary()) ->
+ {atomic, [#offline_msg{}]} | {aborted, any()}.
+-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(binary(), binary(), non_neg_integer()) ->
+ {ok, #offline_msg{}} | error.
+-callback remove_message(binary(), binary(), non_neg_integer()) -> ok.
+-callback read_all_messages(binary(), binary()) -> [#offline_msg{}].
+-callback remove_all_messages(binary(), binary()) -> {atomic, any()}.
+-callback count_messages(binary(), binary()) -> non_neg_integer().
+
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
?GEN_SERVER:start_link({local, Proc}, ?MODULE,
@@ -104,14 +131,10 @@ stop(Host) ->
%%====================================================================
init([Host, Opts]) ->
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(offline_msg,
- [{disc_only_copies, [node()]}, {type, bag},
- {attributes, record_info(fields, offline_msg)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ no_queue),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
store_packet, 50),
ejabberd_hooks:add(resend_offline_messages_hook, Host,
@@ -124,12 +147,19 @@ init([Host, Opts]) ->
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(disco_local_features, Host,
?MODULE, get_sm_features, 50),
+ ejabberd_hooks:add(disco_sm_identity, Host,
+ ?MODULE, get_sm_identity, 50),
+ ejabberd_hooks:add(disco_sm_items, Host,
+ ?MODULE, get_sm_items, 50),
+ ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 50),
ejabberd_hooks:add(webadmin_page_host, Host,
?MODULE, webadmin_page, 50),
ejabberd_hooks:add(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:add(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE,
+ ?MODULE, handle_offline_query, IQDisc),
AccessMaxOfflineMsgs =
gen_mod:get_opt(access_max_user_messages, Opts,
fun(A) when is_atom(A) -> A end,
@@ -154,7 +184,7 @@ handle_info(#offline_msg{us = UserServer} = Msg, State) ->
Len = length(Msgs),
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
UserServer, Host),
- store_offline_msg(Host, UserServer, Msgs, Len, MaxOfflineMsgs, DBType),
+ store_offline_msg(Host, UserServer, Msgs, Len, MaxOfflineMsgs),
{noreply, State};
handle_info(_Info, State) ->
@@ -174,77 +204,28 @@ terminate(_Reason, State) ->
?MODULE, remove_user, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
+ ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50),
+ ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 50),
+ ejabberd_hooks:delete(disco_info, Host, ?MODULE, get_info, 50),
ejabberd_hooks:delete(webadmin_page_host, Host,
?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE),
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
-
-store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs,
- mnesia) ->
- F = fun () ->
- Count = if MaxOfflineMsgs =/= infinity ->
- Len + count_mnesia_records(US);
- true -> 0
- end,
- if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs);
- true ->
- if Len >= (?OFFLINE_TABLE_LOCK_THRESHOLD) ->
- mnesia:write_lock_table(offline_msg);
- true -> ok
- end,
- lists:foreach(fun (M) -> mnesia:write(M) end, Msgs)
- end
- end,
- mnesia:transaction(F);
-store_offline_msg(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs, odbc) ->
- Count = if MaxOfflineMsgs =/= infinity ->
- Len + count_offline_messages(User, Host);
- true -> 0
- end,
- if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs);
- true ->
- Query = lists:map(fun (M) ->
- Username =
- ejabberd_odbc:escape((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 =
- ejabberd_odbc:escape(xml:element_to_binary(NewPacket)),
- odbc_queries:add_spool_sql(Username, XML)
- end,
- Msgs),
- odbc_queries:add_spool(Host, Query)
- end;
-store_offline_msg(Host, {User, _}, Msgs, Len, MaxOfflineMsgs,
- riak) ->
- Count = if MaxOfflineMsgs =/= infinity ->
- Len + count_offline_messages(User, Host);
- true -> 0
- end,
- if
- Count > MaxOfflineMsgs ->
- discard_warn_sender(Msgs);
- true ->
- lists:foreach(
- fun(#offline_msg{us = US,
- timestamp = TS} = M) ->
- ejabberd_riak:put(M, offline_msg_schema(),
- [{i, TS}, {'2i', [{<<"us">>, US}]}])
- end, Msgs)
+store_offline_msg(Host, US, Msgs, Len, MaxOfflineMsgs) ->
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ case Mod:store_messages(Host, US, Msgs, Len, MaxOfflineMsgs) of
+ {atomic, discard} ->
+ discard_warn_sender(Msgs);
+ _ ->
+ ok
end.
get_max_user_messages(AccessRule, {User, Server}, Host) ->
@@ -262,7 +243,7 @@ receive_all(US, Msgs, DBType) ->
after 0 ->
case DBType of
mnesia -> Msgs;
- odbc -> lists:reverse(Msgs);
+ sql -> lists:reverse(Msgs);
riak -> Msgs
end
end.
@@ -272,38 +253,215 @@ get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
{result, I} -> I;
_ -> []
end,
- {result, Feats ++ [?NS_FEATURE_MSGOFFLINE]};
+ {result, Feats ++ [?NS_FEATURE_MSGOFFLINE, ?NS_FLEX_OFFLINE]};
get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) ->
%% override all lesser features...
{result, []};
+get_sm_features(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S},
+ ?NS_FLEX_OFFLINE, _Lang) ->
+ {result, [?NS_FLEX_OFFLINE]};
+
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
+get_sm_identity(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S},
+ ?NS_FLEX_OFFLINE, _Lang) ->
+ Identity = #xmlel{name = <<"identity">>,
+ attrs = [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"message-list">>}]},
+ [Identity];
+get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID,
+ #jid{luser = U, lserver = S},
+ ?NS_FLEX_OFFLINE, _Lang) ->
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ Pid when is_pid(Pid) ->
+ Hdrs = read_message_headers(U, S),
+ BareJID = jid:to_string(jid:remove_resource(JID)),
+ Pid ! dont_ask_offline,
+ {result, lists:map(
+ fun({Node, From, _To, _El}) ->
+ #xmlel{name = <<"item">>,
+ attrs = [{<<"jid">>, BareJID},
+ {<<"node">>, Node},
+ {<<"name">>, jid:to_string(From)}]}
+ end, Hdrs)};
+ none ->
+ {result, []}
+ end;
+get_sm_items(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+get_info(_Acc, #jid{luser = U, lserver = S, lresource = R},
+ #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, _Lang) ->
+ N = jlib:integer_to_binary(count_offline_messages(U, S)),
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ Pid when is_pid(Pid) ->
+ Pid ! dont_ask_offline;
+ none ->
+ ok
+ end,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"result">>}],
+ children = [#xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata,
+ ?NS_FLEX_OFFLINE}]}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"number_of_messages">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata, N}]}]}]}];
+get_info(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+handle_offline_query(#jid{luser = U, lserver = S} = From,
+ #jid{luser = U, lserver = S} = _To,
+ #iq{type = Type, sub_el = SubEl} = IQ) ->
+ case Type of
+ get ->
+ case fxml:get_subtag(SubEl, <<"fetch">>) of
+ #xmlel{} ->
+ handle_offline_fetch(From);
+ false ->
+ handle_offline_items_view(From, SubEl)
+ end;
+ set ->
+ case fxml:get_subtag(SubEl, <<"purge">>) of
+ #xmlel{} ->
+ delete_all_msgs(U, S);
+ false ->
+ handle_offline_items_remove(From, SubEl)
+ end
+ end,
+ IQ#iq{type = result, sub_el = []};
+handle_offline_query(_From, _To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
+ Txt = <<"Query to another users is forbidden">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}.
+
+handle_offline_items_view(JID, #xmlel{children = Items}) ->
+ {U, S, R} = jid:tolower(JID),
+ lists:foreach(
+ fun(Node) ->
+ case fetch_msg_by_node(JID, Node) of
+ {ok, OfflineMsg} ->
+ case offline_msg_to_route(S, OfflineMsg) of
+ {route, From, To, El} ->
+ NewEl = set_offline_tag(El, Node),
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ Pid when is_pid(Pid) ->
+ Pid ! {route, From, To, NewEl};
+ none ->
+ ok
+ end;
+ error ->
+ ok
+ end;
+ error ->
+ ok
+ end
+ end, get_nodes_from_items(Items, <<"view">>)).
+
+handle_offline_items_remove(JID, #xmlel{children = Items}) ->
+ lists:foreach(
+ fun(Node) ->
+ remove_msg_by_node(JID, Node)
+ end, get_nodes_from_items(Items, <<"remove">>)).
+
+get_nodes_from_items(Items, Action) ->
+ lists:flatmap(
+ fun(#xmlel{name = <<"item">>, attrs = Attrs}) ->
+ case fxml:get_attr_s(<<"action">>, Attrs) of
+ Action ->
+ case fxml:get_attr_s(<<"node">>, Attrs) of
+ <<"">> ->
+ [];
+ TS ->
+ [TS]
+ end;
+ _ ->
+ []
+ end;
+ (_) ->
+ []
+ end, Items).
+
+set_offline_tag(#xmlel{children = Els} = El, Node) ->
+ OfflineEl = #xmlel{name = <<"offline">>,
+ attrs = [{<<"xmlns">>, ?NS_FLEX_OFFLINE}],
+ children = [#xmlel{name = <<"item">>,
+ attrs = [{<<"node">>, Node}]}]},
+ El#xmlel{children = [OfflineEl|Els]}.
+
+handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) ->
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ none ->
+ ok;
+ Pid when is_pid(Pid) ->
+ Pid ! dont_ask_offline,
+ lists:foreach(
+ fun({Node, From, To, El}) ->
+ NewEl = set_offline_tag(El, Node),
+ Pid ! {route, From, To, NewEl}
+ end, read_message_headers(U, S))
+ end.
+
+fetch_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:read_message(LUser, LServer, I);
+ _ ->
+ error
+ end.
+
+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);
+ _ ->
+ ok
+ end.
+
need_to_store(LServer, Packet) ->
- Type = xml:get_tag_attr_s(<<"type">>, Packet),
+ Type = fxml:get_tag_attr_s(<<"type">>, Packet),
if (Type /= <<"error">>) and (Type /= <<"groupchat">>)
and (Type /= <<"headline">>) ->
- case check_store_hint(Packet) of
- store ->
- true;
- no_store ->
- false;
- none ->
- case gen_mod:get_module_opt(
- LServer, ?MODULE, store_empty_body,
- fun(V) when is_boolean(V) -> V;
- (unless_chat_state) -> unless_chat_state
- end,
- unless_chat_state) of
- false ->
- xml:get_subtag(Packet, <<"body">>) /= false;
- unless_chat_state ->
- not jlib:is_standalone_chat_state(Packet);
- true ->
- true
- end
+ case has_offline_tag(Packet) of
+ false ->
+ case check_store_hint(Packet) of
+ store ->
+ true;
+ no_store ->
+ false;
+ none ->
+ case gen_mod:get_module_opt(
+ LServer, ?MODULE, store_empty_body,
+ fun(V) when is_boolean(V) -> V;
+ (unless_chat_state) -> unless_chat_state
+ end,
+ unless_chat_state) of
+ false ->
+ fxml:get_subtag(Packet, <<"body">>) /= false;
+ unless_chat_state ->
+ not jlib:is_standalone_chat_state(Packet);
+ true ->
+ true
+ end
+ end;
+ true ->
+ false
end;
true ->
false
@@ -342,12 +500,15 @@ check_store_hint(Packet) ->
end.
has_store_hint(Packet) ->
- xml:get_subtag_with_xmlns(Packet, <<"store">>, ?NS_HINTS) =/= false.
+ fxml:get_subtag_with_xmlns(Packet, <<"store">>, ?NS_HINTS) =/= false.
has_no_store_hint(Packet) ->
- xml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS) =/= false
+ fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS) =/= false
orelse
- xml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS) =/= false.
+ fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS) =/= false.
+
+has_offline_tag(Packet) ->
+ fxml:get_subtag_with_xmlns(Packet, <<"offline">>, ?NS_FLEX_OFFLINE) =/= false.
%% Check if the packet has any content about XEP-0022
check_event(From, To, Packet) ->
@@ -356,12 +517,12 @@ check_event(From, To, Packet) ->
case find_x_event(Els) of
false -> true;
El ->
- case xml:get_subtag(El, <<"id">>) of
+ case fxml:get_subtag(El, <<"id">>) of
false ->
- case xml:get_subtag(El, <<"offline">>) of
+ case fxml:get_subtag(El, <<"offline">>) of
false -> true;
_ ->
- ID = case xml:get_tag_attr_s(<<"id">>, Packet) of
+ ID = case fxml:get_tag_attr_s(<<"id">>, Packet) of
<<"">> ->
#xmlel{name = <<"id">>, attrs = [],
children = []};
@@ -398,7 +559,7 @@ find_x_event([]) -> false;
find_x_event([{xmlcdata, _} | Els]) ->
find_x_event(Els);
find_x_event([El | Els]) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
?NS_EVENT -> El;
_ -> find_x_event(Els)
end.
@@ -407,9 +568,9 @@ find_x_expire(_, []) -> never;
find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
find_x_expire(TimeStamp, Els);
find_x_expire(TimeStamp, [El | Els]) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
?NS_EXPIRE ->
- Val = xml:get_tag_attr_s(<<"seconds">>, El),
+ Val = fxml:get_tag_attr_s(<<"seconds">>, El),
case catch jlib:binary_to_integer(Val) of
{'EXIT', _} -> never;
Int when Int > 0 ->
@@ -426,21 +587,11 @@ find_x_expire(TimeStamp, [El | Els]) ->
resend_offline_messages(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- US = {LUser, LServer},
- F = fun () ->
- Rs = mnesia:wread({offline_msg, US}),
- mnesia:delete({offline_msg, US}),
- Rs
- end,
- case mnesia:transaction(F) of
- {atomic, Rs} ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:pop_messages(LUser, LServer) of
+ {ok, Rs} ->
lists:foreach(fun (R) ->
- ejabberd_sm !
- {route, R#offline_msg.from, R#offline_msg.to,
- jlib:add_delay_info(R#offline_msg.packet,
- LServer,
- R#offline_msg.timestamp,
- <<"Offline Storage">>)}
+ ejabberd_sm ! offline_msg_to_route(LServer, R)
end,
lists:keysort(#offline_msg.timestamp, Rs));
_ -> ok
@@ -449,194 +600,47 @@ resend_offline_messages(User, Server) ->
pop_offline_messages(Ls, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- pop_offline_messages(Ls, LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-pop_offline_messages(Ls, LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- Rs = mnesia:wread({offline_msg, US}),
- mnesia:delete({offline_msg, US}),
- Rs
- end,
- case mnesia:transaction(F) of
- {atomic, Rs} ->
- TS = p1_time_compat:timestamp(),
- Ls ++
- lists:map(fun (R) ->
- offline_msg_to_route(LServer, R)
- end,
- lists:filter(fun (R) ->
- case R#offline_msg.expire of
- never -> true;
- TimeStamp -> TS < TimeStamp
- end
- end,
- lists:keysort(#offline_msg.timestamp, Rs)));
- _ -> Ls
- end;
-pop_offline_messages(Ls, LUser, LServer, odbc) ->
- EUser = ejabberd_odbc:escape(LUser),
- case odbc_queries:get_and_del_spool_msg_t(LServer,
- EUser)
- of
- {atomic, {selected, [<<"username">>, <<"xml">>], Rs}} ->
- Ls ++
- lists:flatmap(fun ([_, XML]) ->
- case xml_stream:parse_element(XML) of
- {error, _Reason} ->
- [];
- El ->
- case offline_msg_to_route(LServer, El) of
- error ->
- [];
- RouteMsg ->
- [RouteMsg]
- end
- end
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:pop_messages(LUser, LServer) of
+ {ok, Rs} ->
+ TS = p1_time_compat:timestamp(),
+ Ls ++
+ lists:map(fun (R) ->
+ offline_msg_to_route(LServer, R)
end,
- Rs);
- _ -> Ls
- end;
-pop_offline_messages(Ls, LUser, LServer, riak) ->
- case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Rs} ->
- try
- lists:foreach(
- fun(#offline_msg{timestamp = T}) ->
- ok = ejabberd_riak:delete(offline_msg, T)
- end, Rs),
- TS = p1_time_compat:timestamp(),
- Ls ++ lists:map(
- fun (R) ->
- offline_msg_to_route(LServer, R)
- end,
- lists:filter(
- fun(R) ->
- case R#offline_msg.expire of
- never -> true;
- TimeStamp -> TS < TimeStamp
- end
- end,
- lists:keysort(#offline_msg.timestamp, Rs)))
- catch _:{badmatch, _} ->
- Ls
- 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));
_ ->
Ls
end.
remove_expired_messages(Server) ->
LServer = jid:nameprep(Server),
- remove_expired_messages(LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_expired_messages(_LServer, mnesia) ->
- TimeStamp = p1_time_compat:timestamp(),
- F = fun () ->
- mnesia:write_lock_table(offline_msg),
- mnesia:foldl(fun (Rec, _Acc) ->
- case Rec#offline_msg.expire of
- never -> ok;
- TS ->
- if TS < TimeStamp ->
- mnesia:delete_object(Rec);
- true -> ok
- end
- end
- end,
- ok, offline_msg)
- end,
- mnesia:transaction(F);
-remove_expired_messages(_LServer, odbc) -> {atomic, ok};
-remove_expired_messages(_LServer, riak) -> {atomic, ok}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_expired_messages(LServer).
remove_old_messages(Days, Server) ->
LServer = jid:nameprep(Server),
- remove_old_messages(Days, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_old_messages(Days, _LServer, mnesia) ->
- S = p1_time_compat:system_time(seconds) - 60 * 60 * 24 * Days,
- MegaSecs1 = S div 1000000,
- Secs1 = S rem 1000000,
- TimeStamp = {MegaSecs1, Secs1, 0},
- F = fun () ->
- mnesia:write_lock_table(offline_msg),
- mnesia:foldl(fun (#offline_msg{timestamp = TS} = Rec,
- _Acc)
- when TS < TimeStamp ->
- mnesia:delete_object(Rec);
- (_Rec, _Acc) -> ok
- end,
- ok, offline_msg)
- end,
- mnesia:transaction(F);
-
-remove_old_messages(Days, LServer, odbc) ->
- case catch ejabberd_odbc:sql_query(
- LServer,
- [<<"DELETE FROM spool"
- " WHERE created_at < "
- "DATE_SUB(CURDATE(), INTERVAL ">>,
- integer_to_list(Days), <<" DAY);">>]) of
- {updated, N} ->
- ?INFO_MSG("~p message(s) deleted from offline spool", [N]);
- _Error ->
- ?ERROR_MSG("Cannot delete message in offline spool: ~p", [_Error])
- end,
- {atomic, ok};
-remove_old_messages(_Days, _LServer, riak) ->
- {atomic, ok}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_old_messages(Days, LServer).
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- remove_user(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_user(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () -> mnesia:delete({offline_msg, US}) end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_spool_msg(LServer, Username);
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete_by_index(offline_msg,
- <<"us">>, {LUser, LServer})}.
-
-jid_to_binary(#jid{user = U, server = S, resource = R,
- luser = LU, lserver = LS, lresource = LR}) ->
- #jid{user = iolist_to_binary(U),
- server = iolist_to_binary(S),
- resource = iolist_to_binary(R),
- luser = iolist_to_binary(LU),
- lserver = iolist_to_binary(LS),
- lresource = iolist_to_binary(LR)}.
-
-update_table() ->
- Fields = record_info(fields, offline_msg),
- case mnesia:table_info(offline_msg, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- offline_msg, Fields, bag,
- fun(#offline_msg{us = {U, _}}) -> U end,
- fun(#offline_msg{us = {U, S},
- from = From,
- to = To,
- packet = El} = R) ->
- R#offline_msg{us = {iolist_to_binary(U),
- iolist_to_binary(S)},
- from = jid_to_binary(From),
- to = jid_to_binary(To),
- packet = xml:to_xmlel(El)}
- end);
- _ ->
- ?INFO_MSG("Recreating offline_msg table", []),
- mnesia:transform_table(offline_msg, ignore, Fields)
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
%% Helper functions:
@@ -646,7 +650,7 @@ discard_warn_sender(Msgs) ->
packet = Packet}) ->
ErrText = <<"Your contact offline message queue is "
"full. The message has been discarded.">>,
- Lang = xml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Err = jlib:make_error_reply(Packet,
?ERRT_RESOURCE_CONSTRAINT(Lang,
ErrText)),
@@ -662,138 +666,71 @@ webadmin_page(_, Host,
webadmin_page(Acc, _, _) -> Acc.
get_offline_els(LUser, LServer) ->
- get_offline_els(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
-
-get_offline_els(LUser, LServer, DBType)
- when DBType == mnesia; DBType == riak ->
- Msgs = read_all_msgs(LUser, LServer, DBType),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Hdrs = Mod:read_message_headers(LUser, LServer),
lists:map(
- fun(Msg) ->
- {route, From, To, Packet} = offline_msg_to_route(LServer, Msg),
- jlib:replace_from_to(From, To, Packet)
- end, Msgs);
-get_offline_els(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select xml from spool where username='">>,
- Username, <<"' order by seq;">>]) of
- {selected, [<<"xml">>], Rs} ->
- lists:flatmap(
- fun([XML]) ->
- case xml_stream:parse_element(XML) of
- #xmlel{} = El ->
- case offline_msg_to_route(LServer, El) of
- {route, _, _, NewEl} ->
- [NewEl];
- error ->
- []
- end;
- _ ->
- []
- end
- end, Rs);
- _ ->
- []
- end.
+ fun({_Seq, From, To, Packet}) ->
+ jlib:replace_from_to(From, To, Packet)
+ end, Hdrs).
offline_msg_to_route(LServer, #offline_msg{} = R) ->
- {route, R#offline_msg.from, R#offline_msg.to,
- jlib:add_delay_info(R#offline_msg.packet, LServer, R#offline_msg.timestamp,
- <<"Offline Storage">>)};
-offline_msg_to_route(_LServer, #xmlel{} = El) ->
- To = jid:from_string(xml:get_tag_attr_s(<<"to">>, El)),
- From = jid:from_string(xml:get_tag_attr_s(<<"from">>, El)),
- if (To /= error) and (From /= error) ->
- {route, From, To, El};
- true ->
- error
- end.
-
-read_all_msgs(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- lists:keysort(#offline_msg.timestamp,
- mnesia:dirty_read({offline_msg, US}));
-read_all_msgs(LUser, LServer, riak) ->
- case ejabberd_riak:get_by_index(
- offline_msg, offline_msg_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Rs} ->
- lists:keysort(#offline_msg.timestamp, Rs);
- _Err ->
- []
- end;
-read_all_msgs(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select xml from spool where username='">>,
- Username, <<"' order by seq;">>])
- of
- {selected, [<<"xml">>], Rs} ->
- lists:flatmap(fun ([XML]) ->
- case xml_stream:parse_element(XML) of
- {error, _Reason} -> [];
- El -> [El]
- end
- end,
- Rs);
- _ -> []
- end.
+ 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) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ lists:map(
+ fun({Seq, From, To, El}) ->
+ Node = integer_to_binary(Seq),
+ {Node, From, To, El}
+ end, Mod:read_message_headers(LUser, LServer)).
-format_user_queue(Msgs, DBType) when DBType == mnesia; DBType == riak ->
- lists:map(fun (#offline_msg{timestamp = TimeStamp,
- from = From, to = To,
- packet =
- #xmlel{name = Name, attrs = Attrs,
- children = Els}} =
- Msg) ->
- ID = jlib:encode_base64((term_to_binary(Msg))),
- {{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:now_to_local_time(TimeStamp),
- Time =
- iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Year, Month, Day,
- Hour, Minute,
- Second])),
- SFrom = jid:to_string(From),
- STo = jid:to_string(To),
- Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
- Packet = #xmlel{name = Name, attrs = Attrs2,
- children = Els},
- FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
- ?XE(<<"tr">>,
- [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
- [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
- ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time),
- ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom),
- ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo),
- ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
- [?XC(<<"pre">>, FPacket)])])
- end,
- Msgs);
-format_user_queue(Msgs, odbc) ->
- lists:map(fun (#xmlel{} = Msg) ->
- ID = jlib:encode_base64((term_to_binary(Msg))),
- Packet = Msg,
- FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
- ?XE(<<"tr">>,
- [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
- [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
- ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
- [?XC(<<"pre">>, FPacket)])])
- end,
- Msgs).
+format_user_queue(Hdrs) ->
+ lists:map(
+ fun({Seq, From, To, 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
+ {_, _, _} = 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]));
+ _ ->
+ <<"">>
+ end,
+ ?XE(<<"tr">>,
+ [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
+ [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
+ ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time),
+ ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom),
+ ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo),
+ ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
+ [?XC(<<"pre">>, FPacket)])])
+ end, Hdrs).
user_queue(User, Server, Query, Lang) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
- DBType = gen_mod:db_type(LServer, ?MODULE),
- Res = user_queue_parse_query(LUser, LServer, Query,
- DBType),
- MsgsAll = read_all_msgs(LUser, LServer, DBType),
- Msgs = get_messages_subset(US, Server, MsgsAll,
- DBType),
- FMsgs = format_user_queue(Msgs, DBType),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Res = user_queue_parse_query(LUser, LServer, Query),
+ HdrsAll = Mod:read_message_headers(LUser, LServer),
+ 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">>),
[us_to_list(US)])))]
@@ -823,128 +760,33 @@ user_queue(User, Server, Query, Lang) ->
?INPUTT(<<"submit">>, <<"delete">>,
<<"Delete Selected">>)])].
-user_queue_parse_query(LUser, LServer, Query, mnesia) ->
- US = {LUser, LServer},
- case lists:keysearch(<<"delete">>, 1, Query) of
- {value, _} ->
- Msgs = lists:keysort(#offline_msg.timestamp,
- mnesia:dirty_read({offline_msg, US})),
- F = fun () ->
- lists:foreach(fun (Msg) ->
- ID =
- jlib:encode_base64((term_to_binary(Msg))),
- case lists:member({<<"selected">>,
- ID},
- Query)
- of
- true -> mnesia:delete_object(Msg);
- false -> ok
- end
- end,
- Msgs)
- end,
- mnesia:transaction(F),
- ok;
- false -> nothing
- end;
-user_queue_parse_query(LUser, LServer, Query, riak) ->
- case lists:keysearch(<<"delete">>, 1, Query) of
- {value, _} ->
- Msgs = read_all_msgs(LUser, LServer, riak),
- lists:foreach(
- fun (Msg) ->
- ID = jlib:encode_base64((term_to_binary(Msg))),
- case lists:member({<<"selected">>, ID}, Query) of
- true ->
- ejabberd_riak:delete(offline_msg,
- Msg#offline_msg.timestamp);
- false ->
- ok
- end
- end,
- Msgs),
- ok;
- false ->
- nothing
- end;
-user_queue_parse_query(LUser, LServer, Query, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
+user_queue_parse_query(LUser, LServer, Query) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
case lists:keysearch(<<"delete">>, 1, Query) of
- {value, _} ->
- Msgs = case catch ejabberd_odbc:sql_query(LServer,
- [<<"select xml, seq from spool where username='">>,
- Username,
- <<"' order by seq;">>])
- of
- {selected, [<<"xml">>, <<"seq">>], Rs} ->
- lists:flatmap(fun ([XML, Seq]) ->
- case xml_stream:parse_element(XML)
- of
- {error, _Reason} -> [];
- El -> [{El, Seq}]
- end
- end,
- Rs);
- _ -> []
- end,
- F = fun () ->
- lists:foreach(fun ({Msg, Seq}) ->
- ID =
- jlib:encode_base64((term_to_binary(Msg))),
- case lists:member({<<"selected">>,
- ID},
- Query)
- of
- true ->
- SSeq =
- ejabberd_odbc:escape(Seq),
- catch
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from spool where username='">>,
- Username,
- <<"' and seq='">>,
- SSeq,
- <<"';">>]);
- false -> ok
- end
- end,
- Msgs)
- end,
- mnesia:transaction(F),
- ok;
- false -> nothing
+ {value, _} ->
+ case lists:keyfind(<<"selected">>, 1, Query) of
+ {_, Seq} ->
+ case catch binary_to_integer(Seq) of
+ I when is_integer(I), I>=0 ->
+ Mod:remove_message(LUser, LServer, I),
+ ok;
+ _ ->
+ nothing
+ end;
+ false ->
+ nothing
+ end;
+ _ ->
+ nothing
end.
us_to_list({User, Server}) ->
jid:to_string({User, Server, <<"">>}).
get_queue_length(LUser, LServer) ->
- get_queue_length(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_queue_length(LUser, LServer, mnesia) ->
- length(mnesia:dirty_read({offline_msg,
- {LUser, LServer}}));
-get_queue_length(LUser, LServer, riak) ->
- case ejabberd_riak:count_by_index(offline_msg,
- <<"us">>, {LUser, LServer}) of
- {ok, N} ->
- N;
- _ ->
- 0
- end;
-get_queue_length(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from spool where username='">>,
- Username, <<"';">>])
- of
- {selected, [_], [[SCount]]} ->
- jlib:binary_to_integer(SCount);
- _ -> 0
- end.
+ count_offline_messages(LUser, LServer).
-get_messages_subset(User, Host, MsgsAll, DBType) ->
+get_messages_subset(User, Host, MsgsAll) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages,
fun(A) when is_atom(A) -> A end,
max_user_offline_messages),
@@ -955,36 +797,23 @@ get_messages_subset(User, Host, MsgsAll, DBType) ->
_ -> 100
end,
Length = length(MsgsAll),
- get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll,
- DBType).
+ get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll).
-get_messages_subset2(Max, Length, MsgsAll, _DBType)
- when Length =< Max * 2 ->
+get_messages_subset2(Max, Length, MsgsAll) when Length =< Max * 2 ->
MsgsAll;
-get_messages_subset2(Max, Length, MsgsAll, DBType)
- when DBType == mnesia; DBType == riak ->
+get_messages_subset2(Max, Length, MsgsAll) ->
FirstN = Max,
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
Msgs2),
NoJID = jid:make(<<"...">>, <<"...">>, <<"">>),
- IntermediateMsg = #offline_msg{timestamp = p1_time_compat:timestamp(),
- from = NoJID, to = NoJID,
- packet =
- #xmlel{name = <<"...">>, attrs = [],
- children = []}},
- MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN;
-get_messages_subset2(Max, Length, MsgsAll, odbc) ->
- FirstN = Max,
- {MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
- MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
- Msgs2),
+ Seq = <<"0">>,
IntermediateMsg = #xmlel{name = <<"...">>, attrs = [],
children = []},
- MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN.
+ MsgsFirstN ++ [{Seq, NoJID, NoJID, IntermediateMsg}] ++ MsgsLastN.
webadmin_user(Acc, User, Server, Lang) ->
- QueueLen = get_queue_length(jid:nodeprep(User),
+ QueueLen = count_offline_messages(jid:nodeprep(User),
jid:nameprep(Server)),
FQueueLen = [?AC(<<"queue/">>,
(iolist_to_binary(integer_to_list(QueueLen))))],
@@ -998,26 +827,8 @@ webadmin_user(Acc, User, Server, Lang) ->
delete_all_msgs(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- delete_all_msgs(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-delete_all_msgs(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:write_lock_table(offline_msg),
- lists:foreach(fun (Msg) -> mnesia:delete_object(Msg)
- end,
- mnesia:dirty_read({offline_msg, US}))
- end,
- mnesia:transaction(F);
-delete_all_msgs(LUser, LServer, riak) ->
- Res = ejabberd_riak:delete_by_index(offline_msg,
- <<"us">>, {LUser, LServer}),
- {atomic, Res};
-delete_all_msgs(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_spool_msg(LServer, Username),
- {atomic, ok}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_all_messages(LUser, LServer).
webadmin_user_parse_query(_, <<"removealloffline">>,
User, Server, _Query) ->
@@ -1039,114 +850,20 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server,
count_offline_messages(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- count_offline_messages(LUser, LServer, DBType).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:count_messages(LUser, LServer).
-count_offline_messages(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- count_mnesia_records(US)
- end,
- case catch mnesia:async_dirty(F) of
- I when is_integer(I) -> I;
- _ -> 0
- end;
-count_offline_messages(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:count_records_where(LServer,
- <<"spool">>,
- <<"where username='",
- Username/binary, "'">>)
- of
- {selected, [_], [[Res]]} ->
- jlib:binary_to_integer(Res);
- _ -> 0
- end;
-count_offline_messages(LUser, LServer, riak) ->
- case ejabberd_riak:count_by_index(
- offline_msg, <<"us">>, {LUser, LServer}) of
- {ok, Res} ->
- Res;
- _ ->
- 0
- end.
-
-%% Return the number of records matching a given match expression.
-%% This function is intended to be used inside a Mnesia transaction.
-%% The count has been written to use the fewest possible memory by
-%% getting the record by small increment and by using continuation.
--define(BATCHSIZE, 100).
-
-count_mnesia_records(US) ->
- MatchExpression = #offline_msg{us = US, _ = '_'},
- case mnesia:select(offline_msg, [{MatchExpression, [], [[]]}],
- ?BATCHSIZE, read) of
- {Result, Cont} ->
- Count = length(Result),
- count_records_cont(Cont, Count);
- '$end_of_table' ->
- 0
- end.
-
-count_records_cont(Cont, Count) ->
- case mnesia:select(Cont) of
- {Result, Cont} ->
- NewCount = Count + length(Result),
- count_records_cont(Cont, NewCount);
- '$end_of_table' ->
- Count
- end.
-
-offline_msg_schema() ->
- {record_info(fields, offline_msg), #offline_msg{}}.
-
-export(_Server) ->
- [{offline_msg,
- fun(Host, #offline_msg{us = {LUser, LServer},
- timestamp = TimeStamp, from = From, to = To,
- packet = Packet})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- Packet1 = jlib:replace_from_to(From, To, Packet),
- Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp,
- <<"Offline Storage">>),
- XML = ejabberd_odbc:escape(xml:element_to_binary(Packet2)),
- [[<<"delete from spool where username='">>, Username, <<"';">>],
- [<<"insert into spool(username, xml) values ('">>,
- Username, <<"', '">>, XML, <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username, xml from spool;">>,
- fun([LUser, XML]) ->
- El = #xmlel{} = xml_stream:parse_element(XML),
- From = #jid{} = jid:from_string(
- xml:get_attr_s(<<"from">>, El#xmlel.attrs)),
- To = #jid{} = jid:from_string(
- xml:get_attr_s(<<"to">>, El#xmlel.attrs)),
- Stamp = xml: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 = find_x_expire(TS, El#xmlel.children),
- #offline_msg{us = {LUser, LServer},
- from = From, to = To,
- timestamp = TS, expire = Expire}
- end}].
-
-import(_LServer, mnesia, #offline_msg{} = Msg) ->
- mnesia:dirty_write(Msg);
-import(_LServer, riak, #offline_msg{us = US, timestamp = TS} = M) ->
- ejabberd_riak:put(M, offline_msg_schema(),
- [{i, TS}, {'2i', [{<<"us">>, US}]}]);
-import(_, _, _) ->
- pass.
+ 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).
mod_opt_type(access_max_user_messages) ->
fun (A) -> A end;
diff --git a/src/mod_offline_mnesia.erl b/src/mod_offline_mnesia.erl
new file mode 100644
index 000000000..6a1d9e309
--- /dev/null
+++ b/src/mod_offline_mnesia.erl
@@ -0,0 +1,232 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_offline_mnesia).
+
+-behaviour(mod_offline).
+
+-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]).
+
+-include("jlib.hrl").
+-include("mod_offline.hrl").
+-include("logger.hrl").
+
+-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(offline_msg,
+ [{disc_only_copies, [node()]}, {type, bag},
+ {attributes, record_info(fields, offline_msg)}]),
+ update_table().
+
+store_messages(_Host, US, Msgs, Len, MaxOfflineMsgs) ->
+ F = fun () ->
+ Count = if MaxOfflineMsgs =/= infinity ->
+ Len + count_mnesia_records(US);
+ true -> 0
+ end,
+ if Count > MaxOfflineMsgs -> discard;
+ true ->
+ if Len >= (?OFFLINE_TABLE_LOCK_THRESHOLD) ->
+ mnesia:write_lock_table(offline_msg);
+ true -> ok
+ end,
+ lists:foreach(fun (M) -> mnesia:write(M) end, Msgs)
+ end
+ end,
+ mnesia:transaction(F).
+
+pop_messages(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ Rs = mnesia:wread({offline_msg, US}),
+ mnesia:delete({offline_msg, US}),
+ Rs
+ end,
+ case mnesia:transaction(F) of
+ {atomic, L} ->
+ {ok, lists:keysort(#offline_msg.timestamp, L)};
+ {aborted, Reason} ->
+ {error, Reason}
+ end.
+
+remove_expired_messages(_LServer) ->
+ TimeStamp = p1_time_compat:timestamp(),
+ F = fun () ->
+ mnesia:write_lock_table(offline_msg),
+ mnesia:foldl(fun (Rec, _Acc) ->
+ case Rec#offline_msg.expire of
+ never -> ok;
+ TS ->
+ if TS < TimeStamp ->
+ mnesia:delete_object(Rec);
+ true -> ok
+ end
+ end
+ end,
+ ok, offline_msg)
+ end,
+ mnesia:transaction(F).
+
+remove_old_messages(Days, _LServer) ->
+ S = p1_time_compat:system_time(seconds) - 60 * 60 * 24 * Days,
+ MegaSecs1 = S div 1000000,
+ Secs1 = S rem 1000000,
+ TimeStamp = {MegaSecs1, Secs1, 0},
+ F = fun () ->
+ mnesia:write_lock_table(offline_msg),
+ mnesia:foldl(fun (#offline_msg{timestamp = TS} = Rec,
+ _Acc)
+ when TS < TimeStamp ->
+ mnesia:delete_object(Rec);
+ (_Rec, _Acc) -> ok
+ end,
+ ok, offline_msg)
+ end,
+ mnesia:transaction(F).
+
+remove_user(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () -> mnesia:delete({offline_msg, US}) end,
+ mnesia:transaction(F).
+
+read_message_headers(LUser, LServer) ->
+ Msgs = mnesia:dirty_read({offline_msg, {LUser, LServer}}),
+ Hdrs = lists:map(
+ 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}
+ end, Msgs),
+ lists:keysort(1, Hdrs).
+
+read_message(LUser, LServer, I) ->
+ US = {LUser, LServer},
+ TS = integer_to_now(I),
+ case mnesia:dirty_match_object(
+ offline_msg, #offline_msg{us = US, timestamp = TS, _ = '_'}) of
+ [Msg|_] ->
+ {ok, Msg};
+ _ ->
+ error
+ end.
+
+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).
+
+read_all_messages(LUser, LServer) ->
+ US = {LUser, LServer},
+ lists:keysort(#offline_msg.timestamp,
+ mnesia:dirty_read({offline_msg, US})).
+
+remove_all_messages(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:write_lock_table(offline_msg),
+ lists:foreach(fun (Msg) -> mnesia:delete_object(Msg) end,
+ mnesia:dirty_read({offline_msg, US}))
+ end,
+ mnesia:transaction(F).
+
+count_messages(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ count_mnesia_records(US)
+ end,
+ case catch mnesia:async_dirty(F) of
+ I when is_integer(I) -> I;
+ _ -> 0
+ end.
+
+import(_LServer, #offline_msg{} = Msg) ->
+ mnesia:dirty_write(Msg).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+%% Return the number of records matching a given match expression.
+%% This function is intended to be used inside a Mnesia transaction.
+%% The count has been written to use the fewest possible memory by
+%% getting the record by small increment and by using continuation.
+-define(BATCHSIZE, 100).
+
+count_mnesia_records(US) ->
+ MatchExpression = #offline_msg{us = US, _ = '_'},
+ case mnesia:select(offline_msg, [{MatchExpression, [], [[]]}],
+ ?BATCHSIZE, read) of
+ {Result, Cont} ->
+ Count = length(Result),
+ count_records_cont(Cont, Count);
+ '$end_of_table' ->
+ 0
+ end.
+
+count_records_cont(Cont, Count) ->
+ case mnesia:select(Cont) of
+ {Result, Cont} ->
+ NewCount = Count + length(Result),
+ count_records_cont(Cont, NewCount);
+ '$end_of_table' ->
+ Count
+ end.
+
+jid_to_binary(#jid{user = U, server = S, resource = R,
+ luser = LU, lserver = LS, lresource = LR}) ->
+ #jid{user = iolist_to_binary(U),
+ server = iolist_to_binary(S),
+ resource = iolist_to_binary(R),
+ luser = iolist_to_binary(LU),
+ lserver = iolist_to_binary(LS),
+ lresource = iolist_to_binary(LR)}.
+
+now_to_integer({MS, S, US}) ->
+ (MS * 1000000 + S) * 1000000 + US.
+
+integer_to_now(Int) ->
+ Secs = Int div 1000000,
+ USec = Int rem 1000000,
+ MSec = Secs div 1000000,
+ Sec = Secs rem 1000000,
+ {MSec, Sec, USec}.
+
+update_table() ->
+ Fields = record_info(fields, offline_msg),
+ case mnesia:table_info(offline_msg, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ offline_msg, Fields, bag,
+ fun(#offline_msg{us = {U, _}}) -> U end,
+ fun(#offline_msg{us = {U, S},
+ from = From,
+ to = To,
+ packet = El} = R) ->
+ R#offline_msg{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ from = jid_to_binary(From),
+ to = jid_to_binary(To),
+ packet = fxml:to_xmlel(El)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating offline_msg table", []),
+ mnesia:transform_table(offline_msg, ignore, Fields)
+ end.
diff --git a/src/mod_offline_riak.erl b/src/mod_offline_riak.erl
new file mode 100644
index 000000000..217e8f828
--- /dev/null
+++ b/src/mod_offline_riak.erl
@@ -0,0 +1,153 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_offline_riak).
+
+-behaviour(mod_offline).
+
+-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]).
+
+-include("jlib.hrl").
+-include("mod_offline.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+store_messages(Host, {User, _}, Msgs, Len, MaxOfflineMsgs) ->
+ Count = if MaxOfflineMsgs =/= infinity ->
+ Len + count_messages(User, Host);
+ true -> 0
+ end,
+ if
+ Count > MaxOfflineMsgs ->
+ {atomic, discard};
+ true ->
+ try
+ lists:foreach(
+ fun(#offline_msg{us = US,
+ timestamp = TS} = M) ->
+ ok = ejabberd_riak:put(
+ M, offline_msg_schema(),
+ [{i, TS}, {'2i', [{<<"us">>, US}]}])
+ end, Msgs),
+ {atomic, ok}
+ catch _:{badmatch, Err} ->
+ {atomic, Err}
+ end
+ end.
+
+pop_messages(LUser, LServer) ->
+ case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Rs} ->
+ try
+ lists:foreach(
+ fun(#offline_msg{timestamp = T}) ->
+ ok = ejabberd_riak:delete(offline_msg, T)
+ end, Rs),
+ {ok, lists:keysort(#offline_msg.timestamp, Rs)}
+ catch _:{badmatch, Err} ->
+ Err
+ end;
+ Err ->
+ Err
+ end.
+
+remove_expired_messages(_LServer) ->
+ %% TODO
+ {atomic, ok}.
+
+remove_old_messages(_Days, _LServer) ->
+ %% TODO
+ {atomic, ok}.
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete_by_index(offline_msg,
+ <<"us">>, {LUser, LServer})}.
+
+read_message_headers(LUser, LServer) ->
+ case ejabberd_riak:get_by_index(
+ offline_msg, offline_msg_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Rs} ->
+ Hdrs = lists:map(
+ 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}
+ end, Rs),
+ lists:keysort(1, Hdrs);
+ _Err ->
+ []
+ end.
+
+read_message(_LUser, _LServer, I) ->
+ TS = integer_to_now(I),
+ case ejabberd_riak:get(offline_msg, offline_msg_schema(), TS) of
+ {ok, Msg} ->
+ {ok, Msg};
+ _ ->
+ error
+ end.
+
+remove_message(_LUser, _LServer, I) ->
+ TS = integer_to_now(I),
+ ejabberd_riak:delete(offline_msg, TS),
+ ok.
+
+read_all_messages(LUser, LServer) ->
+ case ejabberd_riak:get_by_index(
+ offline_msg, offline_msg_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Rs} ->
+ lists:keysort(#offline_msg.timestamp, Rs);
+ _Err ->
+ []
+ end.
+
+remove_all_messages(LUser, LServer) ->
+ Res = ejabberd_riak:delete_by_index(offline_msg,
+ <<"us">>, {LUser, LServer}),
+ {atomic, Res}.
+
+count_messages(LUser, LServer) ->
+ case ejabberd_riak:count_by_index(
+ offline_msg, <<"us">>, {LUser, LServer}) of
+ {ok, Res} ->
+ Res;
+ _ ->
+ 0
+ end.
+
+import(_LServer, #offline_msg{us = US, timestamp = TS} = M) ->
+ ejabberd_riak:put(M, offline_msg_schema(),
+ [{i, TS}, {'2i', [{<<"us">>, US}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+offline_msg_schema() ->
+ {record_info(fields, offline_msg), #offline_msg{}}.
+
+now_to_integer({MS, S, US}) ->
+ (MS * 1000000 + S) * 1000000 + US.
+
+integer_to_now(Int) ->
+ Secs = Int div 1000000,
+ USec = Int rem 1000000,
+ MSec = Secs div 1000000,
+ Sec = Secs rem 1000000,
+ {MSec, Sec, USec}.
diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl
new file mode 100644
index 000000000..4d9455570
--- /dev/null
+++ b/src/mod_offline_sql.erl
@@ -0,0 +1,252 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_offline_sql).
+
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
+-behaviour(mod_offline).
+
+-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]).
+
+-include("jlib.hrl").
+-include("mod_offline.hrl").
+-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
+ Count = if MaxOfflineMsgs =/= infinity ->
+ Len + count_messages(User, Host);
+ true -> 0
+ end,
+ if Count > MaxOfflineMsgs -> {atomic, discard};
+ true ->
+ Query = lists:map(
+ fun(M) ->
+ Username =
+ ejabberd_sql:escape((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 =
+ ejabberd_sql:escape(fxml:element_to_binary(NewPacket)),
+ sql_queries:add_spool_sql(Username, XML)
+ end,
+ Msgs),
+ sql_queries:add_spool(Host, Query)
+ end.
+
+pop_messages(LUser, LServer) ->
+ case sql_queries:get_and_del_spool_msg_t(LServer, LUser) of
+ {atomic, {selected, Rs}} ->
+ {ok, lists:flatmap(
+ fun({_, XML}) ->
+ case xml_to_offline_msg(XML) of
+ {ok, Msg} ->
+ [Msg];
+ _Err ->
+ []
+ end
+ end, Rs)};
+ Err ->
+ {error, Err}
+ end.
+
+remove_expired_messages(_LServer) ->
+ %% TODO
+ {atomic, ok}.
+
+remove_old_messages(Days, LServer) ->
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ [<<"DELETE FROM spool"
+ " WHERE created_at < "
+ "DATE_SUB(CURDATE(), INTERVAL ">>,
+ integer_to_list(Days), <<" DAY);">>]) of
+ {updated, N} ->
+ ?INFO_MSG("~p message(s) deleted from offline spool", [N]);
+ _Error ->
+ ?ERROR_MSG("Cannot delete message in offline spool: ~p", [_Error])
+ end,
+ {atomic, ok}.
+
+remove_user(LUser, LServer) ->
+ sql_queries:del_spool_msg(LServer, LUser).
+
+read_message_headers(LUser, LServer) ->
+ Username = ejabberd_sql:escape(LUser),
+ case catch ejabberd_sql:sql_query(
+ LServer, [<<"select xml, seq from spool where username ='">>,
+ Username, <<"' order by seq;">>]) of
+ {selected, [<<"xml">>, <<"seq">>], Rows} ->
+ lists:flatmap(
+ fun([XML, Seq]) ->
+ case xml_to_offline_msg(XML) of
+ {ok, #offline_msg{from = From,
+ to = To,
+ packet = El}} ->
+ Seq0 = binary_to_integer(Seq),
+ [{Seq0, From, To, El}];
+ _ ->
+ []
+ end
+ end, Rows);
+ _Err ->
+ []
+ end.
+
+read_message(LUser, LServer, Seq) ->
+ Username = ejabberd_sql:escape(LUser),
+ SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
+ case ejabberd_sql:sql_query(
+ LServer,
+ [<<"select xml from spool where username='">>, Username,
+ <<"' and seq='">>, SSeq, <<"';">>]) of
+ {selected, [<<"xml">>], [[RawXML]|_]} ->
+ case xml_to_offline_msg(RawXML) of
+ {ok, Msg} ->
+ {ok, Msg};
+ _ ->
+ error
+ end;
+ _ ->
+ error
+ end.
+
+remove_message(LUser, LServer, Seq) ->
+ Username = ejabberd_sql:escape(LUser),
+ SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
+ ejabberd_sql:sql_query(
+ LServer,
+ [<<"delete from spool where username='">>, Username,
+ <<"' and seq='">>, SSeq, <<"';">>]),
+ ok.
+
+read_all_messages(LUser, LServer) ->
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(xml)s from spool where "
+ "username=%(LUser)s order by seq")) of
+ {selected, Rs} ->
+ lists:flatmap(
+ fun({XML}) ->
+ case xml_to_offline_msg(XML) of
+ {ok, Msg} -> [Msg];
+ _ -> []
+ end
+ end, Rs);
+ _ ->
+ []
+ end.
+
+remove_all_messages(LUser, LServer) ->
+ sql_queries:del_spool_msg(LServer, LUser),
+ {atomic, ok}.
+
+count_messages(LUser, LServer) ->
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(count(*))d from spool "
+ "where username=%(LUser)s")) of
+ {selected, [{Res}]} ->
+ Res;
+ _ -> 0
+ end.
+
+export(_Server) ->
+ [{offline_msg,
+ fun(Host, #offline_msg{us = {LUser, LServer},
+ timestamp = TimeStamp, from = From, to = To,
+ packet = Packet})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ Packet1 = jlib:replace_from_to(From, To, Packet),
+ Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp,
+ <<"Offline Storage">>),
+ XML = ejabberd_sql:escape(fxml:element_to_binary(Packet2)),
+ [[<<"delete from spool where username='">>, Username, <<"';">>],
+ [<<"insert into spool(username, xml) values ('">>,
+ Username, <<"', '">>, XML, <<"');">>]];
+ (_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.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+xml_to_offline_msg(XML) ->
+ case fxml_stream:parse_element(XML) of
+ #xmlel{} = El ->
+ el_to_offline_msg(El);
+ Err ->
+ ?ERROR_MSG("got ~p when parsing XML packet ~s",
+ [Err, XML]),
+ Err
+ end.
+
+el_to_offline_msg(El) ->
+ To_s = fxml:get_tag_attr_s(<<"to">>, El),
+ From_s = fxml:get_tag_attr_s(<<"from">>, El),
+ To = jid:from_string(To_s),
+ From = jid:from_string(From_s),
+ if To == error ->
+ ?ERROR_MSG("failed to get 'to' JID from offline XML ~p", [El]),
+ {error, bad_jid_to};
+ From == error ->
+ ?ERROR_MSG("failed to get 'from' JID from offline XML ~p", [El]),
+ {error, bad_jid_from};
+ true ->
+ {ok, #offline_msg{us = {To#jid.luser, To#jid.lserver},
+ from = From,
+ to = To,
+ timestamp = undefined,
+ expire = undefined,
+ packet = El}}
+ end.
diff --git a/src/mod_ping.erl b/src/mod_ping.erl
index 85ff770e3..f1c175a91 100644
--- a/src/mod_ping.erl
+++ b/src/mod_ping.erl
@@ -202,13 +202,14 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%% Hook callbacks
%%====================================================================
iq_ping(_From, _To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
+ #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, ?ERR_FEATURE_NOT_IMPLEMENTED]}
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end.
user_online(_SID, JID, _Info) ->
diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl
index 34fdcdb75..1118b7bbc 100644
--- a/src/mod_pres_counter.erl
+++ b/src/mod_pres_counter.erl
@@ -52,7 +52,7 @@ check_packet(_, _User, Server, _PrivacyList,
{From, To, #xmlel{name = Name, attrs = Attrs}}, Dir) ->
case Name of
<<"presence">> ->
- IsSubscription = case xml:get_attr_s(<<"type">>, Attrs)
+ IsSubscription = case fxml:get_attr_s(<<"type">>, Attrs)
of
<<"subscribe">> -> true;
<<"subscribed">> -> true;
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index 9c2af037b..413dcb52a 100644
--- a/src/mod_privacy.erl
+++ b/src/mod_privacy.erl
@@ -33,18 +33,10 @@
-export([start/2, stop/1, process_iq/3, export/1, import/1,
process_iq_set/4, process_iq_get/5, get_user_list/3,
- check_packet/6, remove_user/2, item_to_raw/1,
- raw_to_item/1, is_list_needdb/1, updated_list/3,
- item_to_xml/1, get_user_lists/2, import/3]).
-
--export([sql_add_privacy_list/2,
- sql_get_default_privacy_list/2,
- sql_get_default_privacy_list_t/1,
- sql_get_privacy_list_data/3,
- sql_get_privacy_list_data_by_id_t/1,
- sql_get_privacy_list_id_t/2,
- sql_set_default_privacy_list/2, sql_set_privacy_list/2,
- privacy_schema/0, mod_opt_type/1]).
+ check_packet/6, remove_user/2,
+ is_list_needdb/1, updated_list/3,
+ item_to_xml/1, get_user_lists/2, import/3,
+ set_privacy_list/1, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -53,20 +45,24 @@
-include("mod_privacy.hrl").
-privacy_schema() ->
- {record_info(fields, privacy), #privacy{}}.
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #privacy{}) -> ok | pass.
+-callback process_lists_get(binary(), binary()) -> {none | binary(), [xmlel()]} | 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_active_set(binary(), binary(), binary()) -> [listitem()] | error.
+-callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}.
+-callback set_privacy_list(#privacy{}) -> any().
+-callback set_privacy_list(binary(), binary(), binary(), [listitem()]) -> {atomic, any()}.
+-callback get_user_list(binary(), binary()) -> {none | binary(), [listitem()]}.
+-callback get_user_lists(binary(), binary()) -> {ok, #privacy{}} | error.
+-callback remove_user(binary(), binary()) -> {atomic, any()}.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(privacy,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, privacy)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
mod_disco:register_feature(Host, ?NS_PRIVACY),
ejabberd_hooks:add(privacy_iq_get, Host, ?MODULE,
process_iq_get, 50),
@@ -104,27 +100,28 @@ 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{sub_el = SubEl},
+process_iq_get(_, From, _To, #iq{lang = Lang, sub_el = SubEl},
#userlist{name = Active}) ->
#jid{luser = LUser, lserver = LServer} = From,
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
- [] -> process_lists_get(LUser, LServer, Active);
+ case fxml:remove_cdata(Els) of
+ [] -> process_lists_get(LUser, LServer, Active, Lang);
[#xmlel{name = Name, attrs = Attrs}] ->
case Name of
<<"list">> ->
- ListName = xml:get_attr(<<"name">>, Attrs),
- process_list_get(LUser, LServer, ListName);
- _ -> {error, ?ERR_BAD_REQUEST}
+ 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, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Too many elements">>)}
end.
-process_lists_get(LUser, LServer, Active) ->
- case process_lists_get(LUser, LServer, Active,
- gen_mod:db_type(LServer, ?MODULE))
- of
- error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+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">>,
@@ -150,60 +147,10 @@ process_lists_get(LUser, LServer, Active) ->
children = ADItems}]}
end.
-process_lists_get(LUser, LServer, _Active, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer})
- of
- {'EXIT', _Reason} -> error;
- [] -> {none, []};
- [#privacy{default = Default, lists = Lists}] ->
- LItems = lists:map(fun ({N, _}) ->
- #xmlel{name = <<"list">>,
- attrs = [{<<"name">>, N}],
- children = []}
- end,
- Lists),
- {Default, LItems}
- end;
-process_lists_get(LUser, LServer, _Active, riak) ->
- 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),
- {Default, LItems};
- {error, notfound} ->
- {none, []};
- {error, _} ->
- error
- end;
-process_lists_get(LUser, LServer, _Active, odbc) ->
- Default = case catch sql_get_default_privacy_list(LUser,
- LServer)
- of
- {selected, [<<"name">>], []} -> none;
- {selected, [<<"name">>], [[DefName]]} -> DefName;
- _ -> none
- end,
- case catch sql_get_privacy_list_names(LUser, LServer) of
- {selected, [<<"name">>], Names} ->
- LItems = lists:map(fun ([N]) ->
- #xmlel{name = <<"list">>,
- attrs = [{<<"name">>, N}],
- children = []}
- end,
- Names),
- {Default, LItems};
- _ -> error
- end.
-
-process_list_get(LUser, LServer, {value, Name}) ->
- case process_list_get(LUser, LServer, Name,
- gen_mod:db_type(LServer, ?MODULE))
- of
- error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+process_list_get(LUser, LServer, {value, 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),
@@ -214,50 +161,9 @@ process_list_get(LUser, LServer, {value, Name}) ->
[#xmlel{name = <<"list">>, attrs = [{<<"name">>, Name}],
children = LItems}]}]}
end;
-process_list_get(_LUser, _LServer, false) ->
+process_list_get(_LUser, _LServer, false, _Lang) ->
{error, ?ERR_BAD_REQUEST}.
-process_list_get(LUser, LServer, Name, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer})
- of
- {'EXIT', _Reason} -> error;
- [] -> not_found;
- [#privacy{lists = Lists}] ->
- case lists:keysearch(Name, 1, Lists) of
- {value, {_, List}} -> List;
- _ -> not_found
- end
- end;
-process_list_get(LUser, LServer, Name, riak) ->
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{lists = Lists}} ->
- case lists:keysearch(Name, 1, Lists) of
- {value, {_, List}} -> List;
- _ -> not_found
- end;
- {error, notfound} ->
- not_found;
- {error, _} ->
- error
- end;
-process_list_get(LUser, LServer, Name, odbc) ->
- case catch sql_get_privacy_list_id(LUser, LServer, Name)
- of
- {selected, [<<"id">>], []} -> not_found;
- {selected, [<<"id">>], [[ID]]} ->
- case catch sql_get_privacy_list_data_by_id(ID, LServer)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
- lists:flatmap(fun raw_to_item/1, RItems);
- _ -> error
- end;
- _ -> error
- end.
-
item_to_xml(Item) ->
Attrs1 = [{<<"action">>,
action_to_list(Item#listitem.action)},
@@ -339,109 +245,41 @@ list_to_action(S) ->
<<"deny">> -> deny
end.
-process_iq_set(_, From, _To, #iq{sub_el = SubEl}) ->
+process_iq_set(_, From, _To, #iq{lang = Lang, sub_el = SubEl}) ->
#jid{luser = LUser, lserver = LServer} = From,
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = Name, attrs = Attrs,
children = SubEls}] ->
- ListName = xml:get_attr(<<"name">>, Attrs),
+ ListName = fxml:get_attr(<<"name">>, Attrs),
case Name of
<<"list">> ->
process_list_set(LUser, LServer, ListName,
- xml:remove_cdata(SubEls));
+ fxml:remove_cdata(SubEls), Lang);
<<"active">> ->
process_active_set(LUser, LServer, ListName);
<<"default">> ->
- process_default_set(LUser, LServer, ListName);
- _ -> {error, ?ERR_BAD_REQUEST}
+ process_default_set(LUser, LServer, ListName, Lang);
+ _ ->
+ Txt = <<"Unsupported tag name">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
_ -> {error, ?ERR_BAD_REQUEST}
end.
-process_default_set(LUser, LServer, Value) ->
- case process_default_set(LUser, LServer, Value,
- gen_mod:db_type(LServer, ?MODULE))
- of
- {atomic, error} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+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}
end.
-process_default_set(LUser, LServer, {value, Name},
- mnesia) ->
- F = fun () ->
- case mnesia:read({privacy, {LUser, LServer}}) of
- [] -> not_found;
- [#privacy{lists = Lists} = P] ->
- case lists:keymember(Name, 1, Lists) of
- true ->
- mnesia:write(P#privacy{default = Name,
- lists = Lists}),
- ok;
- false -> not_found
- end
- end
- end,
- mnesia:transaction(F);
-process_default_set(LUser, LServer, {value, Name}, riak) ->
- {atomic,
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{lists = Lists} = P} ->
- case lists:keymember(Name, 1, Lists) of
- true ->
- ejabberd_riak:put(P#privacy{default = Name,
- lists = Lists},
- privacy_schema());
- false ->
- not_found
- end;
- {error, _} ->
- not_found
- end};
-process_default_set(LUser, LServer, {value, Name},
- odbc) ->
- F = fun () ->
- case sql_get_privacy_list_names_t(LUser) of
- {selected, [<<"name">>], []} -> not_found;
- {selected, [<<"name">>], Names} ->
- case lists:member([Name], Names) of
- true -> sql_set_default_privacy_list(LUser, Name), ok;
- false -> not_found
- end
- end
- end,
- odbc_queries:sql_transaction(LServer, F);
-process_default_set(LUser, LServer, false, mnesia) ->
- 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, false, riak) ->
- {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, false, odbc) ->
- case catch sql_unset_default_privacy_list(LUser,
- LServer)
- of
- {'EXIT', _Reason} -> {atomic, error};
- {error, _Reason} -> {atomic, error};
- _ -> {atomic, ok}
- end.
-
process_active_set(LUser, LServer, {value, Name}) ->
- case process_active_set(LUser, LServer, Name,
- gen_mod:db_type(LServer, ?MODULE))
- of
+ 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),
@@ -451,135 +289,19 @@ process_active_set(LUser, LServer, {value, Name}) ->
process_active_set(_LUser, _LServer, false) ->
{result, [], #userlist{}}.
-process_active_set(LUser, LServer, Name, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer})
- of
- [] -> error;
- [#privacy{lists = Lists}] ->
- case lists:keysearch(Name, 1, Lists) of
- {value, {_, List}} -> List;
- false -> error
- end
- end;
-process_active_set(LUser, LServer, Name, riak) ->
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{lists = Lists}} ->
- case lists:keysearch(Name, 1, Lists) of
- {value, {_, List}} -> List;
- false -> error
- end;
- {error, _} ->
- error
- end;
-process_active_set(LUser, LServer, Name, odbc) ->
- case catch sql_get_privacy_list_id(LUser, LServer, Name)
- of
- {selected, [<<"id">>], []} -> error;
- {selected, [<<"id">>], [[ID]]} ->
- case catch sql_get_privacy_list_data_by_id(ID, LServer)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
- lists:flatmap(fun raw_to_item/1, RItems);
- _ -> error
- end;
- _ -> error
- end.
+set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_privacy_list(Privacy).
-remove_privacy_list(LUser, LServer, Name, mnesia) ->
- F = fun () ->
- case mnesia:read({privacy, {LUser, LServer}}) of
- [] -> ok;
- [#privacy{default = Default, lists = Lists} = P] ->
- if Name == Default -> conflict;
- true ->
- NewLists = lists:keydelete(Name, 1, Lists),
- mnesia:write(P#privacy{lists = NewLists})
- end
- end
- end,
- mnesia:transaction(F);
-remove_privacy_list(LUser, LServer, Name, riak) ->
- {atomic,
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{default = Default, lists = Lists} = P} ->
- if Name == Default ->
- conflict;
- true ->
- NewLists = lists:keydelete(Name, 1, Lists),
- ejabberd_riak:put(P#privacy{lists = NewLists},
- privacy_schema())
- end;
- {error, _} ->
- ok
- end};
-remove_privacy_list(LUser, LServer, Name, odbc) ->
- F = fun () ->
- case sql_get_default_privacy_list_t(LUser) of
- {selected, [<<"name">>], []} ->
- sql_remove_privacy_list(LUser, Name), ok;
- {selected, [<<"name">>], [[Default]]} ->
- if Name == Default -> conflict;
- true -> sql_remove_privacy_list(LUser, Name), ok
- end
- end
- end,
- odbc_queries:sql_transaction(LServer, F).
-
-set_privacy_list(LUser, LServer, Name, List, mnesia) ->
- F = fun () ->
- case mnesia:wread({privacy, {LUser, LServer}}) of
- [] ->
- NewLists = [{Name, List}],
- mnesia:write(#privacy{us = {LUser, LServer},
- lists = NewLists});
- [#privacy{lists = Lists} = P] ->
- NewLists1 = lists:keydelete(Name, 1, Lists),
- NewLists = [{Name, List} | NewLists1],
- mnesia:write(P#privacy{lists = NewLists})
- end
- end,
- mnesia:transaction(F);
-set_privacy_list(LUser, LServer, Name, List, riak) ->
- {atomic,
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{lists = Lists} = P} ->
- NewLists1 = lists:keydelete(Name, 1, Lists),
- NewLists = [{Name, List} | NewLists1],
- ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
- {error, _} ->
- NewLists = [{Name, List}],
- ejabberd_riak:put(#privacy{us = {LUser, LServer},
- lists = NewLists},
- privacy_schema())
- end};
-set_privacy_list(LUser, LServer, Name, List, odbc) ->
- RItems = lists:map(fun item_to_raw/1, List),
- F = fun () ->
- ID = case sql_get_privacy_list_id_t(LUser, Name) of
- {selected, [<<"id">>], []} ->
- sql_add_privacy_list(LUser, Name),
- {selected, [<<"id">>], [[I]]} =
- sql_get_privacy_list_id_t(LUser, Name),
- I;
- {selected, [<<"id">>], [[I]]} -> I
- end,
- sql_set_privacy_list(ID, RItems),
- ok
- end,
- odbc_queries:sql_transaction(LServer, F).
-
-process_list_set(LUser, LServer, {value, Name}, Els) ->
+process_list_set(LUser, LServer, {value, Name}, Els, Lang) ->
case parse_items(Els) of
false -> {error, ?ERR_BAD_REQUEST};
remove ->
- case remove_privacy_list(LUser, LServer, Name,
- gen_mod:db_type(LServer, ?MODULE))
- of
- {atomic, conflict} -> {error, ?ERR_CONFLICT};
+ 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,
<<"">>),
@@ -589,12 +311,11 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
list = []},
Name}}),
{result, []};
- _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end;
List ->
- case set_privacy_list(LUser, LServer, Name, List,
- gen_mod:db_type(LServer, ?MODULE))
- of
+ 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,
@@ -606,10 +327,10 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
needdb = NeedDb},
Name}}),
{result, []};
- _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end
end;
-process_list_set(_LUser, _LServer, false, _Els) ->
+process_list_set(_LUser, _LServer, false, _Els, _Lang) ->
{error, ?ERR_BAD_REQUEST}.
parse_items([]) -> remove;
@@ -621,10 +342,10 @@ parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
children = SubEls}
| Els],
Res) ->
- Type = xml:get_attr(<<"type">>, Attrs),
- Value = xml:get_attr(<<"value">>, Attrs),
- SAction = xml:get_attr(<<"action">>, Attrs),
- SOrder = xml:get_attr(<<"order">>, Attrs),
+ Type = fxml:get_attr(<<"type">>, Attrs),
+ Value = fxml:get_attr(<<"value">>, Attrs),
+ SAction = fxml:get_attr(<<"action">>, Attrs),
+ SOrder = fxml:get_attr(<<"order">>, Attrs),
Action = case catch list_to_action(element(2, SAction))
of
{'EXIT', _} -> false;
@@ -674,7 +395,7 @@ parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
case I2 of
false -> false;
_ ->
- case parse_matches(I2, xml:remove_cdata(SubEls)) of
+ case parse_matches(I2, fxml:remove_cdata(SubEls)) of
false -> false;
I3 -> parse_items(Els, [I3 | Res])
end
@@ -714,115 +435,20 @@ is_list_needdb(Items) ->
end,
Items).
-get_user_list(Acc, User, Server) ->
+get_user_list(_Acc, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- {Default, Items} = get_user_list(Acc, LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ {Default, Items} = Mod:get_user_list(LUser, LServer),
NeedDb = is_list_needdb(Items),
#userlist{name = Default, list = Items,
needdb = NeedDb}.
-get_user_list(_, LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer})
- of
- [] -> {none, []};
- [#privacy{default = Default, lists = Lists}] ->
- case Default of
- none -> {none, []};
- _ ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} -> {Default, List};
- _ -> {none, []}
- end
- end;
- _ -> {none, []}
- end;
-get_user_list(_, LUser, LServer, riak) ->
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{default = Default, lists = Lists}} ->
- case Default of
- none -> {none, []};
- _ ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} -> {Default, List};
- _ -> {none, []}
- end
- end;
- {error, _} ->
- {none, []}
- end;
-get_user_list(_, LUser, LServer, odbc) ->
- case catch sql_get_default_privacy_list(LUser, LServer)
- of
- {selected, [<<"name">>], []} -> {none, []};
- {selected, [<<"name">>], [[Default]]} ->
- case catch sql_get_privacy_list_data(LUser, LServer,
- Default)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
- {Default, lists:flatmap(fun raw_to_item/1, RItems)};
- _ -> {none, []}
- end;
- _ -> {none, []}
- end.
-
get_user_lists(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- get_user_lists(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
-
-get_user_lists(LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
- [#privacy{} = P] ->
- {ok, P};
- _ ->
- error
- end;
-get_user_lists(LUser, LServer, riak) ->
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{} = P} ->
- {ok, P};
- {error, _} ->
- error
- end;
-get_user_lists(LUser, LServer, odbc) ->
- Default = case catch sql_get_default_privacy_list(LUser, LServer) of
- {selected, [<<"name">>], []} ->
- none;
- {selected, [<<"name">>], [[DefName]]} ->
- DefName;
- _ ->
- none
- end,
- case catch sql_get_privacy_list_names(LUser, LServer) of
- {selected, [<<"name">>], Names} ->
- Lists =
- lists:flatmap(
- fun([Name]) ->
- case catch sql_get_privacy_list_data(
- LUser, LServer, Name) of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>,
- <<"ord">>, <<"match_all">>, <<"match_iq">>,
- <<"match_message">>, <<"match_presence_in">>,
- <<"match_presence_out">>],
- RItems} ->
- [{Name, lists:flatmap(fun raw_to_item/1, RItems)}];
- _ ->
- []
- end
- end, Names),
- {ok, #privacy{default = Default,
- us = {LUser, LServer},
- lists = Lists}};
- _ ->
- error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_user_lists(LUser, LServer).
%% From is the sender, To is the destination.
%% If Dir = out, User@Server is the sender account (From).
@@ -852,7 +478,7 @@ check_packet(_, User, Server,
<<"message">> -> message;
<<"iq">> -> iq;
<<"presence">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
%% notification
<<"">> -> presence;
<<"unavailable">> -> presence;
@@ -946,17 +572,8 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- remove_user(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_user(LUser, LServer, mnesia) ->
- F = fun () -> mnesia:delete({privacy, {LUser, LServer}})
- end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete(privacy, {LUser, LServer})};
-remove_user(LUser, LServer, odbc) ->
- sql_del_privacy_lists(LUser, LServer).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
updated_list(_, #userlist{name = OldName} = Old,
#userlist{name = NewName} = New) ->
@@ -964,296 +581,17 @@ updated_list(_, #userlist{name = OldName} = Old,
true -> Old
end.
-raw_to_item([SType, SValue, SAction, SOrder, SMatchAll,
- SMatchIQ, SMatchMessage, SMatchPresenceIn,
- SMatchPresenceOut] = Row) ->
- try
- {Type, Value} = case SType of
- <<"n">> -> {none, none};
- <<"j">> ->
- case jid:from_string(SValue) of
- #jid{} = JID ->
- {jid, jid:tolower(JID)}
- end;
- <<"g">> -> {group, SValue};
- <<"s">> ->
- case SValue of
- <<"none">> -> {subscription, none};
- <<"both">> -> {subscription, both};
- <<"from">> -> {subscription, from};
- <<"to">> -> {subscription, to}
- end
- end,
- Action = case SAction of
- <<"a">> -> allow;
- <<"d">> -> deny
- end,
- Order = jlib:binary_to_integer(SOrder),
- MatchAll = ejabberd_odbc:to_bool(SMatchAll),
- MatchIQ = ejabberd_odbc:to_bool(SMatchIQ),
- MatchMessage = ejabberd_odbc:to_bool(SMatchMessage),
- MatchPresenceIn = ejabberd_odbc:to_bool(SMatchPresenceIn),
- MatchPresenceOut = ejabberd_odbc:to_bool(SMatchPresenceOut),
- [#listitem{type = Type, value = Value, action = Action,
- order = Order, match_all = MatchAll, match_iq = MatchIQ,
- match_message = MatchMessage,
- match_presence_in = MatchPresenceIn,
- match_presence_out = MatchPresenceOut}]
- catch _:_ ->
- ?WARNING_MSG("failed to parse row: ~p", [Row]),
- []
- end.
-
-item_to_raw(#listitem{type = Type, value = Value,
- action = Action, order = Order, match_all = MatchAll,
- match_iq = MatchIQ, match_message = MatchMessage,
- match_presence_in = MatchPresenceIn,
- match_presence_out = MatchPresenceOut}) ->
- {SType, SValue} = case Type of
- none -> {<<"n">>, <<"">>};
- jid ->
- {<<"j">>,
- ejabberd_odbc:escape(jid:to_string(Value))};
- group -> {<<"g">>, ejabberd_odbc:escape(Value)};
- subscription ->
- case Value of
- none -> {<<"s">>, <<"none">>};
- both -> {<<"s">>, <<"both">>};
- from -> {<<"s">>, <<"from">>};
- to -> {<<"s">>, <<"to">>}
- end
- end,
- SAction = case Action of
- allow -> <<"a">>;
- deny -> <<"d">>
- end,
- SOrder = iolist_to_binary(integer_to_list(Order)),
- SMatchAll = if MatchAll -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchIQ = if MatchIQ -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchMessage = if MatchMessage -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchPresenceIn = if MatchPresenceIn -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchPresenceOut = if MatchPresenceOut -> <<"1">>;
- true -> <<"0">>
- end,
- [SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ,
- SMatchMessage, SMatchPresenceIn, SMatchPresenceOut].
-
-sql_get_default_privacy_list(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_default_privacy_list(LServer,
- Username).
-
-sql_get_default_privacy_list_t(LUser) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_default_privacy_list_t(Username).
-
-sql_get_privacy_list_names(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_privacy_list_names(LServer, Username).
-
-sql_get_privacy_list_names_t(LUser) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_privacy_list_names_t(Username).
-
-sql_get_privacy_list_id(LUser, LServer, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_id(LServer, Username,
- SName).
-
-sql_get_privacy_list_id_t(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_id_t(Username, SName).
-
-sql_get_privacy_list_data(LUser, LServer, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_data(LServer, Username,
- SName).
-
-sql_get_privacy_list_data_t(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_data_t(Username, SName).
-
-sql_get_privacy_list_data_by_id(ID, LServer) ->
- odbc_queries:get_privacy_list_data_by_id(LServer, ID).
-
-sql_get_privacy_list_data_by_id_t(ID) ->
- odbc_queries:get_privacy_list_data_by_id_t(ID).
-
-sql_set_default_privacy_list(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:set_default_privacy_list(Username, SName).
-
-sql_unset_default_privacy_list(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:unset_default_privacy_list(LServer,
- Username).
-
-sql_remove_privacy_list(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:remove_privacy_list(Username, SName).
-
-sql_add_privacy_list(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:add_privacy_list(Username, SName).
-
-sql_set_privacy_list(ID, RItems) ->
- odbc_queries:set_privacy_list(ID, RItems).
-
-sql_del_privacy_lists(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- Server = ejabberd_odbc:escape(LServer),
- odbc_queries:del_privacy_lists(LServer, Server,
- Username).
-
-update_table() ->
- Fields = record_info(fields, privacy),
- case mnesia:table_info(privacy, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- privacy, Fields, set,
- fun(#privacy{us = {U, _}}) -> U end,
- fun(#privacy{us = {U, S}, default = Def, lists = Lists} = R) ->
- NewLists =
- lists:map(
- fun({Name, Ls}) ->
- NewLs =
- lists:map(
- fun(#listitem{value = Val} = L) ->
- NewVal =
- case Val of
- {LU, LS, LR} ->
- {iolist_to_binary(LU),
- iolist_to_binary(LS),
- iolist_to_binary(LR)};
- none -> none;
- both -> both;
- from -> from;
- to -> to;
- _ -> iolist_to_binary(Val)
- end,
- L#listitem{value = NewVal}
- end, Ls),
- {iolist_to_binary(Name), NewLs}
- end, Lists),
- NewDef = case Def of
- none -> none;
- _ -> iolist_to_binary(Def)
- end,
- NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
- R#privacy{us = NewUS, default = NewDef,
- lists = NewLists}
- end);
- _ ->
- ?INFO_MSG("Recreating privacy table", []),
- mnesia:transform_table(privacy, ignore, Fields)
- end.
-
-export(Server) ->
- case catch ejabberd_odbc:sql_query(jid:nameprep(Server),
- [<<"select id from privacy_list order by "
- "id desc limit 1;">>]) of
- {selected, [<<"id">>], [[I]]} ->
- put(id, jlib:binary_to_integer(I));
- _ ->
- put(id, 0)
- end,
- [{privacy,
- fun(Host, #privacy{us = {LUser, LServer}, lists = Lists,
- default = Default})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- if Default /= none ->
- SDefault = ejabberd_odbc:escape(Default),
- [[<<"delete from privacy_default_list where ">>,
- <<"username='">>, Username, <<"';">>],
- [<<"insert into privacy_default_list(username, "
- "name) ">>,
- <<"values ('">>, Username, <<"', '">>,
- SDefault, <<"');">>]];
- true ->
- []
- end ++
- lists:flatmap(
- fun({Name, List}) ->
- SName = ejabberd_odbc:escape(Name),
- RItems = lists:map(fun item_to_raw/1, List),
- ID = jlib:integer_to_binary(get_id()),
- [[<<"delete from privacy_list where username='">>,
- Username, <<"' and name='">>,
- SName, <<"';">>],
- [<<"insert into privacy_list(username, "
- "name, id) values ('">>,
- Username, <<"', '">>, SName,
- <<"', '">>, ID, <<"');">>],
- [<<"delete from privacy_list_data where "
- "id='">>, ID, <<"';">>]] ++
- [[<<"insert into privacy_list_data(id, t, "
- "value, action, ord, match_all, match_iq, "
- "match_message, match_presence_in, "
- "match_presence_out) values ('">>,
- ID, <<"', '">>, str:join(Items, <<"', '">>),
- <<"');">>] || Items <- RItems]
- end,
- Lists);
- (_Host, _R) ->
- []
- end}].
-
-get_id() ->
- ID = get(id),
- put(id, ID + 1),
- ID + 1.
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
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}].
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-import(_LServer, mnesia, #privacy{} = P) ->
- mnesia:dirty_write(P);
-import(_LServer, riak, #privacy{} = P) ->
- ejabberd_riak:put(P, privacy_schema());
-import(_, _, _) ->
- pass.
+import(LServer, DBType, Data) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, Data).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
diff --git a/src/mod_privacy_mnesia.erl b/src/mod_privacy_mnesia.erl
new file mode 100644
index 000000000..4026b7f64
--- /dev/null
+++ b/src/mod_privacy_mnesia.erl
@@ -0,0 +1,198 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_privacy_mnesia).
+
+-behaviour(mod_privacy).
+
+%% API
+-export([init/2, process_lists_get/2, process_list_get/3,
+ 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]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(privacy,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, privacy)}]),
+ update_table().
+
+process_lists_get(LUser, LServer) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ {'EXIT', _Reason} -> error;
+ [] -> {none, []};
+ [#privacy{default = Default, lists = Lists}] ->
+ LItems = lists:map(fun ({N, _}) ->
+ #xmlel{name = <<"list">>,
+ attrs = [{<<"name">>, N}],
+ children = []}
+ end, Lists),
+ {Default, LItems}
+ end.
+
+process_list_get(LUser, LServer, Name) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ {'EXIT', _Reason} -> error;
+ [] -> not_found;
+ [#privacy{lists = Lists}] ->
+ case lists:keysearch(Name, 1, Lists) of
+ {value, {_, List}} -> List;
+ _ -> not_found
+ end
+ end.
+
+process_default_set(LUser, LServer, {value, Name}) ->
+ F = fun () ->
+ case mnesia:read({privacy, {LUser, LServer}}) of
+ [] -> not_found;
+ [#privacy{lists = Lists} = P] ->
+ case lists:keymember(Name, 1, Lists) of
+ true ->
+ mnesia:write(P#privacy{default = Name,
+ lists = Lists}),
+ ok;
+ false -> not_found
+ 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) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ [] -> error;
+ [#privacy{lists = Lists}] ->
+ case lists:keysearch(Name, 1, Lists) of
+ {value, {_, List}} -> List;
+ false -> error
+ end
+ end.
+
+remove_privacy_list(LUser, LServer, Name) ->
+ F = fun () ->
+ case mnesia:read({privacy, {LUser, LServer}}) of
+ [] -> ok;
+ [#privacy{default = Default, lists = Lists} = P] ->
+ if Name == Default -> conflict;
+ true ->
+ NewLists = lists:keydelete(Name, 1, Lists),
+ mnesia:write(P#privacy{lists = NewLists})
+ end
+ end
+ end,
+ mnesia:transaction(F).
+
+set_privacy_list(Privacy) ->
+ mnesia:dirty_write(Privacy).
+
+set_privacy_list(LUser, LServer, Name, List) ->
+ F = fun () ->
+ case mnesia:wread({privacy, {LUser, LServer}}) of
+ [] ->
+ NewLists = [{Name, List}],
+ mnesia:write(#privacy{us = {LUser, LServer},
+ lists = NewLists});
+ [#privacy{lists = Lists} = P] ->
+ NewLists1 = lists:keydelete(Name, 1, Lists),
+ NewLists = [{Name, List} | NewLists1],
+ mnesia:write(P#privacy{lists = NewLists})
+ end
+ end,
+ mnesia:transaction(F).
+
+get_user_list(LUser, LServer) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer})
+ of
+ [] -> {none, []};
+ [#privacy{default = Default, lists = Lists}] ->
+ case Default of
+ none -> {none, []};
+ _ ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} -> {Default, List};
+ _ -> {none, []}
+ end
+ end;
+ _ -> {none, []}
+ end.
+
+get_user_lists(LUser, LServer) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ [#privacy{} = P] ->
+ {ok, P};
+ _ ->
+ error
+ end.
+
+remove_user(LUser, LServer) ->
+ F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) end,
+ mnesia:transaction(F).
+
+import(_LServer, #privacy{} = P) ->
+ mnesia:dirty_write(P).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, privacy),
+ case mnesia:table_info(privacy, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ privacy, Fields, set,
+ fun(#privacy{us = {U, _}}) -> U end,
+ fun(#privacy{us = {U, S}, default = Def, lists = Lists} = R) ->
+ NewLists =
+ lists:map(
+ fun({Name, Ls}) ->
+ NewLs =
+ lists:map(
+ fun(#listitem{value = Val} = L) ->
+ NewVal =
+ case Val of
+ {LU, LS, LR} ->
+ {iolist_to_binary(LU),
+ iolist_to_binary(LS),
+ iolist_to_binary(LR)};
+ none -> none;
+ both -> both;
+ from -> from;
+ to -> to;
+ _ -> iolist_to_binary(Val)
+ end,
+ L#listitem{value = NewVal}
+ end, Ls),
+ {iolist_to_binary(Name), NewLs}
+ end, Lists),
+ NewDef = case Def of
+ none -> none;
+ _ -> iolist_to_binary(Def)
+ end,
+ NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
+ R#privacy{us = NewUS, default = NewDef,
+ lists = NewLists}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating privacy table", []),
+ mnesia:transform_table(privacy, ignore, Fields)
+ end.
diff --git a/src/mod_privacy_riak.erl b/src/mod_privacy_riak.erl
new file mode 100644
index 000000000..0c43e74f9
--- /dev/null
+++ b/src/mod_privacy_riak.erl
@@ -0,0 +1,160 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_privacy_riak).
+
+-behaviour(mod_privacy).
+
+%% API
+-export([init/2, process_lists_get/2, process_list_get/3,
+ 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]).
+
+-export([privacy_schema/0]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+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),
+ {Default, LItems};
+ {error, notfound} ->
+ {none, []};
+ {error, _} ->
+ error
+ end.
+
+process_list_get(LUser, LServer, Name) ->
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{lists = Lists}} ->
+ case lists:keysearch(Name, 1, Lists) of
+ {value, {_, List}} -> List;
+ _ -> not_found
+ end;
+ {error, notfound} ->
+ not_found;
+ {error, _} ->
+ error
+ end.
+
+process_default_set(LUser, LServer, {value, Name}) ->
+ {atomic,
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{lists = Lists} = P} ->
+ case lists:keymember(Name, 1, Lists) of
+ true ->
+ ejabberd_riak:put(P#privacy{default = Name,
+ lists = Lists},
+ privacy_schema());
+ false ->
+ not_found
+ 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) ->
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{lists = Lists}} ->
+ case lists:keysearch(Name, 1, Lists) of
+ {value, {_, List}} -> List;
+ false -> error
+ end;
+ {error, _} ->
+ error
+ end.
+
+remove_privacy_list(LUser, LServer, Name) ->
+ {atomic,
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{default = Default, lists = Lists} = P} ->
+ if Name == Default ->
+ conflict;
+ true ->
+ NewLists = lists:keydelete(Name, 1, Lists),
+ ejabberd_riak:put(P#privacy{lists = NewLists},
+ privacy_schema())
+ end;
+ {error, _} ->
+ ok
+ end}.
+
+set_privacy_list(Privacy) ->
+ ejabberd_riak:put(Privacy, privacy_schema()).
+
+set_privacy_list(LUser, LServer, Name, List) ->
+ {atomic,
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{lists = Lists} = P} ->
+ NewLists1 = lists:keydelete(Name, 1, Lists),
+ NewLists = [{Name, List} | NewLists1],
+ ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
+ {error, _} ->
+ NewLists = [{Name, List}],
+ ejabberd_riak:put(#privacy{us = {LUser, LServer},
+ lists = NewLists},
+ privacy_schema())
+ end}.
+
+get_user_list(LUser, LServer) ->
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{default = Default, lists = Lists}} ->
+ case Default of
+ none -> {none, []};
+ _ ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} -> {Default, List};
+ _ -> {none, []}
+ end
+ end;
+ {error, _} ->
+ {none, []}
+ end.
+
+get_user_lists(LUser, LServer) ->
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{} = P} ->
+ {ok, P};
+ {error, _} ->
+ error
+ end.
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete(privacy, {LUser, LServer})}.
+
+import(_LServer, #privacy{} = P) ->
+ ejabberd_riak:put(P, privacy_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+privacy_schema() ->
+ {record_info(fields, privacy), #privacy{}}.
diff --git a/src/mod_privacy_sql.erl b/src/mod_privacy_sql.erl
new file mode 100644
index 000000000..6b996fa8b
--- /dev/null
+++ b/src/mod_privacy_sql.erl
@@ -0,0 +1,397 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_privacy_sql).
+
+-behaviour(mod_privacy).
+
+%% API
+-export([init/2, process_lists_get/2, process_list_get/3,
+ 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]).
+
+-export([item_to_raw/1, raw_to_item/1,
+ sql_add_privacy_list/2,
+ sql_get_default_privacy_list/2,
+ sql_get_default_privacy_list_t/1,
+ sql_get_privacy_list_data/3,
+ sql_get_privacy_list_data_by_id_t/1,
+ sql_get_privacy_list_id_t/2,
+ sql_set_default_privacy_list/2, sql_set_privacy_list/2]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+process_lists_get(LUser, LServer) ->
+ Default = case catch sql_get_default_privacy_list(LUser, LServer) of
+ {selected, []} -> none;
+ {selected, [{DefName}]} -> DefName;
+ _ -> none
+ end,
+ case catch sql_get_privacy_list_names(LUser, LServer) of
+ {selected, Names} ->
+ LItems = lists:map(fun ({N}) ->
+ #xmlel{name = <<"list">>,
+ attrs = [{<<"name">>, N}],
+ children = []}
+ end,
+ Names),
+ {Default, LItems};
+ _ -> error
+ end.
+
+process_list_get(LUser, LServer, Name) ->
+ case catch sql_get_privacy_list_id(LUser, LServer, Name) of
+ {selected, []} -> not_found;
+ {selected, [{ID}]} ->
+ case catch sql_get_privacy_list_data_by_id(ID, LServer) of
+ {selected, RItems} ->
+ lists:flatmap(fun raw_to_item/1, RItems);
+ _ -> error
+ end;
+ _ -> error
+ end.
+
+process_default_set(LUser, LServer, {value, Name}) ->
+ F = fun () ->
+ case sql_get_privacy_list_names_t(LUser) of
+ {selected, []} -> not_found;
+ {selected, Names} ->
+ case lists:member({Name}, Names) of
+ true -> sql_set_default_privacy_list(LUser, Name), ok;
+ false -> not_found
+ end
+ 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.
+
+process_active_set(LUser, LServer, Name) ->
+ case catch sql_get_privacy_list_id(LUser, LServer, Name) of
+ {selected, []} -> error;
+ {selected, [{ID}]} ->
+ case catch sql_get_privacy_list_data_by_id(ID, LServer) of
+ {selected, RItems} ->
+ lists:flatmap(fun raw_to_item/1, RItems);
+ _ -> error
+ end;
+ _ -> error
+ end.
+
+remove_privacy_list(LUser, LServer, Name) ->
+ F = fun () ->
+ case sql_get_default_privacy_list_t(LUser) of
+ {selected, []} ->
+ sql_remove_privacy_list(LUser, Name), ok;
+ {selected, [{Default}]} ->
+ if Name == Default -> conflict;
+ true -> sql_remove_privacy_list(LUser, Name), ok
+ end
+ end
+ end,
+ sql_queries:sql_transaction(LServer, F).
+
+set_privacy_list(#privacy{us = {LUser, LServer},
+ default = Default,
+ lists = Lists}) ->
+ F = fun() ->
+ lists:foreach(
+ fun({Name, List}) ->
+ sql_add_privacy_list(LUser, Name),
+ {selected, [<<"id">>], [[I]]} =
+ sql_get_privacy_list_id_t(LUser, Name),
+ RItems = lists:map(fun item_to_raw/1, List),
+ sql_set_privacy_list(I, RItems),
+ if is_binary(Default) ->
+ sql_set_default_privacy_list(LUser, Default),
+ ok;
+ true ->
+ ok
+ end
+ end, Lists)
+ end,
+ sql_queries:sql_transaction(LServer, F).
+
+set_privacy_list(LUser, LServer, Name, List) ->
+ RItems = lists:map(fun item_to_raw/1, List),
+ F = fun () ->
+ ID = case sql_get_privacy_list_id_t(LUser, Name) of
+ {selected, []} ->
+ sql_add_privacy_list(LUser, Name),
+ {selected, [{I}]} =
+ sql_get_privacy_list_id_t(LUser, Name),
+ I;
+ {selected, [{I}]} -> I
+ end,
+ sql_set_privacy_list(ID, RItems),
+ ok
+ end,
+ sql_queries:sql_transaction(LServer, F).
+
+get_user_list(LUser, LServer) ->
+ case catch sql_get_default_privacy_list(LUser, LServer)
+ of
+ {selected, []} -> {none, []};
+ {selected, [{Default}]} ->
+ case catch sql_get_privacy_list_data(LUser, LServer,
+ Default) of
+ {selected, RItems} ->
+ {Default, lists:flatmap(fun raw_to_item/1, RItems)};
+ _ -> {none, []}
+ end;
+ _ -> {none, []}
+ end.
+
+get_user_lists(LUser, LServer) ->
+ Default = case catch sql_get_default_privacy_list(LUser, LServer) of
+ {selected, []} ->
+ none;
+ {selected, [{DefName}]} ->
+ DefName;
+ _ ->
+ none
+ end,
+ case catch sql_get_privacy_list_names(LUser, LServer) of
+ {selected, Names} ->
+ Lists =
+ lists:flatmap(
+ fun({Name}) ->
+ case catch sql_get_privacy_list_data(
+ LUser, LServer, Name) of
+ {selected, RItems} ->
+ [{Name, lists:flatmap(fun raw_to_item/1, RItems)}];
+ _ ->
+ []
+ end
+ end, Names),
+ {ok, #privacy{default = Default,
+ us = {LUser, LServer},
+ lists = Lists}};
+ _ ->
+ error
+ end.
+
+remove_user(LUser, LServer) ->
+ sql_del_privacy_lists(LUser, LServer).
+
+export(Server) ->
+ case catch ejabberd_sql:sql_query(jid:nameprep(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, 0)
+ end,
+ [{privacy,
+ fun(Host, #privacy{us = {LUser, LServer}, lists = Lists,
+ default = Default})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ if Default /= none ->
+ SDefault = ejabberd_sql:escape(Default),
+ [[<<"delete from privacy_default_list where ">>,
+ <<"username='">>, Username, <<"';">>],
+ [<<"insert into privacy_default_list(username, "
+ "name) ">>,
+ <<"values ('">>, Username, <<"', '">>,
+ SDefault, <<"');">>]];
+ true ->
+ []
+ end ++
+ lists:flatmap(
+ fun({Name, List}) ->
+ SName = ejabberd_sql:escape(Name),
+ RItems = lists:map(fun item_to_raw/1, List),
+ ID = jlib:integer_to_binary(get_id()),
+ [[<<"delete from privacy_list where username='">>,
+ Username, <<"' and name='">>,
+ SName, <<"';">>],
+ [<<"insert into privacy_list(username, "
+ "name, id) values ('">>,
+ Username, <<"', '">>, SName,
+ <<"', '">>, ID, <<"');">>],
+ [<<"delete from privacy_list_data where "
+ "id='">>, ID, <<"';">>]] ++
+ [[<<"insert into privacy_list_data(id, t, "
+ "value, action, ord, match_all, match_iq, "
+ "match_message, match_presence_in, "
+ "match_presence_out) values ('">>,
+ ID, <<"', '">>, str:join(Items, <<"', '">>),
+ <<"');">>] || Items <- RItems]
+ end,
+ Lists);
+ (_Host, _R) ->
+ []
+ end}].
+
+get_id() ->
+ ID = 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.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+raw_to_item({SType, SValue, SAction, Order, MatchAll,
+ MatchIQ, MatchMessage, MatchPresenceIn,
+ MatchPresenceOut} = Row) ->
+ try
+ {Type, Value} = case SType of
+ <<"n">> -> {none, none};
+ <<"j">> ->
+ case jid:from_string(SValue) of
+ #jid{} = JID ->
+ {jid, jid:tolower(JID)}
+ end;
+ <<"g">> -> {group, SValue};
+ <<"s">> ->
+ case SValue of
+ <<"none">> -> {subscription, none};
+ <<"both">> -> {subscription, both};
+ <<"from">> -> {subscription, from};
+ <<"to">> -> {subscription, to}
+ end
+ end,
+ Action = case SAction of
+ <<"a">> -> allow;
+ <<"d">> -> deny
+ end,
+ [#listitem{type = Type, value = Value, action = Action,
+ order = Order, match_all = MatchAll, match_iq = MatchIQ,
+ match_message = MatchMessage,
+ match_presence_in = MatchPresenceIn,
+ match_presence_out = MatchPresenceOut}]
+ catch _:_ ->
+ ?WARNING_MSG("failed to parse row: ~p", [Row]),
+ []
+ end.
+
+item_to_raw(#listitem{type = Type, value = Value,
+ action = Action, order = Order, match_all = MatchAll,
+ match_iq = MatchIQ, match_message = MatchMessage,
+ match_presence_in = MatchPresenceIn,
+ match_presence_out = MatchPresenceOut}) ->
+ {SType, SValue} = case Type of
+ none -> {<<"n">>, <<"">>};
+ jid ->
+ {<<"j">>,
+ ejabberd_sql:escape(jid:to_string(Value))};
+ group -> {<<"g">>, ejabberd_sql:escape(Value)};
+ subscription ->
+ case Value of
+ none -> {<<"s">>, <<"none">>};
+ both -> {<<"s">>, <<"both">>};
+ from -> {<<"s">>, <<"from">>};
+ to -> {<<"s">>, <<"to">>}
+ end
+ end,
+ SAction = case Action of
+ allow -> <<"a">>;
+ deny -> <<"d">>
+ end,
+ {SType, SValue, SAction, Order, MatchAll, MatchIQ,
+ MatchMessage, MatchPresenceIn, MatchPresenceOut}.
+
+sql_get_default_privacy_list(LUser, LServer) ->
+ sql_queries:get_default_privacy_list(LServer, LUser).
+
+sql_get_default_privacy_list_t(LUser) ->
+ sql_queries:get_default_privacy_list_t(LUser).
+
+sql_get_privacy_list_names(LUser, LServer) ->
+ sql_queries:get_privacy_list_names(LServer, LUser).
+
+sql_get_privacy_list_names_t(LUser) ->
+ sql_queries:get_privacy_list_names_t(LUser).
+
+sql_get_privacy_list_id(LUser, LServer, Name) ->
+ sql_queries:get_privacy_list_id(LServer, LUser, Name).
+
+sql_get_privacy_list_id_t(LUser, Name) ->
+ sql_queries: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) ->
+ Username = ejabberd_sql:escape(LUser),
+ SName = ejabberd_sql:escape(Name),
+ sql_queries:get_privacy_list_data_t(Username, SName).
+
+sql_get_privacy_list_data_by_id(ID, LServer) ->
+ sql_queries:get_privacy_list_data_by_id(LServer, ID).
+
+sql_get_privacy_list_data_by_id_t(ID) ->
+ sql_queries:get_privacy_list_data_by_id_t(ID).
+
+sql_set_default_privacy_list(LUser, Name) ->
+ sql_queries:set_default_privacy_list(LUser, Name).
+
+sql_unset_default_privacy_list(LUser, LServer) ->
+ sql_queries:unset_default_privacy_list(LServer, LUser).
+
+sql_remove_privacy_list(LUser, Name) ->
+ sql_queries:remove_privacy_list(LUser, Name).
+
+sql_add_privacy_list(LUser, Name) ->
+ sql_queries:add_privacy_list(LUser, Name).
+
+sql_set_privacy_list(ID, RItems) ->
+ sql_queries:set_privacy_list(ID, RItems).
+
+sql_del_privacy_lists(LUser, LServer) ->
+ sql_queries:del_privacy_lists(LServer, LUser).
diff --git a/src/mod_private.erl b/src/mod_private.erl
index a03d83e5a..029789e63 100644
--- a/src/mod_private.erl
+++ b/src/mod_private.erl
@@ -33,18 +33,20 @@
-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]).
+ mod_opt_type/1, set_data/3]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-
--record(private_storage,
- {usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
- '$1' | '_'},
- xml = #xmlel{} :: xmlel() | '_' | '$1'}).
-
+-include("mod_private.hrl").
+
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #private_storage{}) -> ok | pass.
+-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}).
@@ -52,15 +54,8 @@
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(private_storage,
- [{disc_only_copies, [node()]},
- {attributes,
- record_info(fields, private_storage)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
@@ -73,255 +68,110 @@ stop(Host) ->
?NS_PRIVATE).
process_sm_iq(#jid{luser = LUser, lserver = LServer},
- #jid{luser = LUser, lserver = LServer}, IQ)
+ #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, ?ERR_NOT_ACCEPTABLE]};
+ sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]};
Data ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- F = fun () ->
- lists:foreach(fun (Datum) ->
- set_data(LUser, LServer,
- Datum, DBType)
- end,
- Data)
- end,
- case DBType of
- odbc -> ejabberd_odbc:sql_transaction(LServer, F);
- mnesia -> mnesia:transaction(F);
- riak -> F()
- end,
+ set_data(LUser, LServer, Data),
IQ#iq{type = result, sub_el = []}
end;
_ ->
+ Txt = <<"No query found">>,
IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]}
+ sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]}
end;
%%
process_sm_iq(#jid{luser = LUser, lserver = LServer},
- #jid{luser = LUser, lserver = LServer}, IQ)
+ #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, ?ERR_BAD_FORMAT]};
+ 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, ?ERR_INTERNAL_SERVER_ERROR]};
+ [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, ?ERR_BAD_FORMAT]}
+ sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]}
end;
%%
-process_sm_iq(_From, _To, IQ) ->
+process_sm_iq(_From, _To, #iq{lang = Lang} = IQ) ->
+ Txt = <<"Query to another users is forbidden">>,
IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERR_FORBIDDEN]}.
+ 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 xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"">> -> [];
XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data])
end;
filter_xmlels([_ | Xmlels], Data) ->
filter_xmlels(Xmlels, Data).
-set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) ->
- mnesia:write(#private_storage{usns =
- {LUser, LServer, XmlNS},
- xml = Xmlel});
-set_data(LUser, LServer, {XMLNS, El}, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- LXMLNS = ejabberd_odbc:escape(XMLNS),
- SData = ejabberd_odbc:escape(xml:element_to_binary(El)),
- odbc_queries:set_private_data(LServer, Username, LXMLNS,
- SData);
-set_data(LUser, LServer, {XMLNS, El}, riak) ->
- ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS},
- xml = El},
- private_storage_schema(),
- [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+set_data(LUser, LServer, Data) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_data(LUser, LServer, Data).
get_data(LUser, LServer, Data) ->
- get_data(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE), Data, []).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ get_data(LUser, LServer, Data, Mod, []).
-get_data(_LUser, _LServer, _DBType, [],
- Storage_Xmlels) ->
+get_data(_LUser, _LServer, [], _Mod, Storage_Xmlels) ->
lists:reverse(Storage_Xmlels);
-get_data(LUser, LServer, mnesia,
- [{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
- case mnesia:dirty_read(private_storage,
- {LUser, LServer, XmlNS})
- of
- [#private_storage{xml = Storage_Xmlel}] ->
- get_data(LUser, LServer, mnesia, Data,
- [Storage_Xmlel | Storage_Xmlels]);
- _ ->
- get_data(LUser, LServer, mnesia, Data,
- [Xmlel | Storage_Xmlels])
- end;
-get_data(LUser, LServer, odbc, [{XMLNS, El} | Els],
- Res) ->
- Username = ejabberd_odbc:escape(LUser),
- LXMLNS = ejabberd_odbc:escape(XMLNS),
- case catch odbc_queries:get_private_data(LServer,
- Username, LXMLNS)
- of
- {selected, [<<"data">>], [[SData]]} ->
- case xml_stream:parse_element(SData) of
- Data when is_record(Data, xmlel) ->
- get_data(LUser, LServer, odbc, Els, [Data | Res])
- end;
- _ -> get_data(LUser, LServer, odbc, Els, [El | Res])
- end;
-get_data(LUser, LServer, riak, [{XMLNS, El} | Els],
- Res) ->
- case ejabberd_riak:get(private_storage, private_storage_schema(),
- {LUser, LServer, XMLNS}) of
- {ok, #private_storage{xml = NewEl}} ->
- get_data(LUser, LServer, riak, Els, [NewEl|Res]);
- _ ->
- get_data(LUser, LServer, riak, Els, [El|Res])
+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.
get_data(LUser, LServer) ->
- get_all_data(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_all_data(LUser, LServer, mnesia) ->
- lists:flatten(
- mnesia:dirty_select(private_storage,
- [{#private_storage{usns = {LUser, LServer, '_'},
- xml = '$1'},
- [], ['$1']}]));
-get_all_data(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_private_data(LServer, Username) of
- {selected, [<<"namespace">>, <<"data">>], Res} ->
- lists:flatmap(
- fun([_, SData]) ->
- case xml_stream:parse_element(SData) of
- #xmlel{} = El ->
- [El];
- _ ->
- []
- end
- end, Res);
- _ ->
- []
- end;
-get_all_data(LUser, LServer, riak) ->
- case ejabberd_riak:get_by_index(
- private_storage, private_storage_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Res} ->
- [El || #private_storage{xml = El} <- Res];
- _ ->
- []
- end.
-
-private_storage_schema() ->
- {record_info(fields, private_storage), #private_storage{}}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_all_data(LUser, LServer).
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- remove_user(LUser, LServer,
- gen_mod:db_type(Server, ?MODULE)).
+ Mod = gen_mod:db_mod(Server, ?MODULE),
+ Mod:remove_user(LUser, LServer).
-remove_user(LUser, LServer, mnesia) ->
- F = fun () ->
- Namespaces = mnesia:select(private_storage,
- [{#private_storage{usns =
- {LUser,
- LServer,
- '$1'},
- _ = '_'},
- [], ['$$']}]),
- lists:foreach(fun ([Namespace]) ->
- mnesia:delete({private_storage,
- {LUser, LServer,
- Namespace}})
- end,
- Namespaces)
- end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_user_private_storage(LServer,
- Username);
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete_by_index(private_storage,
- <<"us">>, {LUser, LServer})}.
-
-update_table() ->
- Fields = record_info(fields, private_storage),
- case mnesia:table_info(private_storage, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- private_storage, Fields, set,
- fun(#private_storage{usns = {U, _, _}}) -> U end,
- fun(#private_storage{usns = {U, S, NS}, xml = El} = R) ->
- R#private_storage{usns = {iolist_to_binary(U),
- iolist_to_binary(S),
- iolist_to_binary(NS)},
- xml = xml:to_xmlel(El)}
- end);
- _ ->
- ?INFO_MSG("Recreating private_storage table", []),
- mnesia:transform_table(private_storage, ignore, Fields)
- end.
-
-export(_Server) ->
- [{private_storage,
- fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
- xml = Data})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- LXMLNS = ejabberd_odbc:escape(XMLNS),
- SData =
- ejabberd_odbc:escape(xml:element_to_binary(Data)),
- odbc_queries:set_private_data_sql(Username, LXMLNS,
- SData);
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username, namespace, data from private_storage;">>,
- fun([LUser, XMLNS, XML]) ->
- El = #xmlel{} = xml_stream:parse_element(XML),
- #private_storage{usns = {LUser, LServer, XMLNS},
- xml = El}
- end}].
-
-import(_LServer, mnesia, #private_storage{} = PS) ->
- mnesia:dirty_write(PS);
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-import(_LServer, riak, #private_storage{usns = {LUser, LServer, _}} = PS) ->
- ejabberd_riak:put(PS, private_storage_schema(),
- [{'2i', [{<<"us">>, {LUser, LServer}}]}]);
-import(_, _, _) ->
- pass.
+import(LServer, DBType, PD) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, PD).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
diff --git a/src/mod_private_mnesia.erl b/src/mod_private_mnesia.erl
new file mode 100644
index 000000000..7a852c4f8
--- /dev/null
+++ b/src/mod_private_mnesia.erl
@@ -0,0 +1,97 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_private_mnesia).
+-behaviour(mod_private).
+
+%% API
+-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2,
+ import/2]).
+
+-include("jlib.hrl").
+-include("mod_private.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(private_storage,
+ [{disc_only_copies, [node()]},
+ {attributes,
+ record_info(fields, private_storage)}]),
+ update_table().
+
+set_data(LUser, LServer, Data) ->
+ F = fun () ->
+ lists:foreach(
+ fun({XmlNS, Xmlel}) ->
+ mnesia:write(
+ #private_storage{
+ usns = {LUser, LServer, XmlNS},
+ xml = Xmlel})
+ end, Data)
+ end,
+ mnesia:transaction(F).
+
+get_data(LUser, LServer, XmlNS) ->
+ case mnesia:dirty_read(private_storage, {LUser, LServer, XmlNS}) of
+ [#private_storage{xml = Storage_Xmlel}] ->
+ {ok, Storage_Xmlel};
+ _ ->
+ error
+ end.
+
+get_all_data(LUser, LServer) ->
+ lists:flatten(
+ mnesia:dirty_select(private_storage,
+ [{#private_storage{usns = {LUser, LServer, '_'},
+ xml = '$1'},
+ [], ['$1']}])).
+
+remove_user(LUser, LServer) ->
+ F = fun () ->
+ Namespaces = mnesia:select(private_storage,
+ [{#private_storage{usns =
+ {LUser,
+ LServer,
+ '$1'},
+ _ = '_'},
+ [], ['$$']}]),
+ lists:foreach(fun ([Namespace]) ->
+ mnesia:delete({private_storage,
+ {LUser, LServer,
+ Namespace}})
+ end,
+ Namespaces)
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #private_storage{} = PS) ->
+ mnesia:dirty_write(PS).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, private_storage),
+ case mnesia:table_info(private_storage, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ private_storage, Fields, set,
+ fun(#private_storage{usns = {U, _, _}}) -> U end,
+ fun(#private_storage{usns = {U, S, NS}, xml = El} = R) ->
+ R#private_storage{usns = {iolist_to_binary(U),
+ iolist_to_binary(S),
+ iolist_to_binary(NS)},
+ xml = fxml:to_xmlel(El)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating private_storage table", []),
+ mnesia:transform_table(private_storage, ignore, Fields)
+ end.
diff --git a/src/mod_private_riak.erl b/src/mod_private_riak.erl
new file mode 100644
index 000000000..11cfa4770
--- /dev/null
+++ b/src/mod_private_riak.erl
@@ -0,0 +1,67 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_private_riak).
+
+-behaviour(mod_private).
+
+%% API
+-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2,
+ import/2]).
+
+-include("jlib.hrl").
+-include("mod_private.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+set_data(LUser, LServer, Data) ->
+ lists:foreach(
+ fun({XMLNS, El}) ->
+ ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS},
+ xml = El},
+ private_storage_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}])
+ end, Data),
+ {atomic, ok}.
+
+get_data(LUser, LServer, XMLNS) ->
+ case ejabberd_riak:get(private_storage, private_storage_schema(),
+ {LUser, LServer, XMLNS}) of
+ {ok, #private_storage{xml = El}} ->
+ {ok, El};
+ _ ->
+ error
+ end.
+
+get_all_data(LUser, LServer) ->
+ case ejabberd_riak:get_by_index(
+ private_storage, private_storage_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Res} ->
+ [El || #private_storage{xml = El} <- Res];
+ _ ->
+ []
+ end.
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete_by_index(private_storage,
+ <<"us">>, {LUser, LServer})}.
+
+import(_LServer, #private_storage{usns = {LUser, LServer, _}} = PS) ->
+ ejabberd_riak:put(PS, private_storage_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+private_storage_schema() ->
+ {record_info(fields, private_storage), #private_storage{}}.
diff --git a/src/mod_private_sql.erl b/src/mod_private_sql.erl
new file mode 100644
index 000000000..6ec6c9df1
--- /dev/null
+++ b/src/mod_private_sql.erl
@@ -0,0 +1,97 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_private_sql).
+
+-behaviour(mod_private).
+
+%% API
+-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2,
+ import/1, import/2, export/1]).
+
+-include("jlib.hrl").
+-include("mod_private.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+set_data(LUser, LServer, Data) ->
+ F = fun() ->
+ lists:foreach(
+ fun({XMLNS, El}) ->
+ SData = fxml:element_to_binary(El),
+ sql_queries:set_private_data(
+ LServer, LUser, XMLNS, SData)
+ end, Data)
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+get_data(LUser, LServer, XMLNS) ->
+ case catch sql_queries:get_private_data(LServer, LUser, XMLNS) of
+ {selected, [{SData}]} ->
+ case fxml_stream:parse_element(SData) of
+ Data when is_record(Data, xmlel) ->
+ {ok, Data};
+ _ ->
+ error
+ end;
+ _ ->
+ error
+ end.
+
+get_all_data(LUser, LServer) ->
+ case catch sql_queries:get_private_data(LServer, LUser) of
+ {selected, Res} ->
+ lists:flatmap(
+ fun({_, SData}) ->
+ case fxml_stream:parse_element(SData) of
+ #xmlel{} = El ->
+ [El];
+ _ ->
+ []
+ end
+ end, Res);
+ _ ->
+ []
+ end.
+
+remove_user(LUser, LServer) ->
+ sql_queries:del_user_private_storage(LServer, LUser).
+
+export(_Server) ->
+ [{private_storage,
+ fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
+ xml = Data})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ LXMLNS = ejabberd_sql:escape(XMLNS),
+ SData =
+ ejabberd_sql:escape(fxml:element_to_binary(Data)),
+ sql_queries:set_private_data_sql(Username, LXMLNS,
+ SData);
+ (_Host, _R) ->
+ []
+ 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.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl
index 97d5b00af..d64afa04d 100644
--- a/src/mod_proxy65_service.erl
+++ b/src/mod_proxy65_service.erl
@@ -63,7 +63,7 @@ start_link(Host, Opts) ->
init([Host, Opts]) ->
State = parse_options(Host, Opts),
- ejabberd_router:register_route(State#state.myhost),
+ ejabberd_router:register_route(State#state.myhost, Host),
{ok, State}.
terminate(_Reason, #state{myhost = MyHost}) ->
@@ -150,7 +150,7 @@ process_iq(_,
children = iq_vcard(Lang)}]};
%% bytestreams info request
process_iq(JID,
- #iq{type = get, sub_el = SubEl,
+ #iq{type = get, sub_el = SubEl, lang = Lang,
xmlns = ?NS_BYTESTREAMS} =
IQ,
#state{acl = ACL, stream_addr = StreamAddr,
@@ -165,21 +165,22 @@ process_iq(JID,
attrs = [{<<"xmlns">>, ?NS_BYTESTREAMS}],
children = StreamHostEl}]};
deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ Txt = <<"Denied by ACL">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
end;
%% bytestream activation request
process_iq(InitiatorJID,
- #iq{type = set, sub_el = SubEl,
+ #iq{type = set, sub_el = SubEl, lang = Lang,
xmlns = ?NS_BYTESTREAMS} =
IQ,
#state{acl = ACL, serverhost = ServerHost}) ->
case acl:match_rule(ServerHost, ACL, InitiatorJID) of
allow ->
- ActivateEl = xml:get_path_s(SubEl,
+ ActivateEl = fxml:get_path_s(SubEl,
[{elem, <<"activate">>}]),
- SID = xml:get_tag_attr_s(<<"sid">>, SubEl),
+ SID = fxml:get_tag_attr_s(<<"sid">>, SubEl),
case catch
- jid:from_string(xml:get_tag_cdata(ActivateEl))
+ jid:from_string(fxml:get_tag_cdata(ActivateEl))
of
TargetJID
when is_record(TargetJID, jid), SID /= <<"">>,
@@ -194,22 +195,27 @@ process_iq(InitiatorJID,
of
ok -> IQ#iq{type = result, sub_el = []};
false ->
+ Txt = <<"Failed to activate bytestream">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
+ sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
limit ->
+ Txt = <<"Too many active bytestreams">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]};
+ sub_el = [SubEl, ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)]};
conflict ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_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;
_ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ Txt = <<"Malformed JID">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end;
deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ 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, _)
diff --git a/src/mod_proxy65_stream.erl b/src/mod_proxy65_stream.erl
index 36dd7af19..840173299 100644
--- a/src/mod_proxy65_stream.erl
+++ b/src/mod_proxy65_stream.erl
@@ -153,7 +153,7 @@ wait_for_auth(Packet,
#state{socket = Socket, host = Host} = StateData) ->
case mod_proxy65_lib:unpack_auth_request(Packet) of
{User, Pass} ->
- Result = ejabberd_auth:check_password(User, Host, Pass),
+ Result = ejabberd_auth:check_password(User, <<"">>, Host, Pass),
gen_tcp:send(Socket,
mod_proxy65_lib:make_auth_reply(Result)),
case Result of
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 616c64929..e42d5c058 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -63,7 +63,7 @@
%% exports for console debug manual use
-export([create_node/5, create_node/7, delete_node/3,
subscribe_node/5, unsubscribe_node/5, publish_item/6,
- delete_item/4, send_items/7, get_items/2, get_item/3,
+ delete_item/4, delete_item/5, send_items/7, get_items/2, get_item/3,
get_cached_item/2, get_configure/5, set_configure/5,
tree_action/3, node_action/4, node_call/4]).
@@ -241,6 +241,7 @@ stop(Host) ->
init([ServerHost, Opts]) ->
?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]),
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
+ ejabberd_router:register_route(Host, ServerHost),
Access = gen_mod:get_opt(access_createnode, Opts,
fun(A) when is_atom(A) -> A end, all),
PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts,
@@ -256,22 +257,26 @@ init([ServerHost, Opts]) ->
DefaultNodeCfg = gen_mod:get_opt(default_node_config, Opts,
fun(A) when is_list(A) -> filter_node_options(A) end, []),
pubsub_index:init(Host, ServerHost, Opts),
- ets:new(gen_mod:get_module_proc(ServerHost, config), [set, named_table]),
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
mnesia:create_table(pubsub_last_item,
[{ram_copies, [node()]},
{attributes, record_info(fields, pubsub_last_item)}]),
mod_disco:register_feature(ServerHost, ?NS_PUBSUB),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {nodetree, NodeTree}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {plugins, Plugins}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {last_item_cache, LastItemCache}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_items_node, MaxItemsNode}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_subscriptions_node, MaxSubsNode}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {default_node_config, DefaultNodeCfg}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {pep_mapping, PepMapping}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {ignore_pep_from_offline, PepOffline}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {host, Host}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {access, Access}),
+ lists:foreach(
+ fun(H) ->
+ T = gen_mod:get_module_proc(H, config),
+ ets:new(T, [set, named_table]),
+ ets:insert(T, {nodetree, NodeTree}),
+ ets:insert(T, {plugins, Plugins}),
+ ets:insert(T, {last_item_cache, LastItemCache}),
+ ets:insert(T, {max_items_node, MaxItemsNode}),
+ ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
+ ets:insert(T, {default_node_config, DefaultNodeCfg}),
+ ets:insert(T, {pep_mapping, PepMapping}),
+ ets:insert(T, {ignore_pep_from_offline, PepOffline}),
+ ets:insert(T, {host, Host}),
+ ets:insert(T, {access, Access})
+ end, [Host, ServerHost]),
ejabberd_hooks:add(sm_remove_connection_hook, ServerHost,
?MODULE, on_user_offline, 75),
ejabberd_hooks:add(disco_local_identity, ServerHost,
@@ -309,7 +314,6 @@ init([ServerHost, Opts]) ->
false ->
ok
end,
- ejabberd_router:register_route(Host),
pubsub_migrate:update_node_database(Host, ServerHost),
pubsub_migrate:update_state_database(Host, ServerHost),
pubsub_migrate:update_lastitem_database(Host, ServerHost),
@@ -482,7 +486,7 @@ send_loop(State) ->
-> [xmlel()]
).
disco_local_identity(Acc, _From, To, <<>>, _Lang) ->
- case lists:member(?PEPNODE, plugins(To#jid.lserver)) of
+ case lists:member(?PEPNODE, plugins(host(To#jid.lserver))) of
true ->
[#xmlel{name = <<"identity">>,
attrs = [{<<"category">>, <<"pubsub">>},
@@ -504,7 +508,7 @@ disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
-> [binary(),...]
).
disco_local_features(Acc, _From, To, <<>>, _Lang) ->
- Host = To#jid.lserver,
+ Host = host(To#jid.lserver),
Feats = case Acc of
{result, I} -> I;
_ -> []
@@ -873,7 +877,6 @@ handle_info(_Info, State) ->
%% @private
terminate(_Reason,
#state{host = Host, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
- ejabberd_router:unregister_route(Host),
case lists:member(?PEPNODE, Plugins) of
true ->
ejabberd_hooks:delete(caps_add, ServerHost,
@@ -918,7 +921,8 @@ terminate(_Reason,
Pid ->
Pid ! stop
end,
- terminate_plugins(Host, ServerHost, Plugins, TreePlugin).
+ terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
+ ejabberd_router:unregister_route(Host).
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
@@ -951,7 +955,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
case jlib:iq_query_info(Packet) of
#iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl, lang = Lang} = IQ ->
#xmlel{attrs = QAttrs} = SubEl,
- Node = xml:get_attr_s(<<"node">>, QAttrs),
+ Node = fxml:get_attr_s(<<"node">>, QAttrs),
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
[],
[ServerHost, ?MODULE, <<>>, <<>>]),
@@ -968,7 +972,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
ejabberd_router:route(To, From, Res);
#iq{type = get, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} = IQ ->
#xmlel{attrs = QAttrs} = SubEl,
- Node = xml:get_attr_s(<<"node">>, QAttrs),
+ Node = fxml:get_attr_s(<<"node">>, QAttrs),
Res = case iq_disco_items(Host, Node, From, jlib:rsm_decode(IQ)) of
{result, IQRes} ->
jlib:iq_to_xml(IQ#iq{type = result,
@@ -1022,7 +1026,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
ok
end;
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> ->
ok;
_ ->
@@ -1030,7 +1034,10 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
none ->
ok;
invalid ->
- Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
+ 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)
@@ -1040,7 +1047,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
ok
end;
_ ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> ->
ok;
<<"result">> ->
@@ -1236,7 +1243,7 @@ iq_get_vcard(Lang) ->
).
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
- iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)).
+ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(Host)).
-spec(iq_pubsub/8 ::
(
@@ -1255,16 +1262,16 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
#xmlel{children = SubEls} = SubEl,
- case xml:remove_cdata(SubEls) of
+ case fxml:remove_cdata(SubEls) of
[#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] ->
- Node = xml:get_attr_s(<<"node">>, Attrs),
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
case {IQType, Name} of
{set, <<"create">>} ->
Config = case Rest of
[#xmlel{name = <<"configure">>, children = C}] -> C;
_ -> []
end,
- Type = case xml:get_attr_s(<<"type">>, Attrs) of
+ Type = case fxml:get_attr_s(<<"type">>, Attrs) of
<<>> -> hd(Plugins);
T -> T
end,
@@ -1276,10 +1283,10 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
create_node(Host, ServerHost, Node, From, Type, Access, Config)
end;
{set, <<"publish">>} ->
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"item">>, attrs = ItemAttrs,
children = Payload}] ->
- ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
+ ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs),
publish_item(Host, ServerHost, Node, From, ItemId, Payload, Access);
[] ->
{error,
@@ -1289,14 +1296,14 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)}
end;
{set, <<"retract">>} ->
- ForceNotify = case xml:get_attr_s(<<"notify">>, Attrs) of
+ ForceNotify = case fxml:get_attr_s(<<"notify">>, Attrs) of
<<"1">> -> true;
<<"true">> -> true;
_ -> false
end,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"item">>, attrs = ItemAttrs}] ->
- ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
+ ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs),
delete_item(Host, Node, From, ItemId, ForceNotify);
_ ->
{error,
@@ -1307,37 +1314,37 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
[#xmlel{name = <<"options">>, children = C}] -> C;
_ -> []
end,
- JID = xml:get_attr_s(<<"jid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
subscribe_node(Host, Node, From, JID, Config);
{set, <<"unsubscribe">>} ->
- JID = xml:get_attr_s(<<"jid">>, Attrs),
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
unsubscribe_node(Host, Node, From, JID, SubId);
{get, <<"items">>} ->
- MaxItems = xml:get_attr_s(<<"max_items">>, Attrs),
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ MaxItems = fxml:get_attr_s(<<"max_items">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
ItemIds = lists:foldl(fun
(#xmlel{name = <<"item">>, attrs = ItemAttrs}, Acc) ->
- case xml:get_attr_s(<<"id">>, ItemAttrs) of
+ case fxml:get_attr_s(<<"id">>, ItemAttrs) of
<<>> -> Acc;
ItemId -> [ItemId | Acc]
end;
(_, Acc) ->
Acc
end,
- [], xml:remove_cdata(Els)),
+ [], fxml:remove_cdata(Els)),
get_items(Host, Node, From, SubId, MaxItems, ItemIds, jlib:rsm_decode(SubEl));
{get, <<"subscriptions">>} ->
get_subscriptions(Host, Node, From, Plugins);
{get, <<"affiliations">>} ->
get_affiliations(Host, Node, From, Plugins);
{get, <<"options">>} ->
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
- JID = xml:get_attr_s(<<"jid">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
get_options(Host, Node, JID, SubId, Lang);
{set, <<"options">>} ->
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
- JID = xml:get_attr_s(<<"jid">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
set_options(Host, Node, JID, SubId, Els);
_ ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}
@@ -1362,10 +1369,10 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
).
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
#xmlel{children = SubEls} = SubEl,
- Action = xml:remove_cdata(SubEls),
+ Action = fxml:remove_cdata(SubEls),
case Action of
[#xmlel{name = Name, attrs = Attrs, children = Els}] ->
- Node = xml:get_attr_s(<<"node">>, Attrs),
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
case {IQType, Name} of
{get, <<"configure">>} ->
get_configure(Host, ServerHost, Node, From, Lang);
@@ -1380,11 +1387,11 @@ iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
{get, <<"subscriptions">>} ->
get_subscriptions(Host, Node, From);
{set, <<"subscriptions">>} ->
- set_subscriptions(Host, Node, From, xml:remove_cdata(Els));
+ set_subscriptions(Host, Node, From, fxml:remove_cdata(Els));
{get, <<"affiliations">>} ->
get_affiliations(Host, Node, From);
{set, <<"affiliations">>} ->
- set_affiliations(Host, Node, From, xml:remove_cdata(Els));
+ set_affiliations(Host, Node, From, fxml:remove_cdata(Els));
_ ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}
end;
@@ -1414,13 +1421,14 @@ adhoc_request(Host, _ServerHost, Owner,
send_pending_node_form(Host, Owner, Lang, Plugins);
adhoc_request(Host, _ServerHost, Owner,
#adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
- action = <<"execute">>, xdata = XData},
+ action = <<"execute">>, xdata = XData, lang = Lang},
_Access, _Plugins) ->
ParseOptions = case XData of
#xmlel{name = <<"x">>} = XEl ->
case jlib:parse_xdata_submit(XEl) of
invalid ->
- {error, ?ERR_BAD_REQUEST};
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
XData2 ->
case set_xoption(Host, XData2, []) of
NewOpts when is_list(NewOpts) -> {result, NewOpts};
@@ -1428,8 +1436,8 @@ adhoc_request(Host, _ServerHost, Owner,
end
end;
_ ->
- ?INFO_MSG("Bad XForm: ~p", [XData]),
- {error, ?ERR_BAD_REQUEST}
+ Txt = <<"No data form found">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end,
case ParseOptions of
{result, XForm} ->
@@ -1459,7 +1467,9 @@ send_pending_node_form(Host, Owner, _Lang, Plugins) ->
end,
case lists:filter(Filter, Plugins) of
[] ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED};
+ Err = extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"get-pending">>),
+ {error, Err};
Ps ->
XOpts = [#xmlel{name = <<"option">>, attrs = [],
children = [#xmlel{name = <<"value">>,
@@ -1500,10 +1510,11 @@ send_pending_auth_events(Host, Node, Owner) ->
true ->
case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of
{result, owner} -> node_call(Host, Type, get_node_subscriptions, [Nidx]);
- _ -> {error, ?ERR_FORBIDDEN}
+ _ -> {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
end;
false ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"get-pending">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -1597,9 +1608,9 @@ find_authorization_response(Packet) ->
#xmlel{children = Els} = Packet,
XData1 = lists:map(fun
(#xmlel{name = <<"x">>, attrs = XAttrs} = XEl) ->
- case xml:get_attr_s(<<"xmlns">>, XAttrs) of
+ case fxml:get_attr_s(<<"xmlns">>, XAttrs) of
?NS_XDATA ->
- case xml:get_attr_s(<<"type">>, XAttrs) of
+ case fxml:get_attr_s(<<"type">>, XAttrs) of
<<"cancel">> -> none;
_ -> jlib:parse_xdata_submit(XEl)
end;
@@ -1609,7 +1620,7 @@ find_authorization_response(Packet) ->
(_) ->
none
end,
- xml:remove_cdata(Els)),
+ fxml:remove_cdata(Els)),
XData = lists:filter(fun (E) -> E /= none end, XData1),
case XData of
[invalid] ->
@@ -1640,6 +1651,7 @@ send_authorization_approval(Host, JID, SNode, Subscription) ->
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)}
@@ -1661,7 +1673,7 @@ handle_authorization_response(Host, From, To, Packet, XFields) ->
{result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]),
update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs);
false ->
- {error, ?ERR_FORBIDDEN}
+ {error, ?ERRT_FORBIDDEN(Lang, <<"You're not an owner">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -1676,7 +1688,8 @@ handle_authorization_response(Host, From, To, Packet, XFields) ->
ejabberd_router:route(To, From, Err)
end;
_ ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ACCEPTABLE),
+ Txt = <<"Incorrect data form">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end.
@@ -1687,7 +1700,7 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
end,
Subs),
case Sub of
- [{pending, SubId}] ->
+ [{pending, SubId}|_] ->
NewSub = case Allow of
true -> subscribed;
false -> none
@@ -1696,7 +1709,8 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
send_authorization_approval(Host, Subscriber, Node, NewSub),
{result, ok};
_ ->
- {error, ?ERR_UNEXPECTED_REQUEST}
+ Txt = <<"No pending subscriptions found">>,
+ {error, ?ERRT_UNEXPECTED_REQUEST(?MYLANG, Txt)}
end.
-define(XFIELD(Type, Label, Var, Val),
@@ -1775,6 +1789,20 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
%%<li>nodetree create_node checks if nodeid already exists</li>
%%<li>node plugin create_node just sets default affiliation/subscription</li>
%%</ul>
+-spec(create_node/5 ::
+ (
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: <<>> | mod_pubsub:nodeId(),
+ Owner :: jid(),
+ Type :: binary())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+ ).
+create_node(Host, ServerHost, Node, Owner, Type) ->
+ create_node(Host, ServerHost, Node, Owner, Type, all, []).
+
-spec(create_node/7 ::
(
Host :: mod_pubsub:host(),
@@ -1788,8 +1816,6 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
%%%
| {error, xmlel()}
).
-create_node(Host, ServerHost, Node, Owner, Type) ->
- create_node(Host, ServerHost, Node, Owner, Type, all, []).
create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
case lists:member(<<"instant-nodes">>, plugin_features(Host, Type)) of
true ->
@@ -1808,13 +1834,14 @@ create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
end;
create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
Type = select_type(ServerHost, Host, Node, GivenType),
- ParseOptions = case xml:remove_cdata(Configuration) of
+ ParseOptions = case fxml:remove_cdata(Configuration) of
[] ->
{result, node_options(Host, Type)};
[#xmlel{name = <<"x">>} = XEl] ->
case jlib:parse_xdata_submit(XEl) of
invalid ->
- {error, ?ERR_BAD_REQUEST};
+ 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};
@@ -1823,7 +1850,8 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
end;
_ ->
?INFO_MSG("Node ~p; bad configuration: ~p", [Node, Configuration]),
- {error, ?ERR_BAD_REQUEST}
+ Txt = <<"No data form found">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)}
end,
case ParseOptions of
{result, NodeOptions} ->
@@ -1860,7 +1888,8 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
Error
end;
_ ->
- {error, ?ERR_FORBIDDEN}
+ Txt1 = <<"You're not allowed to create nodes">>,
+ {error, ?ERRT_FORBIDDEN(?MYLANG, Txt1)}
end
end,
Reply = [#xmlel{name = <<"pubsub">>,
@@ -1910,7 +1939,7 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
| {error, xmlel()}
).
delete_node(_Host, <<>>, _Owner) ->
- {error, ?ERR_NOT_ALLOWED};
+ {error, ?ERRT_NOT_ALLOWED(?MYLANG, <<"No node specified">>)};
delete_node(Host, Node, Owner) ->
Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of
@@ -1922,7 +1951,7 @@ delete_node(Host, Node, Owner) ->
Error -> Error
end;
_ ->
- {error, ?ERR_FORBIDDEN}
+ {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
end
end,
Reply = [],
@@ -2231,22 +2260,28 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) ->
{result, Reply};
{result, {_, Result}} ->
{result, Result};
- {error, ?ERR_ITEM_NOT_FOUND} ->
- Type = select_type(ServerHost, Host, Node),
- case lists:member(<<"auto-create">>, plugin_features(Host, Type)) of
+ {error, _} = Error ->
+ case is_item_not_found(Error) 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}]}]}]} ->
- publish_item(Host, ServerHost, NewNode, Publisher, ItemId, Payload);
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ 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}]}]}]} ->
+ publish_item(Host, ServerHost, NewNode, Publisher, ItemId, Payload);
+ _ ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+ end;
+ false ->
+ Txt = <<"Automatic node creation is not enabled">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, Txt)}
end;
false ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ Error
end;
Error ->
Error
@@ -2400,7 +2435,9 @@ get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
end;
true ->
case catch jlib:binary_to_integer(SMaxItems) of
- {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ {'EXIT', _} ->
+ Txt = <<"Value of 'max_items' should be integer">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)};
Val -> Val
end
end,
@@ -2490,7 +2527,7 @@ get_last_item(Host, Type, Nidx, LJID, mnesia) ->
{result, {[LastItem|_], _}} -> LastItem;
_ -> undefined
end;
-get_last_item(Host, Type, Nidx, LJID, odbc) ->
+get_last_item(Host, Type, Nidx, LJID, sql) ->
case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of
{result, [LastItem]} -> LastItem;
_ -> undefined
@@ -2505,7 +2542,7 @@ get_last_items(Host, Type, Nidx, LJID, Number, mnesia) ->
{result, {Items, _}} -> lists:sublist(Items, Number);
_ -> []
end;
-get_last_items(Host, Type, Nidx, LJID, Number, odbc) ->
+get_last_items(Host, Type, Nidx, LJID, Number, sql) ->
case node_action(Host, Type, get_last_items, [Nidx, LJID, Number]) of
{result, Items} -> Items;
_ -> []
@@ -2623,7 +2660,7 @@ get_affiliations(Host, Node, JID) ->
{error,
extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"modify-affiliations">>)};
Affiliation /= owner ->
- {error, ?ERR_FORBIDDEN};
+ {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)};
true ->
node_call(Host, Type, get_node_affiliations, [Nidx])
end
@@ -2668,8 +2705,8 @@ set_affiliations(Host, Node, From, EntitiesEls) ->
(El, Acc) ->
case El of
#xmlel{name = <<"affiliation">>, attrs = Attrs} ->
- JID = jid:from_string(xml:get_attr_s(<<"jid">>, Attrs)),
- Affiliation = string_to_affiliation(xml:get_attr_s(<<"affiliation">>, Attrs)),
+ JID = jid:from_string(fxml:get_attr_s(<<"jid">>, Attrs)),
+ Affiliation = string_to_affiliation(fxml:get_attr_s(<<"affiliation">>, Attrs)),
if (JID == error) or (Affiliation == false) -> error;
true -> [{jid:tolower(JID), Affiliation} | Acc]
end
@@ -2716,7 +2753,7 @@ set_affiliations(Host, Node, From, EntitiesEls) ->
FilteredEntities),
{result, []};
_ ->
- {error, ?ERR_FORBIDDEN}
+ {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -2932,7 +2969,7 @@ get_subscriptions(Host, Node, JID) ->
{error,
extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"manage-subscriptions">>)};
Affiliation /= owner ->
- {error, ?ERR_FORBIDDEN};
+ {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)};
true ->
node_call(Host, Type, get_node_subscriptions, [Nidx])
end
@@ -2975,7 +3012,7 @@ get_subscriptions_for_send_last(Host, PType, mnesia, JID, LJID, BJID) ->
|| {Node, Sub, SubId, SubJID} <- Subs,
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID),
match_option(Node, send_last_published_item, on_sub_and_presence)];
-get_subscriptions_for_send_last(Host, PType, odbc, JID, LJID, BJID) ->
+get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) ->
case catch node_action(Host, PType,
get_entity_subscriptions_for_send_last,
[Host, JID])
@@ -2998,9 +3035,9 @@ set_subscriptions(Host, Node, From, EntitiesEls) ->
(El, Acc) ->
case El of
#xmlel{name = <<"subscription">>, attrs = Attrs} ->
- JID = jid:from_string(xml:get_attr_s(<<"jid">>, Attrs)),
- Sub = string_to_subscription(xml:get_attr_s(<<"subscription">>, Attrs)),
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ JID = jid:from_string(fxml:get_attr_s(<<"jid">>, Attrs)),
+ Sub = string_to_subscription(fxml:get_attr_s(<<"subscription">>, Attrs)),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
if (JID == error) or (Sub == false) -> error;
true -> [{jid:tolower(JID), Sub, SubId} | Acc]
end
@@ -3043,10 +3080,10 @@ set_subscriptions(Host, Node, From, EntitiesEls) ->
[], Entities),
case Result of
[] -> {result, []};
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ [{error, E}|_] -> {error, E}
end;
_ ->
- {error, ?ERR_FORBIDDEN}
+ {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -3170,17 +3207,15 @@ sub_option_can_deliver(_, _, _) -> true.
presence_can_deliver(_, false) ->
true;
presence_can_deliver({User, Server, Resource}, true) ->
- case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of
+ case ejabberd_sm:get_user_present_resources(User, Server) of
[] ->
false;
Ss ->
lists:foldl(fun
(_, true) ->
true;
- ({session, _, _, _, undefined, _}, _Acc) ->
- false;
- ({session, {_, _, R}, _, _, _Priority, _}, _Acc) ->
- case Resource of
+ ({_, R}, _Acc) ->
+ case Resource of
<<>> -> true;
R -> true;
_ -> false
@@ -3294,9 +3329,14 @@ broadcast_publish_item(Host, Node, Nidx, Type, NodeOptions, ItemId, From, Payloa
true -> Payload;
false -> []
end,
+ Attrs = case get_option(NodeOptions, itemreply, none) of
+ owner -> itemAttr(ItemId); %% owner not supported
+ publisher -> itemAttr(ItemId, {<<"publisher">>, jid:to_string(From)});
+ none -> itemAttr(ItemId)
+ end,
Stanza = event_stanza(
[#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
- children = [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId),
+ children = [#xmlel{name = <<"item">>, attrs = Attrs,
children = Content}]}]),
broadcast_stanza(Host, From, Node, Nidx, Type,
NodeOptions, SubsByDepth, items, Stanza, true),
@@ -3587,7 +3627,7 @@ get_configure(Host, ServerHost, Node, From, Lang) ->
children =
get_configure_xfields(Type, Options, Lang, Groups)}]}]}]};
_ ->
- {error, ?ERR_FORBIDDEN}
+ {error, ?ERRT_FORBIDDEN(Lang, <<"You're not an owner">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -3626,7 +3666,7 @@ get_option(Options, Var, Def) ->
end.
node_options(Host, Type) ->
- case config(serverhost(Host), default_node_config) of
+ case config(Host, default_node_config) of
undefined -> node_plugin_options(Host, Type);
[] -> node_plugin_options(Host, Type);
Config -> Config
@@ -3648,7 +3688,7 @@ filter_node_options(Options) ->
node_owners_action(Host, Type, Nidx, []) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
- odbc ->
+ sql ->
case node_action(Host, Type, get_node_affiliations, [Nidx]) of
{result, Affs} -> [LJID || {LJID, Aff} <- Affs, Aff =:= owner];
_ -> []
@@ -3661,7 +3701,7 @@ node_owners_action(_Host, _Type, _Nidx, Owners) ->
node_owners_call(Host, Type, Nidx, []) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
- odbc ->
+ sql ->
case node_call(Host, Type, get_node_affiliations, [Nidx]) of
{result, Affs} -> [LJID || {LJID, Aff} <- Affs, Aff =:= owner];
_ -> []
@@ -3777,7 +3817,9 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available users">>,
presence_based_delivery),
?NLIST_CONFIG_FIELD(<<"The collections with which a node is affiliated">>,
- collection)].
+ collection),
+ ?ALIST_CONFIG_FIELD(<<"Whether owners or publisher should receive replies to items">>,
+ itemreply, [none, owner, publisher])].
%%<p>There are several reasons why the node configuration request might fail:</p>
%%<ul>
@@ -3788,9 +3830,9 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
%%<li>The specified node does not exist.</li>
%%</ul>
set_configure(Host, Node, From, Els, Lang) ->
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>} = XEl] ->
- case {xml:get_tag_attr_s(<<"xmlns">>, XEl), xml:get_tag_attr_s(<<"type">>, XEl)} of
+ case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), fxml:get_tag_attr_s(<<"type">>, XEl)} of
{?NS_XDATA, <<"cancel">>} ->
{result, []};
{?NS_XDATA, <<"submit">>} ->
@@ -3799,7 +3841,8 @@ set_configure(Host, Node, From, Els, Lang) ->
{result, owner} ->
case jlib:parse_xdata_submit(XEl) of
invalid ->
- {error, ?ERR_BAD_REQUEST};
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
XData ->
OldOpts = case Options of
[] -> node_options(Host, Type);
@@ -3819,7 +3862,8 @@ set_configure(Host, Node, From, Els, Lang) ->
end
end;
_ ->
- {error, ?ERR_FORBIDDEN}
+ Txt = <<"You're not an owner">>,
+ {error, ?ERRT_FORBIDDEN(Lang, Txt)}
end
end,
case transaction(Host, Node, Action, transaction) of
@@ -3833,10 +3877,12 @@ set_configure(Host, Node, From, Els, Lang) ->
Other
end;
_ ->
- {error, ?ERR_BAD_REQUEST}
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
_ ->
- {error, ?ERR_BAD_REQUEST}
+ Txt = <<"No data form found">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end.
add_opt(Key, Value, Opts) ->
@@ -3851,7 +3897,10 @@ add_opt(Key, Value, Opts) ->
_ -> error
end,
case BoolVal of
- error -> {error, ?ERR_NOT_ACCEPTABLE};
+ 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).
@@ -3864,10 +3913,14 @@ add_opt(Key, Value, Opts) ->
if (Max =:= undefined) orelse (IVal =< Max) ->
set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
true ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ Txt = <<"Incorrect value of '~s'">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end;
_ ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end).
-define(SET_ALIST_XOPT(Opt, Val, Vals),
@@ -3875,7 +3928,9 @@ add_opt(Key, Value, Opts) ->
true ->
set_xoption(Host, Opts, add_opt(Opt, jlib:binary_to_atom(Val), NewOpts));
false ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ 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),
@@ -3931,25 +3986,21 @@ set_xoption(Host, [{<<"pubsub#collection">>, Value} | Opts], NewOpts) ->
set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts], NewOpts) ->
% NewValue = string_to_node(Value),
?SET_LIST_XOPT(node, Value);
+set_xoption(Host, [{<<"pubsub#itemreply">>, [Val]} | Opts], NewOpts) ->
+ ?SET_ALIST_XOPT(itemreply, Val, [none, owner, publisher]);
set_xoption(Host, [_ | Opts], NewOpts) ->
set_xoption(Host, Opts, NewOpts).
-get_max_items_node({_, ServerHost, _}) ->
- get_max_items_node(ServerHost);
get_max_items_node(Host) ->
- config(serverhost(Host), max_items_node, undefined).
+ config(Host, max_items_node, undefined).
-get_max_subscriptions_node({_, ServerHost, _}) ->
- get_max_subscriptions_node(ServerHost);
get_max_subscriptions_node(Host) ->
- config(serverhost(Host), max_subscriptions_node, undefined).
+ config(Host, max_subscriptions_node, undefined).
%%%% last item cache handling
-is_last_item_cache_enabled({_, ServerHost, _}) ->
- is_last_item_cache_enabled(ServerHost);
is_last_item_cache_enabled(Host) ->
- config(serverhost(Host), last_item_cache, false).
+ config(Host, last_item_cache, false).
set_cached_item({_, ServerHost, _}, Nidx, ItemId, Publisher, Payload) ->
set_cached_item(ServerHost, Nidx, ItemId, Publisher, Payload);
@@ -3999,19 +4050,13 @@ get_cached_item(Host, Nidx) ->
host(ServerHost) ->
config(ServerHost, host, <<"pubsub.", ServerHost/binary>>).
-serverhost({_U, Server, _R})->
- Server;
+serverhost({_U, ServerHost, _R})->
+ serverhost(ServerHost);
serverhost(Host) ->
- case binary:match(Host, <<"pubsub.">>) of
- {0,7} ->
- [_,ServerHost] = binary:split(Host, <<".">>),
- ServerHost;
- _ ->
- Host
- end.
+ ejabberd_router:host_of_route(Host).
tree(Host) ->
- case config(serverhost(Host), nodetree) of
+ case config(Host, nodetree) of
undefined -> tree(Host, ?STDTREE);
Tree -> Tree
end.
@@ -4021,19 +4066,19 @@ tree(_Host, <<"virtual">>) ->
tree(Host, Name) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
mnesia -> jlib:binary_to_atom(<<"nodetree_", Name/binary>>);
- odbc -> jlib:binary_to_atom(<<"nodetree_", Name/binary, "_odbc">>);
+ sql -> jlib:binary_to_atom(<<"nodetree_", Name/binary, "_sql">>);
_ -> Name
end.
plugin(Host, Name) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
mnesia -> jlib:binary_to_atom(<<"node_", Name/binary>>);
- odbc -> jlib:binary_to_atom(<<"node_", Name/binary, "_odbc">>);
+ sql -> jlib:binary_to_atom(<<"node_", Name/binary, "_sql">>);
_ -> Name
end.
plugins(Host) ->
- case config(serverhost(Host), plugins) of
+ case config(Host, plugins) of
undefined -> [?STDNODE];
[] -> [?STDNODE];
Plugins -> Plugins
@@ -4042,12 +4087,15 @@ plugins(Host) ->
subscription_plugin(Host) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
mnesia -> pubsub_subscription;
- odbc -> pubsub_subscription_odbc;
+ sql -> pubsub_subscription_sql;
_ -> none
end.
config(ServerHost, Key) ->
config(ServerHost, Key, undefined).
+
+config({_User, Host, _Resource}, Key, Default) ->
+ config(Host, Key, Default);
config(ServerHost, Key, Default) ->
case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), Key) of
[{Key, Value}] -> Value;
@@ -4064,14 +4112,14 @@ select_type(ServerHost, Host, Node, Type) ->
_ ->
Type
end,
- ConfiguredTypes = plugins(ServerHost),
+ ConfiguredTypes = plugins(Host),
case lists:member(SelectedType, ConfiguredTypes) of
true -> SelectedType;
false -> hd(ConfiguredTypes)
end.
select_type(ServerHost, Host, Node) ->
- select_type(ServerHost, Host, Node, hd(plugins(ServerHost))).
+ select_type(ServerHost, Host, Node, hd(plugins(Host))).
feature(<<"rsm">>) -> ?NS_RSM;
feature(Feature) -> <<(?NS_PUBSUB)/binary, "#", Feature/binary>>.
@@ -4125,8 +4173,9 @@ features(Host, Node) when is_binary(Node) ->
tree_call({_User, Server, _Resource}, Function, Args) ->
tree_call(Server, Function, Args);
tree_call(Host, Function, Args) ->
- ?DEBUG("tree_call ~p ~p ~p", [Host, Function, Args]),
- catch apply(tree(Host), Function, Args).
+ Tree = tree(Host),
+ ?DEBUG("tree_call apply(~s, ~s, ~p) @ ~s", [Tree, Function, Args, Host]),
+ catch apply(Tree, Function, Args).
tree_action(Host, Function, Args) ->
?DEBUG("tree_action ~p ~p ~p", [Host, Function, Args]),
@@ -4135,8 +4184,8 @@ tree_action(Host, Function, Args) ->
case gen_mod:db_type(ServerHost, ?MODULE) of
mnesia ->
catch mnesia:sync_dirty(Fun);
- odbc ->
- case catch ejabberd_odbc:sql_bloc(ServerHost, Fun) of
+ sql ->
+ case catch ejabberd_sql:sql_bloc(ServerHost, Fun) of
{atomic, Result} ->
Result;
{aborted, Reason} ->
@@ -4195,7 +4244,7 @@ transaction(Host, Fun, Trans) ->
ServerHost = serverhost(Host),
DBType = gen_mod:db_type(ServerHost, ?MODULE),
Retry = case DBType of
- odbc -> 2;
+ sql -> 2;
_ -> 1
end,
transaction_retry(Host, ServerHost, Fun, Trans, DBType, Retry).
@@ -4206,12 +4255,12 @@ transaction_retry(Host, ServerHost, Fun, Trans, DBType, Count) ->
Res = case DBType of
mnesia ->
catch mnesia:Trans(Fun);
- odbc ->
+ sql ->
SqlFun = case Trans of
transaction -> sql_transaction;
_ -> sql_bloc
end,
- catch ejabberd_odbc:SqlFun(ServerHost, Fun);
+ catch ejabberd_sql:SqlFun(ServerHost, Fun);
_ ->
{unsupported, DBType}
end,
@@ -4253,6 +4302,13 @@ extended_error(#xmlel{name = Error, attrs = Attrs, children = SubEls}, Ext, ExtA
#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.
+
string_to_ljid(JID) ->
case jid:from_string(JID) of
error ->
@@ -4273,6 +4329,7 @@ nodeAttr(Node) -> [{<<"node">>, Node}].
itemAttr([]) -> [];
itemAttr(ItemId) -> [{<<"id">>, ItemId}].
+itemAttr(ItemId, From) -> [{<<"id">>, ItemId}, From].
itemsEls(Items) ->
[#xmlel{name = <<"item">>, attrs = itemAttr(ItemId), children = Payload}
diff --git a/src/mod_register.erl b/src/mod_register.erl
index c7bfd963b..c1a7cab81 100644
--- a/src/mod_register.erl
+++ b/src/mod_register.erl
@@ -121,9 +121,9 @@ process_iq(From, To,
end,
case Type of
set ->
- UTag = xml:get_subtag(SubEl, <<"username">>),
- PTag = xml:get_subtag(SubEl, <<"password">>),
- RTag = xml:get_subtag(SubEl, <<"remove">>),
+ UTag = fxml:get_subtag(SubEl, <<"username">>),
+ PTag = fxml:get_subtag(SubEl, <<"password">>),
+ RTag = fxml:get_subtag(SubEl, <<"remove">>),
Server = To#jid.lserver,
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
fun(A) when is_atom(A) -> A end,
@@ -132,14 +132,14 @@ process_iq(From, To,
acl:match_rule(Server, Access, From),
if (UTag /= false) and (RTag /= false) and
AllowRemove ->
- User = xml:get_tag_cdata(UTag),
+ User = fxml:get_tag_cdata(UTag),
case From of
#jid{user = User, lserver = Server} ->
ejabberd_auth:remove_user(User, Server),
IQ#iq{type = result, sub_el = []};
_ ->
if PTag /= false ->
- Password = xml:get_tag_cdata(PTag),
+ Password = fxml:get_tag_cdata(PTag),
case ejabberd_auth:remove_user(User, Server,
Password)
of
@@ -151,21 +151,28 @@ process_iq(From, To,
%% modules. lists:foreach can
%% only return ok:
not_allowed ->
+ Txt = <<"Removal is not allowed">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ sub_el = [SubEl,
+ ?ERRT_NOT_ALLOWED(Lang, Txt)]};
not_exists ->
+ Txt = <<"No such user">>,
IQ#iq{type = error,
sub_el =
- [SubEl, ?ERR_ITEM_NOT_FOUND]};
- _ ->
+ [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, ?ERR_BAD_REQUEST]}
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end
end;
(UTag == false) and (RTag /= false) and AllowRemove ->
@@ -182,11 +189,13 @@ process_iq(From, To,
ejabberd_auth:remove_user(User, Server),
ignore;
_ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ 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 = xml:get_tag_cdata(UTag),
- Password = xml:get_tag_cdata(PTag),
+ User = fxml:get_tag_cdata(UTag),
+ Password = fxml:get_tag_cdata(PTag),
try_register_or_set_password(User, Server, Password,
From, IQ, SubEl, Source, Lang,
not IsCaptchaEnabled);
@@ -200,11 +209,14 @@ process_iq(From, To,
SubEl, Source, Lang,
true);
_ ->
+ Txt = <<"Incorrect data form">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end;
{error, malformed} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ 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,
@@ -344,7 +356,8 @@ try_register_or_set_password(User, Server, Password,
IQ#iq{type = error, sub_el = [SubEl, Error]}
end;
deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ 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]}
@@ -359,13 +372,17 @@ try_set_password(User, Server, Password, IQ, SubEl,
of
ok -> IQ#iq{type = result, sub_el = []};
{error, empty_password} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ Txt = <<"Empty password">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]};
{error, not_allowed} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Changing password is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
{error, invalid_jid} ->
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
- _ ->
+ sub_el = [SubEl, ?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]}
end;
@@ -377,7 +394,7 @@ try_set_password(User, Server, Password, IQ, SubEl,
try_register(User, Server, Password, SourceRaw, Lang) ->
case jid:is_nodename(User) of
- false -> {error, ?ERR_BAD_REQUEST};
+ false -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Malformed username">>)};
_ ->
JID = jid:make(User, Server, <<"">>),
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
@@ -387,8 +404,8 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
case {acl:match_rule(Server, Access, JID),
check_ip_access(SourceRaw, IPAccess)}
of
- {deny, _} -> {error, ?ERR_FORBIDDEN};
- {_, deny} -> {error, ?ERR_FORBIDDEN};
+ {deny, _} -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
+ {_, deny} -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
{allow, allow} ->
Source = may_remove_resource(SourceRaw),
case check_timeout(Source) of
@@ -406,14 +423,20 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
Error ->
remove_timeout(Source),
case Error of
- {atomic, exists} -> {error, ?ERR_CONFLICT};
+ {atomic, exists} ->
+ Txt = <<"User already exists">>,
+ {error, ?ERRT_CONFLICT(Lang, Txt)};
{error, invalid_jid} ->
{error, ?ERR_JID_MALFORMED};
{error, not_allowed} ->
{error, ?ERR_NOT_ALLOWED};
{error, too_many_users} ->
- {error, ?ERR_NOT_ALLOWED};
- {error, _Reason} ->
+ Txt = <<"Too many users registered">>,
+ {error, ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)};
+ {error, _} ->
+ ?ERROR_MSG("failed to register user "
+ "~s@~s: ~p",
+ [User, Server, Error]),
{error, ?ERR_INTERNAL_SERVER_ERROR}
end
end;
@@ -599,7 +622,7 @@ write_time({{Y, Mo, D}, {H, Mi, S}}) ->
[Y, Mo, D, H, Mi, S]).
process_xdata_submit(El) ->
- case xml:get_subtag(El, <<"x">>) of
+ case fxml:get_subtag(El, <<"x">>) of
false -> error;
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl
index 35f52f3b2..d93140312 100644
--- a/src/mod_register_web.erl
+++ b/src/mod_register_web.erl
@@ -113,8 +113,8 @@ process([<<"new">>],
end;
process([<<"delete">>],
#request{method = 'POST', q = Q, lang = Lang,
- host = Host}) ->
- case form_del_post(Q, Host) of
+ host = _HTTPHost}) ->
+ case form_del_post(Q) of
{atomic, ok} ->
Text = (?T(<<"Your Jabber account was successfully "
"deleted.">>)),
@@ -129,8 +129,8 @@ process([<<"delete">>],
%% should include the host where the POST was sent.
process([<<"change_password">>],
#request{method = 'POST', q = Q, lang = Lang,
- host = Host}) ->
- case form_changepass_post(Q, Host) of
+ host = _HTTPHost}) ->
+ case form_changepass_post(Q) of
{atomic, ok} ->
Text = (?T(<<"The password of your Jabber account "
"was successfully changed.">>)),
@@ -282,9 +282,9 @@ form_new_post(Q) ->
case catch get_register_parameters(Q) of
[Username, Host, Password, Password, Id, Key] ->
form_new_post(Username, Host, Password, {Id, Key});
- [_Username, _Password, _Password2, false, false] ->
+ [_Username, _Host, _Password, _Password2, false, false] ->
{error, passwords_not_identical};
- [_Username, _Password, _Password2, Id, Key] ->
+ [_Username, _Host, _Password, _Password2, Id, Key] ->
ejabberd_captcha:check_captcha(Id, Key),
{error, passwords_not_identical};
_ -> {error, wrong_parameters}
@@ -361,7 +361,8 @@ form_changepass_get(Host, Lang) ->
?INPUTS(<<"text">>, <<"username">>, <<"">>,
<<"20">>)]),
?XE(<<"li">>,
- [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ [?CT(<<"Server:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"host">>, Host, <<"20">>)]),
?XE(<<"li">>,
[?CT(<<"Old Password:">>), ?C(<<" ">>),
?INPUTS(<<"password">>, <<"passwordold">>, <<"">>,
@@ -386,12 +387,12 @@ form_changepass_get(Host, Lang) ->
%%% Formulary change password POST
%%%----------------------------------------------------------------------
-form_changepass_post(Q, Host) ->
+form_changepass_post(Q) ->
case catch get_changepass_parameters(Q) of
- [Username, PasswordOld, Password, Password] ->
+ [Username, Host, PasswordOld, Password, Password] ->
try_change_password(Username, Host, PasswordOld,
Password);
- [_Username, _PasswordOld, _Password, _Password2] ->
+ [_Username, _Host, _PasswordOld, _Password, _Password2] ->
{error, passwords_not_identical};
_ -> {error, wrong_parameters}
end.
@@ -405,7 +406,7 @@ get_changepass_parameters(Q) ->
{value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
Value
end,
- [<<"username">>, <<"passwordold">>, <<"password">>,
+ [<<"username">>, <<"host">>, <<"passwordold">>, <<"password">>,
<<"password2">>]).
try_change_password(Username, Host, PasswordOld,
@@ -437,7 +438,7 @@ check_account_exists(Username, Host) ->
end.
check_password(Username, Host, Password) ->
- case ejabberd_auth:check_password(Username, Host,
+ case ejabberd_auth:check_password(Username, <<"">>, Host,
Password)
of
true -> password_correct;
@@ -470,7 +471,8 @@ form_del_get(Host, Lang) ->
?INPUTS(<<"text">>, <<"username">>, <<"">>,
<<"20">>)]),
?XE(<<"li">>,
- [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ [?CT(<<"Server:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"host">>, Host, <<"20">>)]),
?XE(<<"li">>,
[?CT(<<"Password:">>), ?C(<<" ">>),
?INPUTS(<<"password">>, <<"password">>, <<"">>,
@@ -513,9 +515,9 @@ register_account2(Username, Host, Password) ->
%%% Formulary delete POST
%%%----------------------------------------------------------------------
-form_del_post(Q, Host) ->
+form_del_post(Q) ->
case catch get_unregister_parameters(Q) of
- [Username, Password] ->
+ [Username, Host, Password] ->
try_unregister_account(Username, Host, Password);
_ -> {error, wrong_parameters}
end.
@@ -529,7 +531,7 @@ get_unregister_parameters(Q) ->
{value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
Value
end,
- [<<"username">>, <<"password">>]).
+ [<<"username">>, <<"host">>, <<"password">>]).
try_unregister_account(Username, Host, Password) ->
try unregister_account(Username, Host, Password) of
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index adc2210db..16354dd8f 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -49,8 +49,7 @@
get_jid_info/4, item_to_xml/1, webadmin_page/3,
webadmin_user/4, get_versioning_feature/2,
roster_versioning_enabled/1, roster_version/2,
- record_to_string/1, groups_to_string/1,
- mod_opt_type/1]).
+ mod_opt_type/1, set_roster/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -65,23 +64,27 @@
-export_type([subscription/0]).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #roster{} | #roster_version{}) -> ok | pass.
+-callback read_roster_version(binary(), binary()) -> binary() | error.
+-callback write_roster_version(binary(), binary(), boolean(), binary()) -> any().
+-callback get_roster(binary(), binary()) -> [#roster{}].
+-callback get_roster_by_jid(binary(), binary(), ljid()) -> #roster{}.
+-callback get_only_items(binary(), binary()) -> [#roster{}].
+-callback roster_subscribe(binary(), binary(), ljid(), #roster{}) -> any().
+-callback transaction(binary(), function()) -> {atomic, any()} | {aborted, any()}.
+-callback get_roster_by_jid_with_groups(binary(), binary(), ljid()) -> #roster{}.
+-callback remove_user(binary(), binary()) -> {atomic, any()}.
+-callback update_roster(binary(), binary(), ljid(), #roster{}) -> any().
+-callback del_roster(binary(), binary(), ljid()) -> any().
+-callback read_subscription_and_groups(binary(), binary(), ljid()) ->
+ {subscription(), [binary()]}.
+
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(roster,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, roster)}]),
- mnesia:create_table(roster_version,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, roster_version)}]),
- update_tables(),
- mnesia:add_table_index(roster, us),
- mnesia:add_table_index(roster_version, us);
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(roster_get, Host, ?MODULE,
get_user_roster, 50),
ejabberd_hooks:add(roster_in_subscription, Host,
@@ -137,13 +140,14 @@ process_iq(From, To, IQ) when ((From#jid.luser == <<"">>) andalso (From#jid.reso
process_iq_manager(From, To, IQ);
process_iq(From, To, IQ) ->
- #iq{sub_el = SubEl} = 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, ?ERR_ITEM_NOT_FOUND]}
+ sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]}
end.
process_local_iq(From, To, #iq{type = Type} = IQ) ->
@@ -193,28 +197,8 @@ roster_version(LServer, LUser) ->
end.
read_roster_version(LUser, LServer) ->
- read_roster_version(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-read_roster_version(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- case mnesia:dirty_read(roster_version, US) of
- [#roster_version{version = V}] -> V;
- [] -> error
- end;
-read_roster_version(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case odbc_queries:get_roster_version(LServer, Username)
- of
- {selected, [<<"version">>], [[Version]]} -> Version;
- {selected, [<<"version">>], []} -> error
- end;
-read_roster_version(LServer, LUser, riak) ->
- case ejabberd_riak:get(roster_version, roster_version_schema(),
- {LUser, LServer}) of
- {ok, #roster_version{version = V}} -> V;
- _Err -> error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:read_roster_version(LUser, LServer).
write_roster_version(LUser, LServer) ->
write_roster_version(LUser, LServer, false).
@@ -224,38 +208,10 @@ write_roster_version_t(LUser, LServer) ->
write_roster_version(LUser, LServer, InTransaction) ->
Ver = p1_sha:sha(term_to_binary(p1_time_compat:unique_integer())),
- write_roster_version(LUser, LServer, InTransaction, Ver,
- gen_mod:db_type(LServer, ?MODULE)),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:write_roster_version(LUser, LServer, InTransaction, Ver),
Ver.
-write_roster_version(LUser, LServer, InTransaction, Ver,
- mnesia) ->
- US = {LUser, LServer},
- if InTransaction ->
- mnesia:write(#roster_version{us = US, version = Ver});
- true ->
- mnesia:dirty_write(#roster_version{us = US,
- version = Ver})
- end;
-write_roster_version(LUser, LServer, InTransaction, Ver,
- odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- EVer = ejabberd_odbc:escape(Ver),
- if InTransaction ->
- odbc_queries:set_roster_version(Username, EVer);
- true ->
- odbc_queries:sql_transaction(LServer,
- fun () ->
- odbc_queries:set_roster_version(Username,
- EVer)
- end)
- end;
-write_roster_version(LUser, LServer, _InTransaction, Ver,
- riak) ->
- US = {LUser, LServer},
- ejabberd_riak:put(#roster_version{us = US, version = Ver},
- roster_version_schema()).
-
%% Load roster from DB only if neccesary.
%% It is neccesary if
%% - roster versioning is disabled in server OR
@@ -267,7 +223,7 @@ process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) ->
LServer = From#jid.lserver,
US = {LUser, LServer},
try {ItemsToSend, VersionToSend} = case
- {xml:get_tag_attr(<<"ver">>, SubEl),
+ {fxml:get_tag_attr(<<"ver">>, SubEl),
roster_versioning_enabled(LServer),
roster_version_on_db(LServer)}
of
@@ -351,65 +307,15 @@ get_user_roster(Acc, {LUser, LServer}) ->
++ Acc.
get_roster(LUser, LServer) ->
- get_roster(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_roster(LUser, LServer).
-get_roster(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- case catch mnesia:dirty_index_read(roster, US,
- #roster.us)
- of
- Items when is_list(Items)-> Items;
- _ -> []
- end;
-get_roster(LUser, LServer, riak) ->
- case ejabberd_riak:get_by_index(roster, roster_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Items} -> Items;
- _Err -> []
- end;
-get_roster(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_roster(LServer, Username) of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Items}
- when is_list(Items) ->
- JIDGroups = case catch
- odbc_queries:get_roster_jid_groups(LServer,
- Username)
- of
- {selected, [<<"jid">>, <<"grp">>], JGrps}
- when is_list(JGrps) ->
- JGrps;
- _ -> []
- end,
- GroupsDict = lists:foldl(fun ([J, G], Acc) ->
- dict:append(J, G, Acc)
- end,
- dict:new(), JIDGroups),
- RItems = lists:flatmap(fun (I) ->
- case raw_to_record(LServer, I) of
- %% Bad JID in database:
- error -> [];
- R ->
- SJID =
- jid:to_string(R#roster.jid),
- Groups = case dict:find(SJID,
- GroupsDict)
- of
- {ok, Gs} -> Gs;
- error -> []
- end,
- [R#roster{groups = Groups}]
- end
- end,
- Items),
- RItems;
- _ -> []
- end.
+set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
+ transaction(
+ LServer,
+ fun() ->
+ roster_subscribe_t(LUser, LServer, LJID, Item)
+ end).
item_to_xml(Item) ->
Attrs1 = [{<<"jid">>,
@@ -440,61 +346,16 @@ item_to_xml(Item) ->
children = SubEls}.
get_roster_by_jid_t(LUser, LServer, LJID) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- get_roster_by_jid_t(LUser, LServer, LJID, DBType).
-
-get_roster_by_jid_t(LUser, LServer, LJID, mnesia) ->
- case mnesia:read({roster, {LUser, LServer, LJID}}) of
- [] ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- [I] ->
- I#roster{jid = LJID, name = <<"">>, groups = [],
- xs = []}
- end;
-get_roster_by_jid_t(LUser, LServer, LJID, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Res} =
- odbc_queries:get_roster_by_jid(LServer, Username, SJID),
- case Res of
- [] ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- [I] ->
- R = raw_to_record(LServer, I),
- case R of
- %% Bad JID in database:
- error ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- _ ->
- R#roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID, name = <<"">>}
- end
- end;
-get_roster_by_jid_t(LUser, LServer, LJID, riak) ->
- case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
- {ok, I} ->
- I#roster{jid = LJID, name = <<"">>, groups = [],
- xs = []};
- {error, notfound} ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- Err ->
- exit(Err)
- end.
+ 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} = IQ) ->
+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) when is_atom(A) -> A end, all),
case acl:match_rule(Server, Access, From) of
deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Denied by ACL">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
allow ->
process_iq_set(From, To, IQ)
end.
@@ -509,7 +370,7 @@ process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) ->
process_item_set(From, To,
#xmlel{attrs = Attrs, children = Els}, Managed) ->
- JID1 = jid:from_string(xml:get_attr_s(<<"jid">>,
+ JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
Attrs)),
#jid{user = User, luser = LUser, lserver = LServer} =
From,
@@ -578,10 +439,10 @@ process_item_els(Item,
| Els]) ->
case Name of
<<"group">> ->
- Groups = [xml:get_cdata(SEls) | Item#roster.groups],
+ Groups = [fxml:get_cdata(SEls) | Item#roster.groups],
process_item_els(Item#roster{groups = Groups}, Els);
_ ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"">> -> process_item_els(Item, Els);
_ ->
XEls = [#xmlel{name = Name, attrs = Attrs,
@@ -640,86 +501,37 @@ push_item_version(Server, User, From, Item,
end,
ejabberd_sm:get_user_resources(User, Server)).
-get_subscription_lists(Acc, User, Server) ->
+get_subscription_lists(_Acc, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- Items = get_subscription_lists(Acc, LUser, LServer,
- DBType),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Items = Mod:get_only_items(LUser, LServer),
fill_subscription_lists(LServer, Items, [], []).
-get_subscription_lists(_, LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- case mnesia:dirty_index_read(roster, US, #roster.us) of
- Items when is_list(Items) -> Items;
- _ -> []
- end;
-get_subscription_lists(_, LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_roster(LServer, Username) of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Items}
- when is_list(Items) ->
- lists:map(fun(I) -> raw_to_record(LServer, I) end, Items);
- _ -> []
- end;
-get_subscription_lists(_, LUser, LServer, riak) ->
- case ejabberd_riak:get_by_index(roster, roster_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Items} -> Items;
- _Err -> []
- end.
-
-fill_subscription_lists(LServer, [#roster{} = I | Is],
- F, T) ->
+fill_subscription_lists(LServer, [I | Is], F, T) ->
J = element(3, I#roster.usj),
case I#roster.subscription of
- both ->
- fill_subscription_lists(LServer, Is, [J | F], [J | T]);
- from ->
- fill_subscription_lists(LServer, Is, [J | F], T);
- to -> fill_subscription_lists(LServer, Is, F, [J | T]);
- _ -> fill_subscription_lists(LServer, Is, F, T)
- end;
-fill_subscription_lists(LServer, [RawI | Is], F, T) ->
- I = raw_to_record(LServer, RawI),
- case I of
- %% Bad JID in database:
- error -> fill_subscription_lists(LServer, Is, F, T);
- _ -> fill_subscription_lists(LServer, [I | Is], F, T)
+ both ->
+ fill_subscription_lists(LServer, Is, [J | F], [J | T]);
+ from ->
+ fill_subscription_lists(LServer, Is, [J | F], T);
+ to -> fill_subscription_lists(LServer, Is, F, [J | T]);
+ _ -> fill_subscription_lists(LServer, Is, F, T)
end;
-fill_subscription_lists(_LServer, [], F, T) -> {F, T}.
+fill_subscription_lists(_LServer, [], F, T) ->
+ {F, T}.
ask_to_pending(subscribe) -> out;
ask_to_pending(unsubscribe) -> none;
ask_to_pending(Ask) -> Ask.
roster_subscribe_t(LUser, LServer, LJID, Item) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- roster_subscribe_t(LUser, LServer, LJID, Item, DBType).
-
-roster_subscribe_t(_LUser, _LServer, _LJID, Item,
- mnesia) ->
- mnesia:write(Item);
-roster_subscribe_t(LUser, LServer, LJID, Item, odbc) ->
- ItemVals = record_to_string(Item),
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- odbc_queries:roster_subscribe(LServer, Username, SJID,
- ItemVals);
-roster_subscribe_t(LUser, LServer, _LJID, Item, riak) ->
- ejabberd_riak:put(Item, roster_schema(),
- [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:roster_subscribe(LUser, LServer, LJID, Item).
transaction(LServer, F) ->
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia -> mnesia:transaction(F);
- odbc -> ejabberd_odbc:sql_transaction(LServer, F);
- riak -> {atomic, F()}
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:transaction(LServer, F).
in_subscription(_, User, Server, JID, Type, Reason) ->
process_subscription(in, User, Server, JID, Type,
@@ -729,57 +541,8 @@ out_subscription(User, Server, JID, Type) ->
process_subscription(out, User, Server, JID, Type, <<"">>).
get_roster_by_jid_with_groups_t(LUser, LServer, LJID) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
- DBType).
-
-get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
- mnesia) ->
- case mnesia:read({roster, {LUser, LServer, LJID}}) of
- [] ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- [I] -> I
- end;
-get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
- odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- case odbc_queries:get_roster_by_jid(LServer, Username,
- SJID)
- of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- [I]} ->
- R = raw_to_record(LServer, I),
- Groups = case odbc_queries:get_roster_groups(LServer,
- Username, SJID)
- of
- {selected, [<<"grp">>], JGrps} when is_list(JGrps) ->
- [JGrp || [JGrp] <- JGrps];
- _ -> []
- end,
- R#roster{groups = Groups};
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- []} ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID}
- end;
-get_roster_by_jid_with_groups_t(LUser, LServer, LJID, riak) ->
- case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
- {ok, I} ->
- I;
- {error, notfound} ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- Err ->
- exit(Err)
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_roster_by_jid_with_groups(LUser, LServer, LJID).
process_subscription(Direction, User, Server, JID1,
Type, Reason) ->
@@ -977,22 +740,8 @@ remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
send_unsubscription_to_rosteritems(LUser, LServer),
- remove_user(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_user(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- lists:foreach(fun (R) -> mnesia:delete_object(R) end,
- mnesia:index_read(roster, US, #roster.us))
- end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_user_roster_t(LServer, Username),
- ok;
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
%% For each contact with Subscription:
%% Both or From, send a "unsubscribed" presence stanza;
@@ -1050,39 +799,16 @@ set_items(User, Server, SubEl) ->
transaction(LServer, F).
update_roster_t(LUser, LServer, LJID, Item) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- update_roster_t(LUser, LServer, LJID, Item, DBType).
-
-update_roster_t(_LUser, _LServer, _LJID, Item,
- mnesia) ->
- mnesia:write(Item);
-update_roster_t(LUser, LServer, LJID, Item, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- ItemVals = record_to_string(Item),
- ItemGroups = groups_to_string(Item),
- odbc_queries:update_roster(LServer, Username, SJID, ItemVals,
- ItemGroups);
-update_roster_t(LUser, LServer, _LJID, Item, riak) ->
- ejabberd_riak:put(Item, roster_schema(),
- [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:update_roster(LUser, LServer, LJID, Item).
del_roster_t(LUser, LServer, LJID) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- del_roster_t(LUser, LServer, LJID, DBType).
-
-del_roster_t(LUser, LServer, LJID, mnesia) ->
- mnesia:delete({roster, {LUser, LServer, LJID}});
-del_roster_t(LUser, LServer, LJID, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- odbc_queries:del_roster(LServer, Username, SJID);
-del_roster_t(LUser, LServer, LJID, riak) ->
- ejabberd_riak:delete(roster, {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(xml:get_attr_s(<<"jid">>,
+ JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
Attrs)),
case JID1 of
error -> ok;
@@ -1141,13 +867,12 @@ process_item_attrs_ws(Item, []) -> Item.
get_in_pending_subscriptions(Ls, User, Server) ->
LServer = jid:nameprep(Server),
- get_in_pending_subscriptions(Ls, User, Server,
- gen_mod:db_type(LServer, ?MODULE)).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ get_in_pending_subscriptions(Ls, User, Server, Mod).
-get_in_pending_subscriptions(Ls, User, Server, DBType)
- when DBType == mnesia; DBType == riak ->
+get_in_pending_subscriptions(Ls, User, Server, Mod) ->
JID = jid:make(User, Server, <<"">>),
- Result = get_roster(JID#jid.luser, JID#jid.lserver, DBType),
+ 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);
@@ -1172,102 +897,15 @@ get_in_pending_subscriptions(Ls, User, Server, DBType)
_ -> false
end
end,
- Result));
-get_in_pending_subscriptions(Ls, User, Server, odbc) ->
- JID = jid:make(User, Server, <<"">>),
- LUser = JID#jid.luser,
- LServer = JID#jid.lserver,
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_roster(LServer, Username) of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Items}
- when is_list(Items) ->
- Ls ++
- lists:map(fun (R) ->
- Message = R#roster.askmessage,
- #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, Message}]}]}
- end,
- lists:flatmap(fun (I) ->
- case raw_to_record(LServer, I) of
- %% Bad JID in database:
- error -> [];
- R ->
- case R#roster.ask of
- in -> [R];
- both -> [R];
- _ -> []
- end
- end
- end,
- Items));
- _ -> Ls
- end.
+ Result)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
read_subscription_and_groups(User, Server, LJID) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- read_subscription_and_groups(LUser, LServer, LJID,
- gen_mod:db_type(LServer, ?MODULE)).
-
-read_subscription_and_groups(LUser, LServer, LJID,
- mnesia) ->
- case catch mnesia:dirty_read(roster,
- {LUser, LServer, LJID})
- of
- [#roster{subscription = Subscription,
- groups = Groups}] ->
- {Subscription, Groups};
- _ -> error
- end;
-read_subscription_and_groups(LUser, LServer, LJID,
- odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- case catch odbc_queries:get_subscription(LServer,
- Username, SJID)
- of
- {selected, [<<"subscription">>], [[SSubscription]]} ->
- Subscription = case SSubscription of
- <<"B">> -> both;
- <<"T">> -> to;
- <<"F">> -> from;
- _ -> none
- end,
- Groups = case catch
- odbc_queries:get_rostergroup_by_jid(LServer, Username,
- SJID)
- of
- {selected, [<<"grp">>], JGrps} when is_list(JGrps) ->
- [JGrp || [JGrp] <- JGrps];
- _ -> []
- end,
- {Subscription, Groups};
- _ -> error
- end;
-read_subscription_and_groups(LUser, LServer, LJID,
- riak) ->
- case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
- {ok, #roster{subscription = Subscription,
- groups = Groups}} ->
- {Subscription, Groups};
- _ ->
- error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:read_subscription_and_groups(LUser, LServer, LJID).
get_jid_info(_, User, Server, JID) ->
LJID = jid:tolower(JID),
@@ -1287,128 +925,6 @@ get_jid_info(_, User, Server, JID) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-raw_to_record(LServer,
- [User, SJID, Nick, SSubscription, SAsk, SAskMessage,
- _SServer, _SSubscribe, _SType]) ->
- case jid:from_string(SJID) of
- error -> error;
- JID ->
- LJID = jid:tolower(JID),
- Subscription = case SSubscription of
- <<"B">> -> both;
- <<"T">> -> to;
- <<"F">> -> from;
- _ -> none
- end,
- Ask = case SAsk of
- <<"S">> -> subscribe;
- <<"U">> -> unsubscribe;
- <<"B">> -> both;
- <<"O">> -> out;
- <<"I">> -> in;
- _ -> none
- end,
- #roster{usj = {User, LServer, LJID},
- us = {User, LServer}, jid = LJID, name = Nick,
- subscription = Subscription, ask = Ask,
- askmessage = SAskMessage}
- end.
-
-record_to_string(#roster{us = {User, _Server},
- jid = JID, name = Name, subscription = Subscription,
- ask = Ask, askmessage = AskMessage}) ->
- Username = ejabberd_odbc:escape(User),
- SJID =
- ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))),
- Nick = ejabberd_odbc:escape(Name),
- SSubscription = case Subscription of
- both -> <<"B">>;
- to -> <<"T">>;
- from -> <<"F">>;
- none -> <<"N">>
- end,
- SAsk = case Ask of
- subscribe -> <<"S">>;
- unsubscribe -> <<"U">>;
- both -> <<"B">>;
- out -> <<"O">>;
- in -> <<"I">>;
- none -> <<"N">>
- end,
- SAskMessage = ejabberd_odbc:escape(AskMessage),
- [Username, SJID, Nick, SSubscription, SAsk, SAskMessage,
- <<"N">>, <<"">>, <<"item">>].
-
-groups_to_string(#roster{us = {User, _Server},
- jid = JID, groups = Groups}) ->
- Username = ejabberd_odbc:escape(User),
- SJID =
- ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))),
- lists:foldl(fun (<<"">>, Acc) -> Acc;
- (Group, Acc) ->
- G = ejabberd_odbc:escape(Group),
- [[Username, SJID, G] | Acc]
- end,
- [], Groups).
-
-update_tables() ->
- update_roster_table(),
- update_roster_version_table().
-
-update_roster_table() ->
- Fields = record_info(fields, roster),
- case mnesia:table_info(roster, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- roster, Fields, set,
- fun(#roster{usj = {U, _, _}}) -> U end,
- fun(#roster{usj = {U, S, {LU, LS, LR}},
- us = {U1, S1},
- jid = {U2, S2, R2},
- name = Name,
- groups = Gs,
- askmessage = Ask,
- xs = Xs} = R) ->
- R#roster{usj = {iolist_to_binary(U),
- iolist_to_binary(S),
- {iolist_to_binary(LU),
- iolist_to_binary(LS),
- iolist_to_binary(LR)}},
- us = {iolist_to_binary(U1),
- iolist_to_binary(S1)},
- jid = {iolist_to_binary(U2),
- iolist_to_binary(S2),
- iolist_to_binary(R2)},
- name = iolist_to_binary(Name),
- groups = [iolist_to_binary(G) || G <- Gs],
- askmessage = try iolist_to_binary(Ask)
- catch _:_ -> <<"">> end,
- xs = [xml:to_xmlel(X) || X <- Xs]}
- end);
- _ ->
- ?INFO_MSG("Recreating roster table", []),
- mnesia:transform_table(roster, ignore, Fields)
- end.
-
-%% Convert roster table to support virtual host
-%% Convert roster table: xattrs fields become
-update_roster_version_table() ->
- Fields = record_info(fields, roster_version),
- case mnesia:table_info(roster_version, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- roster_version, Fields, set,
- fun(#roster_version{us = {U, _}}) -> U end,
- fun(#roster_version{us = {U, S}, version = Ver} = R) ->
- R#roster_version{us = {iolist_to_binary(U),
- iolist_to_binary(S)},
- version = iolist_to_binary(Ver)}
- end);
- _ ->
- ?INFO_MSG("Recreating roster_version table", []),
- mnesia:transform_table(roster_version, ignore, Fields)
- end.
-
webadmin_page(_, Host,
#request{us = _US, path = [<<"user">>, U, <<"roster">>],
q = Query, lang = Lang} =
@@ -1632,8 +1148,9 @@ process_iq_manager(From, To, IQ) ->
true ->
process_iq_manager2(MatchDomain, To, IQ);
false ->
- #iq{sub_el = SubEl} = IQ,
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ #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) ->
@@ -1705,68 +1222,17 @@ is_managed_from_id(<<"roster-remotely-managed">>) ->
is_managed_from_id(_Id) ->
false.
-roster_schema() ->
- {record_info(fields, roster), #roster{}}.
-
-roster_version_schema() ->
- {record_info(fields, roster_version), #roster_version{}}.
-
-export(_Server) ->
- [{roster,
- fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- ItemVals = record_to_string(R),
- ItemGroups = groups_to_string(R),
- odbc_queries:update_roster_sql(Username, SJID,
- ItemVals, ItemGroups);
- (_Host, _R) ->
- []
- end},
- {roster_version,
- fun(Host, #roster_version{us = {LUser, LServer}, version = Ver})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SVer = ejabberd_odbc:escape(Ver),
- [[<<"delete from roster_version where username='">>,
- Username, <<"';">>],
- [<<"insert into roster_version(username, version) values('">>,
- Username, <<"', '">>, SVer, <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
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_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(JID),
- {selected, _, Rows} =
- ejabberd_odbc: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(_LServer, mnesia, #roster{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, mnesia, #roster_version{} = RV) ->
- mnesia:dirty_write(RV);
-import(_LServer, riak, #roster{us = {LUser, LServer}} = R) ->
- ejabberd_riak:put(R, roster_schema(),
- [{'2i', [{<<"us">>, {LUser, LServer}}]}]);
-import(_LServer, riak, #roster_version{} = RV) ->
- ejabberd_riak:put(RV, roster_version_schema());
-import(_, _, _) ->
- pass.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+import(LServer, DBType, R) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, R).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
diff --git a/src/mod_roster_mnesia.erl b/src/mod_roster_mnesia.erl
new file mode 100644
index 000000000..ddfa34d68
--- /dev/null
+++ b/src/mod_roster_mnesia.erl
@@ -0,0 +1,171 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_roster_mnesia).
+
+-behaviour(mod_roster).
+
+%% API
+-export([init/2, read_roster_version/2, write_roster_version/4,
+ 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]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(roster,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, roster)}]),
+ mnesia:create_table(roster_version,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, roster_version)}]),
+ update_tables(),
+ mnesia:add_table_index(roster, us),
+ mnesia:add_table_index(roster_version, us).
+
+read_roster_version(LUser, LServer) ->
+ US = {LUser, LServer},
+ case mnesia:dirty_read(roster_version, US) of
+ [#roster_version{version = V}] -> V;
+ [] -> error
+ end.
+
+write_roster_version(LUser, LServer, InTransaction, Ver) ->
+ US = {LUser, LServer},
+ if InTransaction ->
+ mnesia:write(#roster_version{us = US, version = Ver});
+ true ->
+ mnesia:dirty_write(#roster_version{us = US, version = Ver})
+ end.
+
+get_roster(LUser, LServer) ->
+ mnesia:dirty_index_read(roster, {LUser, LServer}, #roster.us).
+
+get_roster_by_jid(LUser, LServer, LJID) ->
+ case mnesia:read({roster, {LUser, LServer, LJID}}) of
+ [] ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ [I] ->
+ I#roster{jid = LJID, name = <<"">>, groups = [],
+ xs = []}
+ end.
+
+get_only_items(LUser, LServer) ->
+ get_roster(LUser, LServer).
+
+roster_subscribe(_LUser, _LServer, _LJID, Item) ->
+ mnesia:write(Item).
+
+get_roster_by_jid_with_groups(LUser, LServer, LJID) ->
+ case mnesia:read({roster, {LUser, LServer, LJID}}) of
+ [] ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ [I] -> I
+ end.
+
+remove_user(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ lists:foreach(
+ fun (R) -> mnesia:delete_object(R) end,
+ mnesia:index_read(roster, US, #roster.us))
+ end,
+ mnesia:transaction(F).
+
+update_roster(_LUser, _LServer, _LJID, Item) ->
+ mnesia:write(Item).
+
+del_roster(LUser, LServer, LJID) ->
+ mnesia:delete({roster, {LUser, LServer, LJID}}).
+
+read_subscription_and_groups(LUser, LServer, LJID) ->
+ case mnesia:dirty_read(roster, {LUser, LServer, LJID}) of
+ [#roster{subscription = Subscription, groups = Groups}] ->
+ {Subscription, Groups};
+ _ ->
+ error
+ end.
+
+transaction(_LServer, F) ->
+ mnesia:transaction(F).
+
+import(_LServer, #roster{} = R) ->
+ mnesia:dirty_write(R);
+import(_LServer, #roster_version{} = RV) ->
+ mnesia:dirty_write(RV).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_tables() ->
+ update_roster_table(),
+ update_roster_version_table().
+
+update_roster_table() ->
+ Fields = record_info(fields, roster),
+ case mnesia:table_info(roster, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ roster, Fields, set,
+ fun(#roster{usj = {U, _, _}}) -> U end,
+ fun(#roster{usj = {U, S, {LU, LS, LR}},
+ us = {U1, S1},
+ jid = {U2, S2, R2},
+ name = Name,
+ groups = Gs,
+ askmessage = Ask,
+ xs = Xs} = R) ->
+ R#roster{usj = {iolist_to_binary(U),
+ iolist_to_binary(S),
+ {iolist_to_binary(LU),
+ iolist_to_binary(LS),
+ iolist_to_binary(LR)}},
+ us = {iolist_to_binary(U1),
+ iolist_to_binary(S1)},
+ jid = {iolist_to_binary(U2),
+ iolist_to_binary(S2),
+ iolist_to_binary(R2)},
+ name = iolist_to_binary(Name),
+ groups = [iolist_to_binary(G) || G <- Gs],
+ askmessage = try iolist_to_binary(Ask)
+ catch _:_ -> <<"">> end,
+ xs = [fxml:to_xmlel(X) || X <- Xs]}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating roster table", []),
+ mnesia:transform_table(roster, ignore, Fields)
+ end.
+
+%% Convert roster table to support virtual host
+%% Convert roster table: xattrs fields become
+update_roster_version_table() ->
+ Fields = record_info(fields, roster_version),
+ case mnesia:table_info(roster_version, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ roster_version, Fields, set,
+ fun(#roster_version{us = {U, _}}) -> U end,
+ fun(#roster_version{us = {U, S}, version = Ver} = R) ->
+ R#roster_version{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ version = iolist_to_binary(Ver)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating roster_version table", []),
+ mnesia:transform_table(roster_version, ignore, Fields)
+ end.
diff --git a/src/mod_roster_riak.erl b/src/mod_roster_riak.erl
new file mode 100644
index 000000000..38e873827
--- /dev/null
+++ b/src/mod_roster_riak.erl
@@ -0,0 +1,113 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_roster_riak).
+
+
+-behaviour(mod_roster).
+
+%% API
+-export([init/2, read_roster_version/2, write_roster_version/4,
+ get_roster/2, get_roster_by_jid/3,
+ 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]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+read_roster_version(LUser, LServer) ->
+ case ejabberd_riak:get(roster_version, roster_version_schema(),
+ {LUser, LServer}) of
+ {ok, #roster_version{version = V}} -> V;
+ _Err -> error
+ end.
+
+write_roster_version(LUser, LServer, _InTransaction, Ver) ->
+ US = {LUser, LServer},
+ ejabberd_riak:put(#roster_version{us = US, version = Ver},
+ roster_version_schema()).
+
+get_roster(LUser, LServer) ->
+ case ejabberd_riak:get_by_index(roster, roster_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Items} -> Items;
+ _Err -> []
+ end.
+
+get_roster_by_jid(LUser, LServer, LJID) ->
+ case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
+ {ok, I} ->
+ I#roster{jid = LJID, name = <<"">>, groups = [], xs = []};
+ {error, notfound} ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ Err ->
+ exit(Err)
+ end.
+
+get_only_items(LUser, LServer) ->
+ get_roster(LUser, LServer).
+
+roster_subscribe(LUser, LServer, _LJID, Item) ->
+ ejabberd_riak:put(Item, roster_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+
+transaction(_LServer, F) ->
+ {atomic, F()}.
+
+get_roster_by_jid_with_groups(LUser, LServer, LJID) ->
+ case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
+ {ok, I} ->
+ I;
+ {error, notfound} ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ Err ->
+ exit(Err)
+ end.
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}.
+
+update_roster(LUser, LServer, _LJID, Item) ->
+ ejabberd_riak:put(Item, roster_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+
+del_roster(LUser, LServer, LJID) ->
+ ejabberd_riak:delete(roster, {LUser, LServer, LJID}).
+
+read_subscription_and_groups(LUser, LServer, LJID) ->
+ case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
+ {ok, #roster{subscription = Subscription,
+ groups = Groups}} ->
+ {Subscription, Groups};
+ _ ->
+ error
+ end.
+
+import(_LServer, #roster{us = {LUser, LServer}} = R) ->
+ ejabberd_riak:put(R, roster_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}]);
+import(_LServer, #roster_version{} = RV) ->
+ ejabberd_riak:put(RV, roster_version_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+roster_schema() ->
+ {record_info(fields, roster), #roster{}}.
+
+roster_version_schema() ->
+ {record_info(fields, roster_version), #roster_version{}}.
diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl
new file mode 100644
index 000000000..616d0ec60
--- /dev/null
+++ b/src/mod_roster_sql.erl
@@ -0,0 +1,308 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_roster_sql).
+
+-behaviour(mod_roster).
+
+%% API
+-export([init/2, read_roster_version/2, write_roster_version/4,
+ get_roster/2, get_roster_by_jid/3,
+ 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]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+read_roster_version(LUser, LServer) ->
+ case sql_queries:get_roster_version(LServer, LUser) of
+ {selected, [{Version}]} -> Version;
+ {selected, []} -> error
+ end.
+
+write_roster_version(LUser, LServer, InTransaction, Ver) ->
+ Username = ejabberd_sql:escape(LUser),
+ EVer = ejabberd_sql:escape(Ver),
+ if InTransaction ->
+ sql_queries:set_roster_version(Username, EVer);
+ true ->
+ sql_queries:sql_transaction(
+ LServer,
+ fun () ->
+ sql_queries:set_roster_version(Username, EVer)
+ end)
+ end.
+
+get_roster(LUser, LServer) ->
+ case catch sql_queries:get_roster(LServer, LUser) of
+ {selected, Items} when is_list(Items) ->
+ JIDGroups = case catch sql_queries:get_roster_jid_groups(
+ LServer, LUser) of
+ {selected, JGrps} when is_list(JGrps) ->
+ JGrps;
+ _ ->
+ []
+ end,
+ GroupsDict = lists:foldl(fun({J, G}, Acc) ->
+ dict:append(J, G, Acc)
+ end,
+ dict:new(), JIDGroups),
+ lists:flatmap(
+ fun(I) ->
+ case raw_to_record(LServer, I) of
+ %% Bad JID in database:
+ error -> [];
+ R ->
+ SJID = jid:to_string(R#roster.jid),
+ Groups = case dict:find(SJID, GroupsDict) of
+ {ok, Gs} -> Gs;
+ error -> []
+ end,
+ [R#roster{groups = Groups}]
+ end
+ end, Items);
+ _ ->
+ []
+ end.
+
+get_roster_by_jid(LUser, LServer, LJID) ->
+ {selected, Res} =
+ sql_queries:get_roster_by_jid(LServer, LUser, jid:to_string(LJID)),
+ case Res of
+ [] ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ [I] ->
+ R = raw_to_record(LServer, I),
+ case R of
+ %% Bad JID in database:
+ error ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ _ ->
+ R#roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID, name = <<"">>}
+ end
+ end.
+
+get_only_items(LUser, LServer) ->
+ case catch sql_queries:get_roster(LServer, LUser) of
+ {selected, Is} when is_list(Is) ->
+ lists:map(fun(I) -> raw_to_record(LServer, I) end, Is);
+ _ -> []
+ end.
+
+roster_subscribe(_LUser, _LServer, _LJID, Item) ->
+ ItemVals = record_to_row(Item),
+ sql_queries:roster_subscribe(ItemVals).
+
+transaction(LServer, F) ->
+ ejabberd_sql:sql_transaction(LServer, F).
+
+get_roster_by_jid_with_groups(LUser, LServer, LJID) ->
+ SJID = jid:to_string(LJID),
+ case sql_queries:get_roster_by_jid(LServer, LUser, SJID) of
+ {selected, [I]} ->
+ R = raw_to_record(LServer, I),
+ Groups =
+ case sql_queries:get_roster_groups(LServer, LUser, SJID) of
+ {selected, JGrps} when is_list(JGrps) ->
+ [JGrp || {JGrp} <- JGrps];
+ _ -> []
+ end,
+ R#roster{groups = Groups};
+ {selected, []} ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID}
+ end.
+
+remove_user(LUser, LServer) ->
+ sql_queries:del_user_roster_t(LServer, LUser),
+ {atomic, ok}.
+
+update_roster(LUser, LServer, LJID, Item) ->
+ SJID = jid:to_string(LJID),
+ ItemVals = record_to_row(Item),
+ ItemGroups = Item#roster.groups,
+ sql_queries:update_roster(LServer, LUser, SJID, ItemVals,
+ ItemGroups).
+
+del_roster(LUser, LServer, LJID) ->
+ SJID = jid:to_string(LJID),
+ sql_queries:del_roster(LServer, LUser, SJID).
+
+read_subscription_and_groups(LUser, LServer, LJID) ->
+ SJID = jid:to_string(LJID),
+ case catch sql_queries:get_subscription(LServer, LUser, SJID) of
+ {selected, [{SSubscription}]} ->
+ Subscription = case SSubscription of
+ <<"B">> -> both;
+ <<"T">> -> to;
+ <<"F">> -> from;
+ _ -> none
+ end,
+ Groups = case catch sql_queries:get_rostergroup_by_jid(
+ LServer, LUser, SJID) of
+ {selected, JGrps} when is_list(JGrps) ->
+ [JGrp || {JGrp} <- JGrps];
+ _ -> []
+ end,
+ {Subscription, Groups};
+ _ ->
+ error
+ end.
+
+export(_Server) ->
+ [{roster,
+ fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ SJID = ejabberd_sql:escape(jid:to_string(LJID)),
+ ItemVals = record_to_string(R),
+ ItemGroups = groups_to_string(R),
+ sql_queries:update_roster_sql(Username, SJID,
+ ItemVals, ItemGroups);
+ (_Host, _R) ->
+ []
+ end},
+ {roster_version,
+ fun(Host, #roster_version{us = {LUser, LServer}, version = Ver})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ SVer = ejabberd_sql:escape(Ver),
+ [[<<"delete from roster_version where username='">>,
+ Username, <<"';">>],
+ [<<"insert into roster_version(username, version) values('">>,
+ Username, <<"', '">>, SVer, <<"');">>]];
+ (_Host, _R) ->
+ []
+ 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.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+raw_to_record(LServer,
+ [User, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ _SServer, _SSubscribe, _SType]) ->
+ raw_to_record(LServer,
+ {User, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ _SServer, _SSubscribe, _SType});
+raw_to_record(LServer,
+ {User, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ _SServer, _SSubscribe, _SType}) ->
+ case jid:from_string(SJID) of
+ error -> error;
+ JID ->
+ LJID = jid:tolower(JID),
+ Subscription = case SSubscription of
+ <<"B">> -> both;
+ <<"T">> -> to;
+ <<"F">> -> from;
+ _ -> none
+ end,
+ Ask = case SAsk of
+ <<"S">> -> subscribe;
+ <<"U">> -> unsubscribe;
+ <<"B">> -> both;
+ <<"O">> -> out;
+ <<"I">> -> in;
+ _ -> none
+ end,
+ #roster{usj = {User, LServer, LJID},
+ us = {User, LServer}, jid = LJID, name = Nick,
+ subscription = Subscription, ask = Ask,
+ askmessage = SAskMessage}
+ end.
+
+record_to_string(#roster{us = {User, _Server},
+ jid = JID, name = Name, subscription = Subscription,
+ ask = Ask, askmessage = AskMessage}) ->
+ Username = ejabberd_sql:escape(User),
+ SJID =
+ ejabberd_sql:escape(jid:to_string(jid:tolower(JID))),
+ Nick = ejabberd_sql:escape(Name),
+ SSubscription = case Subscription of
+ both -> <<"B">>;
+ to -> <<"T">>;
+ from -> <<"F">>;
+ none -> <<"N">>
+ end,
+ SAsk = case Ask of
+ subscribe -> <<"S">>;
+ unsubscribe -> <<"U">>;
+ both -> <<"B">>;
+ out -> <<"O">>;
+ in -> <<"I">>;
+ none -> <<"N">>
+ end,
+ SAskMessage = ejabberd_sql:escape(AskMessage),
+ [Username, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ <<"N">>, <<"">>, <<"item">>].
+
+record_to_row(
+ #roster{us = {LUser, _LServer},
+ jid = JID, name = Name, subscription = Subscription,
+ ask = Ask, askmessage = AskMessage}) ->
+ SJID = jid:to_string(jid:tolower(JID)),
+ SSubscription = case Subscription of
+ both -> <<"B">>;
+ to -> <<"T">>;
+ from -> <<"F">>;
+ none -> <<"N">>
+ end,
+ SAsk = case Ask of
+ subscribe -> <<"S">>;
+ unsubscribe -> <<"U">>;
+ both -> <<"B">>;
+ out -> <<"O">>;
+ in -> <<"I">>;
+ none -> <<"N">>
+ end,
+ {LUser, SJID, Name, SSubscription, SAsk, AskMessage}.
+
+groups_to_string(#roster{us = {User, _Server},
+ jid = JID, groups = Groups}) ->
+ Username = ejabberd_sql:escape(User),
+ SJID =
+ ejabberd_sql:escape(jid:to_string(jid:tolower(JID))),
+ lists:foldl(fun (<<"">>, Acc) -> Acc;
+ (Group, Acc) ->
+ G = ejabberd_sql:escape(Group),
+ [[Username, SJID, G] | Acc]
+ end,
+ [], Groups).
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index cde0f66b8..6670cf77b 100644
--- a/src/mod_shared_roster.erl
+++ b/src/mod_shared_roster.erl
@@ -38,7 +38,7 @@
list_groups/1, create_group/2, create_group/3,
delete_group/2, get_group_opts/2, set_group_opts/3,
get_group_users/2, get_group_explicit_users/2,
- is_user_in_group/3, add_user_to_group/3,
+ is_user_in_group/3, add_user_to_group/3, opts_to_binary/1,
remove_user_from_group/3, mod_opt_type/1]).
-include("ejabberd.hrl").
@@ -52,25 +52,28 @@
-include("ejabberd_web_admin.hrl").
--record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()},
- opts = [] :: list() | '_' | '$2'}).
-
--record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()},
- group_host = {<<"">>, <<"">>} :: {binary(), binary()}}).
+-include("mod_shared_roster.hrl").
+
+-type group_options() :: [{atom(), any()}].
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #sr_user{} | #sr_group{}) -> ok | pass.
+-callback list_groups(binary()) -> [binary()].
+-callback groups_with_opts(binary()) -> [{binary(), group_options()}].
+-callback create_group(binary(), binary(), group_options()) -> {atomic, any()}.
+-callback delete_group(binary(), binary()) -> {atomic, any()}.
+-callback get_group_opts(binary(), binary()) -> group_options() | error.
+-callback set_group_opts(binary(), binary(), group_options()) -> {atomic, any()}.
+-callback get_user_groups({binary(), binary()}, binary()) -> [binary()].
+-callback get_group_explicit_users(binary(), binary()) -> [{binary(), binary()}].
+-callback get_user_displayed_groups(binary(), binary(), group_options()) ->
+ [{binary(), group_options()}].
+-callback is_user_in_group({binary(), binary()}, binary(), binary()) -> boolean().
+-callback add_user_to_group(binary(), {binary(), binary()}, binary()) -> {atomic, any()}.
+-callback remove_user_from_group(binary(), {binary(), binary()}, binary()) -> {atomic, any()}.
start(Host, Opts) ->
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(sr_group,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, sr_group)}]),
- mnesia:create_table(sr_user,
- [{disc_copies, [node()]}, {type, bag},
- {attributes, record_info(fields, sr_user)}]),
- update_tables(),
- mnesia:add_table_index(sr_user, group_host);
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE,
webadmin_menu, 70),
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE,
@@ -206,11 +209,11 @@ get_rosteritem_name([ModVcard], U, S) ->
get_rosteritem_name_vcard([]) -> <<"">>;
get_rosteritem_name_vcard([Vcard]) ->
- case xml:get_path_s(Vcard,
+ case fxml:get_path_s(Vcard,
[{elem, <<"NICKNAME">>}, cdata])
of
<<"">> ->
- xml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]);
+ fxml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]);
Nickname -> Nickname
end.
@@ -391,195 +394,36 @@ process_subscription(Direction, User, Server, JID,
end.
list_groups(Host) ->
- list_groups(Host, gen_mod:db_type(Host, ?MODULE)).
-
-list_groups(Host, mnesia) ->
- mnesia:dirty_select(sr_group,
- [{#sr_group{group_host = {'$1', '$2'}, _ = '_'},
- [{'==', '$2', Host}], ['$1']}]);
-list_groups(Host, riak) ->
- case ejabberd_riak:get_keys_by_index(sr_group, <<"host">>, Host) of
- {ok, Gs} ->
- [G || {G, _} <- Gs];
- _ ->
- []
- end;
-list_groups(Host, odbc) ->
- case ejabberd_odbc:sql_query(Host,
- [<<"select name from sr_group;">>])
- of
- {selected, [<<"name">>], Rs} -> [G || [G] <- Rs];
- _ -> []
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:list_groups(Host).
groups_with_opts(Host) ->
- groups_with_opts(Host, gen_mod:db_type(Host, ?MODULE)).
-
-groups_with_opts(Host, mnesia) ->
- Gs = mnesia:dirty_select(sr_group,
- [{#sr_group{group_host = {'$1', Host}, opts = '$2',
- _ = '_'},
- [], [['$1', '$2']]}]),
- lists:map(fun ([G, O]) -> {G, O} end, Gs);
-groups_with_opts(Host, riak) ->
- case ejabberd_riak:get_by_index(sr_group, sr_group_schema(),
- <<"host">>, Host) of
- {ok, Rs} ->
- [{G, O} || #sr_group{group_host = {G, _}, opts = O} <- Rs];
- _ ->
- []
- end;
-groups_with_opts(Host, odbc) ->
- case ejabberd_odbc:sql_query(Host,
- [<<"select name, opts from sr_group;">>])
- of
- {selected, [<<"name">>, <<"opts">>], Rs} ->
- [{G, opts_to_binary(ejabberd_odbc:decode_term(Opts))}
- || [G, Opts] <- Rs];
- _ -> []
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:groups_with_opts(Host).
create_group(Host, Group) ->
create_group(Host, Group, []).
create_group(Host, Group, Opts) ->
- create_group(Host, Group, Opts,
- gen_mod:db_type(Host, ?MODULE)).
-
-create_group(Host, Group, Opts, mnesia) ->
- R = #sr_group{group_host = {Group, Host}, opts = Opts},
- F = fun () -> mnesia:write(R) end,
- mnesia:transaction(F);
-create_group(Host, Group, Opts, riak) ->
- {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
- opts = Opts},
- sr_group_schema(),
- [{'2i', [{<<"host">>, Host}]}])};
-create_group(Host, Group, Opts, odbc) ->
- SGroup = ejabberd_odbc:escape(Group),
- SOpts = ejabberd_odbc:encode_term(Opts),
- F = fun () ->
- odbc_queries:update_t(<<"sr_group">>,
- [<<"name">>, <<"opts">>], [SGroup, SOpts],
- [<<"name='">>, SGroup, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(Host, F).
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:create_group(Host, Group, Opts).
delete_group(Host, Group) ->
- delete_group(Host, Group,
- gen_mod:db_type(Host, ?MODULE)).
-
-delete_group(Host, Group, mnesia) ->
- GroupHost = {Group, Host},
- F = fun () ->
- mnesia:delete({sr_group, GroupHost}),
- Users = mnesia:index_read(sr_user, GroupHost,
- #sr_user.group_host),
- lists:foreach(fun (UserEntry) ->
- mnesia:delete_object(UserEntry)
- end,
- Users)
- end,
- mnesia:transaction(F);
-delete_group(Host, Group, riak) ->
- try
- ok = ejabberd_riak:delete(sr_group, {Group, Host}),
- ok = ejabberd_riak:delete_by_index(sr_user, <<"group_host">>,
- {Group, Host}),
- {atomic, ok}
- catch _:{badmatch, Err} ->
- {atomic, Err}
- end;
-delete_group(Host, Group, odbc) ->
- SGroup = ejabberd_odbc:escape(Group),
- F = fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from sr_group where name='">>,
- SGroup, <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from sr_user where grp='">>,
- SGroup, <<"';">>])
- end,
- case ejabberd_odbc:sql_transaction(Host, F) of
- {atomic,{updated,_}} -> {atomic, ok};
- Res -> Res
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:delete_group(Host, Group).
get_group_opts(Host, Group) ->
- get_group_opts(Host, Group,
- gen_mod:db_type(Host, ?MODULE)).
-
-get_group_opts(Host, Group, mnesia) ->
- case catch mnesia:dirty_read(sr_group, {Group, Host}) of
- [#sr_group{opts = Opts}] -> Opts;
- _ -> error
- end;
-get_group_opts(Host, Group, riak) ->
- case ejabberd_riak:get(sr_group, sr_group_schema(), {Group, Host}) of
- {ok, #sr_group{opts = Opts}} -> Opts;
- _ -> error
- end;
-get_group_opts(Host, Group, odbc) ->
- SGroup = ejabberd_odbc:escape(Group),
- case catch ejabberd_odbc:sql_query(Host,
- [<<"select opts from sr_group where name='">>,
- SGroup, <<"';">>])
- of
- {selected, [<<"opts">>], [[SOpts]]} ->
- opts_to_binary(ejabberd_odbc:decode_term(SOpts));
- _ -> error
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:get_group_opts(Host, Group).
set_group_opts(Host, Group, Opts) ->
- set_group_opts(Host, Group, Opts,
- gen_mod:db_type(Host, ?MODULE)).
-
-set_group_opts(Host, Group, Opts, mnesia) ->
- R = #sr_group{group_host = {Group, Host}, opts = Opts},
- F = fun () -> mnesia:write(R) end,
- mnesia:transaction(F);
-set_group_opts(Host, Group, Opts, riak) ->
- {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
- opts = Opts},
- sr_group_schema(),
- [{'2i', [{<<"host">>, Host}]}])};
-set_group_opts(Host, Group, Opts, odbc) ->
- SGroup = ejabberd_odbc:escape(Group),
- SOpts = ejabberd_odbc:encode_term(Opts),
- F = fun () ->
- odbc_queries:update_t(<<"sr_group">>,
- [<<"name">>, <<"opts">>], [SGroup, SOpts],
- [<<"name='">>, SGroup, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(Host, F).
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:set_group_opts(Host, Group, Opts).
get_user_groups(US) ->
Host = element(2, US),
- DBType = gen_mod:db_type(Host, ?MODULE),
- get_user_groups(US, Host, DBType) ++
- get_special_users_groups(Host).
-
-get_user_groups(US, Host, mnesia) ->
- case catch mnesia:dirty_read(sr_user, US) of
- Rs when is_list(Rs) ->
- [Group
- || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
- _ -> []
- end;
-get_user_groups(US, Host, riak) ->
- case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
- {ok, Rs} ->
- [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
- _ ->
- []
- end;
-get_user_groups(US, Host, odbc) ->
- SJID = make_jid_s(US),
- case catch ejabberd_odbc:sql_query(Host,
- [<<"select grp from sr_user where jid='">>,
- SJID, <<"';">>])
- of
- {selected, [<<"grp">>], Rs} -> [G || [G] <- Rs];
- _ -> []
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:get_user_groups(US, Host) ++ get_special_users_groups(Host).
is_group_enabled(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
@@ -630,39 +474,8 @@ get_group_users(Host, Group, GroupOpts) ->
++ get_group_explicit_users(Host, Group).
get_group_explicit_users(Host, Group) ->
- get_group_explicit_users(Host, Group,
- gen_mod:db_type(Host, ?MODULE)).
-
-get_group_explicit_users(Host, Group, mnesia) ->
- Read = (catch mnesia:dirty_index_read(sr_user,
- {Group, Host}, #sr_user.group_host)),
- case Read of
- Rs when is_list(Rs) -> [R#sr_user.us || R <- Rs];
- _ -> []
- end;
-get_group_explicit_users(Host, Group, riak) ->
- case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
- <<"group_host">>, {Group, Host}) of
- {ok, Rs} ->
- [R#sr_user.us || R <- Rs];
- _ ->
- []
- end;
-get_group_explicit_users(Host, Group, odbc) ->
- SGroup = ejabberd_odbc:escape(Group),
- case catch ejabberd_odbc:sql_query(Host,
- [<<"select jid from sr_user where grp='">>,
- SGroup, <<"';">>])
- of
- {selected, [<<"jid">>], Rs} ->
- lists:map(fun ([JID]) ->
- {U, S, _} =
- jid:tolower(jid:from_string(JID)),
- {U, S}
- end,
- Rs);
- _ -> []
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:get_group_explicit_users(Host, Group).
get_group_name(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
@@ -718,44 +531,10 @@ get_special_displayed_groups(GroupsOpts) ->
%% for the list of groups of that server that user is member
%% get the list of groups displayed
get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
- Groups = get_user_displayed_groups(LUser, LServer,
- GroupsOpts,
- gen_mod:db_type(LServer, ?MODULE)),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Groups = Mod:get_user_displayed_groups(LUser, LServer, GroupsOpts),
displayed_groups(GroupsOpts, Groups).
-get_user_displayed_groups(LUser, LServer, GroupsOpts,
- mnesia) ->
- case catch mnesia:dirty_read(sr_user, {LUser, LServer})
- of
- Rs when is_list(Rs) ->
- [{Group, proplists:get_value(Group, GroupsOpts, [])}
- || #sr_user{group_host = {Group, H}} <- Rs,
- H == LServer];
- _ -> []
- end;
-get_user_displayed_groups(LUser, LServer, GroupsOpts,
- riak) ->
- case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Rs} ->
- [{Group, proplists:get_value(Group, GroupsOpts, [])}
- || #sr_user{group_host = {Group, _}} <- Rs];
- _ ->
- []
- end;
-get_user_displayed_groups(LUser, LServer, GroupsOpts,
- odbc) ->
- SJID = make_jid_s(LUser, LServer),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select grp from sr_user where jid='">>,
- SJID, <<"';">>])
- of
- {selected, [<<"grp">>], Rs} ->
- [{Group, proplists:get_value(Group, GroupsOpts, [])}
- || [Group] <- Rs];
- _ -> []
- end.
-
%% @doc Get the list of groups that are displayed to this user
get_user_displayed_groups(US) ->
Host = element(2, US),
@@ -779,42 +558,12 @@ get_user_displayed_groups(US) ->
is_group_enabled(Host, Group)].
is_user_in_group(US, Group, Host) ->
- is_user_in_group(US, Group, Host,
- gen_mod:db_type(Host, ?MODULE)).
-
-is_user_in_group(US, Group, Host, mnesia) ->
- case catch mnesia:dirty_match_object(#sr_user{us = US,
- group_host = {Group, Host}})
- of
- [] -> lists:member(US, get_group_users(Host, Group));
- _ -> true
- end;
-is_user_in_group(US, Group, Host, riak) ->
- case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
- {ok, Rs} ->
- case lists:any(
- fun(#sr_user{group_host = {G, H}}) ->
- (Group == G) and (Host == H)
- end, Rs) of
- false ->
- lists:member(US, get_group_users(Host, Group));
- true ->
- true
- end;
- _Err ->
- false
- end;
-is_user_in_group(US, Group, Host, odbc) ->
- SJID = make_jid_s(US),
- SGroup = ejabberd_odbc:escape(Group),
- case catch ejabberd_odbc:sql_query(Host,
- [<<"select * from sr_user where jid='">>,
- SJID, <<"' and grp='">>, SGroup,
- <<"';">>])
- of
- {selected, _, []} ->
- lists:member(US, get_group_users(Host, Group));
- _ -> true
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ case Mod:is_user_in_group(US, Group, Host) of
+ false ->
+ lists:member(US, get_group_users(Host, Group));
+ true ->
+ true
end.
%% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok}
@@ -837,31 +586,10 @@ add_user_to_group(Host, US, Group) ->
push_displayed_to_user(LUser, LServer, Host, both, DisplayedGroups),
broadcast_user_to_displayed(LUser, LServer, Host, both, DisplayedToGroups),
broadcast_displayed_to_user(LUser, LServer, Host, both, DisplayedGroups),
- add_user_to_group(Host, US, Group, gen_mod:db_type(Host, ?MODULE))
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:add_user_to_group(Host, US, Group)
end.
-add_user_to_group(Host, US, Group, mnesia) ->
- R = #sr_user{us = US, group_host = {Group, Host}},
- F = fun () -> mnesia:write(R) end,
- mnesia:transaction(F);
-add_user_to_group(Host, US, Group, riak) ->
- {atomic, ejabberd_riak:put(
- #sr_user{us = US, group_host = {Group, Host}},
- sr_user_schema(),
- [{i, {US, {Group, Host}}},
- {'2i', [{<<"us">>, US},
- {<<"group_host">>, {Group, Host}}]}])};
-add_user_to_group(Host, US, Group, odbc) ->
- SJID = make_jid_s(US),
- SGroup = ejabberd_odbc:escape(Group),
- F = fun () ->
- odbc_queries:update_t(<<"sr_user">>,
- [<<"jid">>, <<"grp">>], [SJID, SGroup],
- [<<"jid='">>, SJID, <<"' and grp='">>,
- SGroup, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(Host, F).
-
get_displayed_groups(Group, LServer) ->
GroupsOpts = groups_with_opts(LServer),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
@@ -894,8 +622,8 @@ remove_user_from_group(Host, US, Group) ->
end,
(?MODULE):set_group_opts(Host, Group, NewGroupOpts);
nomatch ->
- Result = remove_user_from_group(Host, US, Group,
- gen_mod:db_type(Host, ?MODULE)),
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Result = Mod:remove_user_from_group(Host, US, Group),
DisplayedToGroups = displayed_to_groups(Group, Host),
DisplayedGroups = get_displayed_groups(Group, LServer),
push_user_to_displayed(LUser, LServer, Group, Host, remove, DisplayedToGroups),
@@ -903,23 +631,6 @@ remove_user_from_group(Host, US, Group) ->
Result
end.
-remove_user_from_group(Host, US, Group, mnesia) ->
- R = #sr_user{us = US, group_host = {Group, Host}},
- F = fun () -> mnesia:delete_object(R) end,
- mnesia:transaction(F);
-remove_user_from_group(Host, US, Group, riak) ->
- {atomic, ejabberd_riak:delete(sr_group, {US, {Group, Host}})};
-remove_user_from_group(Host, US, Group, odbc) ->
- SJID = make_jid_s(US),
- SGroup = ejabberd_odbc:escape(Group),
- F = fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from sr_user where jid='">>,
- SJID, <<"' and grp='">>, SGroup,
- <<"';">>]),
- ok
- end,
- ejabberd_odbc:sql_transaction(Host, F).
-
push_members_to_user(LUser, LServer, Group, Host,
Subscription) ->
GroupsOpts = groups_with_opts(LServer),
@@ -1160,7 +871,7 @@ list_shared_roster_groups(Host, Query, Lang) ->
[?INPUTT(<<"submit">>, <<"addnew">>,
<<"Add New">>)])])]))])),
(?H1GL((?T(<<"Shared Roster Groups">>)),
- <<"modsharedroster">>, <<"mod_shared_roster">>))
+ <<"mod_shared_roster">>, <<"mod_shared_roster">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -1254,7 +965,7 @@ shared_roster_group(Host, Group, Query, Lang) ->
<<"20">>,
list_to_binary(FDisplayedGroups))])])])])),
(?H1GL((?T(<<"Shared Roster Groups">>)),
- <<"modsharedroster">>, <<"mod_shared_roster">>))
+ <<"mod_shared_roster">>, <<"mod_shared_roster">>))
++
[?XC(<<"h2">>, <<(?T(<<"Group ">>))/binary, Group/binary>>)] ++
case Res of
@@ -1385,13 +1096,6 @@ displayed_groups_update(Members, DisplayedGroups, Subscription) ->
end
end, Members).
-make_jid_s(U, S) ->
- ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:make(U,
- S,
- <<"">>)))).
-
-make_jid_s({U, S}) -> make_jid_s(U, S).
-
opts_to_binary(Opts) ->
lists:map(
fun({name, Name}) ->
@@ -1404,105 +1108,17 @@ opts_to_binary(Opts) ->
Opt
end, Opts).
-sr_group_schema() ->
- {record_info(fields, sr_group), #sr_group{}}.
-
-sr_user_schema() ->
- {record_info(fields, sr_user), #sr_user{}}.
-
-update_tables() ->
- update_sr_group_table(),
- update_sr_user_table().
-
-update_sr_group_table() ->
- Fields = record_info(fields, sr_group),
- case mnesia:table_info(sr_group, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- sr_group, Fields, set,
- fun(#sr_group{group_host = {G, _}}) -> G end,
- fun(#sr_group{group_host = {G, H},
- opts = Opts} = R) ->
- R#sr_group{group_host = {iolist_to_binary(G),
- iolist_to_binary(H)},
- opts = opts_to_binary(Opts)}
- end);
- _ ->
- ?INFO_MSG("Recreating sr_group table", []),
- mnesia:transform_table(sr_group, ignore, Fields)
- end.
-
-update_sr_user_table() ->
- Fields = record_info(fields, sr_user),
- case mnesia:table_info(sr_user, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- sr_user, Fields, bag,
- fun(#sr_user{us = {U, _}}) -> U end,
- fun(#sr_user{us = {U, S}, group_host = {G, H}} = R) ->
- R#sr_user{us = {iolist_to_binary(U), iolist_to_binary(S)},
- group_host = {iolist_to_binary(G),
- iolist_to_binary(H)}}
- end);
- _ ->
- ?INFO_MSG("Recreating sr_user table", []),
- mnesia:transform_table(sr_user, ignore, Fields)
- end.
-
-export(_Server) ->
- [{sr_group,
- fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts})
- when LServer == Host ->
- SGroup = ejabberd_odbc:escape(Group),
- SOpts = ejabberd_odbc:encode_term(Opts),
- [[<<"delete from sr_group where name='">>, Group, <<"';">>],
- [<<"insert into sr_group(name, opts) values ('">>,
- SGroup, <<"', '">>, SOpts, <<"');">>]];
- (_Host, _R) ->
- []
- end},
- {sr_user,
- fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}})
- when LServer == Host ->
- SGroup = ejabberd_odbc:escape(Group),
- SJID = ejabberd_odbc:escape(
- jid:to_string(
- jid:tolower(
- jid:make(U, S, <<"">>)))),
- [[<<"delete from sr_user where jid='">>, SJID,
- <<"'and grp='">>, Group, <<"';">>],
- [<<"insert into sr_user(jid, grp) values ('">>,
- SJID, <<"', '">>, SGroup, <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select name, opts from sr_group;">>,
- fun([Group, SOpts]) ->
- #sr_group{group_host = {Group, LServer},
- opts = ejabberd_odbc: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(_LServer, mnesia, #sr_group{} = G) ->
- mnesia:dirty_write(G);
-
-import(_LServer, mnesia, #sr_user{} = U) ->
- mnesia:dirty_write(U);
-import(_LServer, riak, #sr_group{group_host = {_, Host}} = G) ->
- ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, Host}]}]);
-import(_LServer, riak, #sr_user{us = US, group_host = {Group, Host}} = User) ->
- ejabberd_riak:put(User, sr_user_schema(),
- [{i, {US, {Group, Host}}},
- {'2i', [{<<"us">>, US},
- {<<"group_host">>, {Group, Host}}]}]);
-import(_, _, _) ->
- pass.
+ 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).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(_) -> [db_type].
diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl
index a4ac65c10..34588b90c 100644
--- a/src/mod_shared_roster_ldap.erl
+++ b/src/mod_shared_roster_ldap.erl
@@ -47,16 +47,13 @@
-include("logger.hrl").
-include("jlib.hrl").
-include("mod_roster.hrl").
-
-include("eldap.hrl").
-define(CACHE_SIZE, 1000).
-
--define(USER_CACHE_VALIDITY, 300).
-
+-define(USER_CACHE_VALIDITY, 300). %% in seconds
-define(GROUP_CACHE_VALIDITY, 300).
-
--define(LDAP_SEARCH_TIMEOUT, 5).
+-define(LDAP_SEARCH_TIMEOUT, 5). %% Timeout for LDAP search queries in seconds
+-define(INVALID_SETTING_MSG, "~s is not properly set! ~s will not function.").
-record(state,
{host = <<"">> :: binary(),
@@ -334,12 +331,11 @@ get_group_users(Host, Group) ->
{ok, State} = eldap_utils:get_state(Host, ?MODULE),
case cache_tab:dirty_lookup(shared_roster_ldap_group,
{Group, Host},
- fun () -> search_group_info(State, Group) end)
- of
- {ok, #group_info{members = Members}}
+ fun () -> search_group_info(State, Group) end) of
+ {ok, #group_info{members = Members}}
when Members /= undefined ->
- Members;
- _ -> []
+ Members;
+ _ -> []
end.
get_group_name(Host, Group) ->
@@ -381,79 +377,49 @@ search_group_info(State, Group) ->
true -> fun ejabberd_auth:is_user_exists/2;
_ -> fun (_U, _S) -> true end
end,
- Host = State#state.host,
case eldap_search(State,
[eldap_filter:do_sub(State#state.gfilter,
[{<<"%g">>, Group}])],
[State#state.group_attr, State#state.group_desc,
State#state.uid])
of
- [] -> error;
+ [] ->
+ error;
LDAPEntries ->
- {GroupDesc, MembersLists} = lists:foldl(fun
- (#eldap_entry{attributes =
- Attrs},
- {DescAcc, JIDsAcc}) ->
- case
- {eldap_utils:get_ldap_attr(State#state.group_attr,
- Attrs),
- eldap_utils:get_ldap_attr(State#state.group_desc,
- Attrs),
- lists:keysearch(State#state.uid,
- 1,
- Attrs)}
- of
- {ID, Desc,
- {value,
- {GroupMemberAttr,
- Members}}}
- when ID /= <<"">>,
- GroupMemberAttr
- ==
- State#state.uid ->
- JIDs =
- lists:foldl(fun
- ({ok,
- UID},
- L) ->
- PUID =
- jid:nodeprep(UID),
- case
- PUID
- of
- error ->
- L;
- _ ->
- case
- AuthChecker(PUID,
- Host)
- of
- true ->
- [{PUID,
- Host}
- | L];
- _ ->
- L
- end
- end;
- (_,
- L) ->
- L
- end,
- [],
- lists:map(Extractor,
- Members)),
- {Desc,
- [JIDs
- | JIDsAcc]};
- _ ->
- {DescAcc, JIDsAcc}
- end
- end,
- {Group, []}, LDAPEntries),
- {ok,
- #group_info{desc = GroupDesc,
- members = lists:usort(lists:flatten(MembersLists))}}
+ {GroupDesc, MembersLists} = lists:foldl(fun(Entry, Acc) ->
+ extract_members(State, Extractor, AuthChecker, Entry, Acc)
+ end,
+ {Group, []}, LDAPEntries),
+ {ok, #group_info{desc = GroupDesc, members = lists:usort(lists:flatten(MembersLists))}}
+ end.
+
+extract_members(State, Extractor, AuthChecker, #eldap_entry{attributes = Attrs}, {DescAcc, JIDsAcc}) ->
+ Host = State#state.host,
+ case {eldap_utils:get_ldap_attr(State#state.group_attr, Attrs),
+ eldap_utils:get_ldap_attr(State#state.group_desc, Attrs),
+ lists:keysearch(State#state.uid, 1, Attrs)} of
+ {ID, Desc, {value, {GroupMemberAttr, Members}}} when ID /= <<"">>,
+ GroupMemberAttr == State#state.uid ->
+ JIDs = lists:foldl(fun({ok, UID}, L) ->
+ PUID = jid:nodeprep(UID),
+ case PUID of
+ error ->
+ L;
+ _ ->
+ case AuthChecker(PUID, Host) of
+ true ->
+ [{PUID, Host} | L];
+ _ ->
+ L
+ end
+ end;
+ (_, L) -> L
+ end,
+ [],
+ lists:map(Extractor, Members)),
+ {Desc, [JIDs | JIDsAcc]};
+ _ ->
+ {DescAcc, JIDsAcc}
end.
search_user_name(State, User) ->
diff --git a/src/mod_shared_roster_mnesia.erl b/src/mod_shared_roster_mnesia.erl
new file mode 100644
index 000000000..ca2e55e2f
--- /dev/null
+++ b/src/mod_shared_roster_mnesia.erl
@@ -0,0 +1,167 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_shared_roster_mnesia).
+
+-behaviour(mod_shared_roster).
+
+%% API
+-export([init/2, list_groups/1, groups_with_opts/1, create_group/3,
+ 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]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+-include("mod_shared_roster.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(sr_group,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, sr_group)}]),
+ mnesia:create_table(sr_user,
+ [{disc_copies, [node()]}, {type, bag},
+ {attributes, record_info(fields, sr_user)}]),
+ update_tables(),
+ mnesia:add_table_index(sr_user, group_host).
+
+list_groups(Host) ->
+ mnesia:dirty_select(sr_group,
+ [{#sr_group{group_host = {'$1', '$2'}, _ = '_'},
+ [{'==', '$2', Host}], ['$1']}]).
+
+groups_with_opts(Host) ->
+ Gs = mnesia:dirty_select(sr_group,
+ [{#sr_group{group_host = {'$1', Host}, opts = '$2',
+ _ = '_'},
+ [], [['$1', '$2']]}]),
+ lists:map(fun ([G, O]) -> {G, O} end, Gs).
+
+create_group(Host, Group, Opts) ->
+ R = #sr_group{group_host = {Group, Host}, opts = Opts},
+ F = fun () -> mnesia:write(R) end,
+ mnesia:transaction(F).
+
+delete_group(Host, Group) ->
+ GroupHost = {Group, Host},
+ F = fun () ->
+ mnesia:delete({sr_group, GroupHost}),
+ Users = mnesia:index_read(sr_user, GroupHost,
+ #sr_user.group_host),
+ lists:foreach(fun (UserEntry) ->
+ mnesia:delete_object(UserEntry)
+ end,
+ Users)
+ end,
+ mnesia:transaction(F).
+
+get_group_opts(Host, Group) ->
+ case catch mnesia:dirty_read(sr_group, {Group, Host}) of
+ [#sr_group{opts = Opts}] -> Opts;
+ _ -> error
+ end.
+
+set_group_opts(Host, Group, Opts) ->
+ R = #sr_group{group_host = {Group, Host}, opts = Opts},
+ F = fun () -> mnesia:write(R) end,
+ mnesia:transaction(F).
+
+get_user_groups(US, Host) ->
+ case catch mnesia:dirty_read(sr_user, US) of
+ Rs when is_list(Rs) ->
+ [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
+ _ ->
+ []
+ end.
+
+get_group_explicit_users(Host, Group) ->
+ Read = (catch mnesia:dirty_index_read(sr_user,
+ {Group, Host}, #sr_user.group_host)),
+ case Read of
+ Rs when is_list(Rs) -> [R#sr_user.us || R <- Rs];
+ _ -> []
+ end.
+
+get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
+ case catch mnesia:dirty_read(sr_user, {LUser, LServer}) of
+ Rs when is_list(Rs) ->
+ [{Group, proplists:get_value(Group, GroupsOpts, [])}
+ || #sr_user{group_host = {Group, H}} <- Rs,
+ H == LServer];
+ _ ->
+ []
+ end.
+
+is_user_in_group(US, Group, Host) ->
+ case mnesia:dirty_match_object(
+ #sr_user{us = US, group_host = {Group, Host}}) of
+ [] -> false;
+ _ -> true
+ end.
+
+add_user_to_group(Host, US, Group) ->
+ R = #sr_user{us = US, group_host = {Group, Host}},
+ F = fun () -> mnesia:write(R) end,
+ mnesia:transaction(F).
+
+remove_user_from_group(Host, US, Group) ->
+ R = #sr_user{us = US, group_host = {Group, Host}},
+ F = fun () -> mnesia:delete_object(R) end,
+ mnesia:transaction(F).
+
+import(_LServer, #sr_group{} = G) ->
+ mnesia:dirty_write(G);
+import(_LServer, #sr_user{} = U) ->
+ mnesia:dirty_write(U).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_tables() ->
+ update_sr_group_table(),
+ update_sr_user_table().
+
+update_sr_group_table() ->
+ Fields = record_info(fields, sr_group),
+ case mnesia:table_info(sr_group, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ sr_group, Fields, set,
+ fun(#sr_group{group_host = {G, _}}) -> G end,
+ fun(#sr_group{group_host = {G, H},
+ opts = Opts} = R) ->
+ R#sr_group{group_host = {iolist_to_binary(G),
+ iolist_to_binary(H)},
+ opts = mod_shared_roster:opts_to_binary(Opts)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating sr_group table", []),
+ mnesia:transform_table(sr_group, ignore, Fields)
+ end.
+
+update_sr_user_table() ->
+ Fields = record_info(fields, sr_user),
+ case mnesia:table_info(sr_user, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ sr_user, Fields, bag,
+ fun(#sr_user{us = {U, _}}) -> U end,
+ fun(#sr_user{us = {U, S}, group_host = {G, H}} = R) ->
+ R#sr_user{us = {iolist_to_binary(U), iolist_to_binary(S)},
+ group_host = {iolist_to_binary(G),
+ iolist_to_binary(H)}}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating sr_user table", []),
+ mnesia:transform_table(sr_user, ignore, Fields)
+ end.
diff --git a/src/mod_shared_roster_riak.erl b/src/mod_shared_roster_riak.erl
new file mode 100644
index 000000000..0df35e37d
--- /dev/null
+++ b/src/mod_shared_roster_riak.erl
@@ -0,0 +1,139 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_shared_roster_riak).
+
+-behaviour(mod_shared_roster).
+
+%% API
+-export([init/2, list_groups/1, groups_with_opts/1, create_group/3,
+ 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]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+-include("mod_shared_roster.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+list_groups(Host) ->
+ case ejabberd_riak:get_keys_by_index(sr_group, <<"host">>, Host) of
+ {ok, Gs} ->
+ [G || {G, _} <- Gs];
+ _ ->
+ []
+ end.
+
+groups_with_opts(Host) ->
+ case ejabberd_riak:get_by_index(sr_group, sr_group_schema(),
+ <<"host">>, Host) of
+ {ok, Rs} ->
+ [{G, O} || #sr_group{group_host = {G, _}, opts = O} <- Rs];
+ _ ->
+ []
+ end.
+
+create_group(Host, Group, Opts) ->
+ {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
+ opts = Opts},
+ sr_group_schema(),
+ [{'2i', [{<<"host">>, Host}]}])}.
+
+delete_group(Host, Group) ->
+ try
+ ok = ejabberd_riak:delete(sr_group, {Group, Host}),
+ ok = ejabberd_riak:delete_by_index(sr_user, <<"group_host">>,
+ {Group, Host}),
+ {atomic, ok}
+ catch _:{badmatch, Err} ->
+ {atomic, Err}
+ end.
+
+get_group_opts(Host, Group) ->
+ case ejabberd_riak:get(sr_group, sr_group_schema(), {Group, Host}) of
+ {ok, #sr_group{opts = Opts}} -> Opts;
+ _ -> error
+ end.
+
+set_group_opts(Host, Group, Opts) ->
+ {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
+ opts = Opts},
+ sr_group_schema(),
+ [{'2i', [{<<"host">>, Host}]}])}.
+
+get_user_groups(US, Host) ->
+ case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
+ {ok, Rs} ->
+ [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
+ _ ->
+ []
+ end.
+
+get_group_explicit_users(Host, Group) ->
+ case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
+ <<"group_host">>, {Group, Host}) of
+ {ok, Rs} ->
+ [R#sr_user.us || R <- Rs];
+ _ ->
+ []
+ end.
+
+get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
+ case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Rs} ->
+ [{Group, proplists:get_value(Group, GroupsOpts, [])}
+ || #sr_user{group_host = {Group, _}} <- Rs];
+ _ ->
+ []
+ end.
+
+is_user_in_group(US, Group, Host) ->
+ case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
+ {ok, Rs} ->
+ lists:any(
+ fun(#sr_user{group_host = {G, H}}) ->
+ (Group == G) and (Host == H)
+ end, Rs);
+ _Err ->
+ false
+ end.
+
+add_user_to_group(Host, US, Group) ->
+ {atomic, ejabberd_riak:put(
+ #sr_user{us = US, group_host = {Group, Host}},
+ sr_user_schema(),
+ [{i, {US, {Group, Host}}},
+ {'2i', [{<<"us">>, US},
+ {<<"group_host">>, {Group, Host}}]}])}.
+
+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) ->
+ ejabberd_riak:put(User, sr_user_schema(),
+ [{i, {US, {Group, Host}}},
+ {'2i', [{<<"us">>, US},
+ {<<"group_host">>, {Group, Host}}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+sr_group_schema() ->
+ {record_info(fields, sr_group), #sr_group{}}.
+
+sr_user_schema() ->
+ {record_info(fields, sr_user), #sr_user{}}.
diff --git a/src/mod_shared_roster_sql.erl b/src/mod_shared_roster_sql.erl
new file mode 100644
index 000000000..bd123cd4a
--- /dev/null
+++ b/src/mod_shared_roster_sql.erl
@@ -0,0 +1,212 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_shared_roster_sql).
+
+-behaviour(mod_shared_roster).
+
+%% API
+-export([init/2, list_groups/1, groups_with_opts/1, create_group/3,
+ 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]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+-include("mod_shared_roster.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+list_groups(Host) ->
+ case ejabberd_sql:sql_query(
+ Host, [<<"select name from sr_group;">>]) of
+ {selected, [<<"name">>], Rs} -> [G || [G] <- Rs];
+ _ -> []
+ end.
+
+groups_with_opts(Host) ->
+ case ejabberd_sql:sql_query(Host,
+ [<<"select name, opts from sr_group;">>])
+ of
+ {selected, [<<"name">>, <<"opts">>], Rs} ->
+ [{G, mod_shared_roster:opts_to_binary(ejabberd_sql:decode_term(Opts))}
+ || [G, Opts] <- Rs];
+ _ -> []
+ end.
+
+create_group(Host, Group, Opts) ->
+ SGroup = ejabberd_sql:escape(Group),
+ SOpts = ejabberd_sql:encode_term(Opts),
+ F = fun () ->
+ sql_queries:update_t(<<"sr_group">>,
+ [<<"name">>, <<"opts">>], [SGroup, SOpts],
+ [<<"name='">>, SGroup, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(Host, F).
+
+delete_group(Host, Group) ->
+ SGroup = ejabberd_sql:escape(Group),
+ F = fun () ->
+ ejabberd_sql:sql_query_t([<<"delete from sr_group where name='">>,
+ SGroup, <<"';">>]),
+ ejabberd_sql:sql_query_t([<<"delete from sr_user where grp='">>,
+ SGroup, <<"';">>])
+ end,
+ case ejabberd_sql:sql_transaction(Host, F) of
+ {atomic,{updated,_}} -> {atomic, ok};
+ Res -> Res
+ end.
+
+get_group_opts(Host, Group) ->
+ SGroup = ejabberd_sql:escape(Group),
+ case catch ejabberd_sql:sql_query(
+ Host,
+ [<<"select opts from sr_group where name='">>,
+ SGroup, <<"';">>]) of
+ {selected, [<<"opts">>], [[SOpts]]} ->
+ mod_shared_roster:opts_to_binary(ejabberd_sql:decode_term(SOpts));
+ _ -> error
+ end.
+
+set_group_opts(Host, Group, Opts) ->
+ SGroup = ejabberd_sql:escape(Group),
+ SOpts = ejabberd_sql:encode_term(Opts),
+ F = fun () ->
+ sql_queries:update_t(<<"sr_group">>,
+ [<<"name">>, <<"opts">>], [SGroup, SOpts],
+ [<<"name='">>, SGroup, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(Host, F).
+
+get_user_groups(US, Host) ->
+ SJID = make_jid_s(US),
+ case catch ejabberd_sql:sql_query(
+ Host,
+ [<<"select grp from sr_user where jid='">>,
+ SJID, <<"';">>]) of
+ {selected, [<<"grp">>], Rs} -> [G || [G] <- Rs];
+ _ -> []
+ end.
+
+get_group_explicit_users(Host, Group) ->
+ SGroup = ejabberd_sql:escape(Group),
+ case catch ejabberd_sql:sql_query(
+ Host,
+ [<<"select jid from sr_user where grp='">>,
+ SGroup, <<"';">>]) of
+ {selected, [<<"jid">>], Rs} ->
+ lists:map(
+ fun([JID]) ->
+ {U, S, _} = jid:tolower(jid:from_string(JID)),
+ {U, S}
+ end, Rs);
+ _ ->
+ []
+ end.
+
+get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
+ SJID = make_jid_s(LUser, LServer),
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ [<<"select grp from sr_user where jid='">>,
+ SJID, <<"';">>]) of
+ {selected, [<<"grp">>], Rs} ->
+ [{Group, proplists:get_value(Group, GroupsOpts, [])}
+ || [Group] <- Rs];
+ _ -> []
+ end.
+
+is_user_in_group(US, Group, Host) ->
+ SJID = make_jid_s(US),
+ SGroup = ejabberd_sql:escape(Group),
+ case catch ejabberd_sql:sql_query(Host,
+ [<<"select * from sr_user where jid='">>,
+ SJID, <<"' and grp='">>, SGroup,
+ <<"';">>]) of
+ {selected, _, []} -> false;
+ _ -> true
+ end.
+
+add_user_to_group(Host, US, Group) ->
+ SJID = make_jid_s(US),
+ SGroup = ejabberd_sql:escape(Group),
+ F = fun () ->
+ sql_queries:update_t(<<"sr_user">>,
+ [<<"jid">>, <<"grp">>], [SJID, SGroup],
+ [<<"jid='">>, SJID, <<"' and grp='">>,
+ SGroup, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(Host, F).
+
+remove_user_from_group(Host, US, Group) ->
+ SJID = make_jid_s(US),
+ SGroup = ejabberd_sql:escape(Group),
+ F = fun () ->
+ ejabberd_sql:sql_query_t([<<"delete from sr_user where jid='">>,
+ SJID, <<"' and grp='">>, SGroup,
+ <<"';">>]),
+ ok
+ end,
+ ejabberd_sql:sql_transaction(Host, F).
+
+export(_Server) ->
+ [{sr_group,
+ fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts})
+ when LServer == Host ->
+ SGroup = ejabberd_sql:escape(Group),
+ SOpts = ejabberd_sql:encode_term(Opts),
+ [[<<"delete from sr_group where name='">>, Group, <<"';">>],
+ [<<"insert into sr_group(name, opts) values ('">>,
+ SGroup, <<"', '">>, SOpts, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end},
+ {sr_user,
+ fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}})
+ when LServer == Host ->
+ SGroup = ejabberd_sql:escape(Group),
+ SJID = ejabberd_sql:escape(
+ jid:to_string(
+ jid:tolower(
+ jid:make(U, S, <<"">>)))),
+ [[<<"delete from sr_user where jid='">>, SJID,
+ <<"'and grp='">>, Group, <<"';">>],
+ [<<"insert into sr_user(jid, grp) values ('">>,
+ SJID, <<"', '">>, SGroup, <<"');">>]];
+ (_Host, _R) ->
+ []
+ 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.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+make_jid_s(U, S) ->
+ ejabberd_sql:escape(jid:to_string(jid:tolower(jid:make(U, S, <<"">>)))).
+
+make_jid_s({U, S}) -> make_jid_s(U, S).
diff --git a/src/mod_sic.erl b/src/mod_sic.erl
index 3ffd9c917..b4eae0daf 100644
--- a/src/mod_sic.erl
+++ b/src/mod_sic.erl
@@ -60,8 +60,9 @@ process_local_iq(#jid{user = User, server = Server,
_To, #iq{type = get, sub_el = _SubEl} = IQ) ->
get_ip({User, Server, Resource}, IQ);
process_local_iq(_From, _To,
- #iq{type = set, sub_el = SubEl} = IQ) ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
+ #iq{type = set, sub_el = SubEl, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
process_sm_iq(#jid{user = User, server = Server,
resource = Resource},
@@ -69,14 +70,17 @@ process_sm_iq(#jid{user = User, server = Server,
#iq{type = get, sub_el = _SubEl} = IQ) ->
get_ip({User, Server, Resource}, IQ);
process_sm_iq(_From, _To,
- #iq{type = get, sub_el = SubEl} = IQ) ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]};
+ #iq{type = get, sub_el = SubEl, 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} = IQ) ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
+ #iq{type = set, sub_el = SubEl, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
get_ip({User, Server, Resource},
- #iq{sub_el =
+ #iq{lang = Lang,
+ sub_el =
#xmlel{name = Name, attrs = Attrs} = SubEl} =
IQ) ->
case ejabberd_sm:get_user_ip(User, Server, Resource) of
@@ -88,8 +92,9 @@ get_ip({User, Server, Resource},
[{xmlcdata,
iolist_to_binary(jlib:ip_to_list(IP))}]}]};
_ ->
+ Txt = <<"User session not found">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+ sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
end.
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
diff --git a/src/mod_stats.erl b/src/mod_stats.erl
index c9f659d3c..c14cf8d15 100644
--- a/src/mod_stats.erl
+++ b/src/mod_stats.erl
@@ -50,17 +50,18 @@ stop(Host) ->
process_local_iq(_From, To,
#iq{id = _ID, type = Type, xmlns = XMLNS,
- sub_el = SubEl} =
+ sub_el = SubEl, lang = Lang} =
IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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(xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl),
<<"/">>),
Names = get_names(Els, []),
- case get_local_stats(To#jid.server, Node, Names) of
+ case get_local_stats(To#jid.server, Node, Names, Lang) of
{result, Res} ->
IQ#iq{type = result,
sub_el =
@@ -76,7 +77,7 @@ get_names([], Res) -> Res;
get_names([#xmlel{name = <<"stat">>, attrs = Attrs}
| Els],
Res) ->
- Name = xml:get_attr_s(<<"name">>, Attrs),
+ Name = fxml:get_attr_s(<<"name">>, Attrs),
case Name of
<<"">> -> get_names(Els, Res);
_ -> get_names(Els, [Name | Res])
@@ -87,18 +88,18 @@ get_names([_ | Els], Res) -> get_names(Els, Res).
#xmlel{name = <<"stat">>, attrs = [{<<"name">>, Name}],
children = []}).
-get_local_stats(_Server, [], []) ->
+get_local_stats(_Server, [], [], _Lang) ->
{result,
[?STAT(<<"users/online">>), ?STAT(<<"users/total">>),
?STAT(<<"users/all-hosts/online">>),
?STAT(<<"users/all-hosts/total">>)]};
-get_local_stats(Server, [], Names) ->
+get_local_stats(Server, [], Names, _Lang) ->
{result,
lists:map(fun (Name) -> get_local_stat(Server, [], Name)
end,
Names)};
get_local_stats(_Server, [<<"running nodes">>, _],
- []) ->
+ [], _Lang) ->
{result,
[?STAT(<<"time/uptime">>), ?STAT(<<"time/cputime">>),
?STAT(<<"users/online">>),
@@ -107,16 +108,19 @@ get_local_stats(_Server, [<<"running nodes">>, _],
?STAT(<<"transactions/restarted">>),
?STAT(<<"transactions/logged">>)]};
get_local_stats(_Server, [<<"running nodes">>, ENode],
- Names) ->
+ Names, Lang) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
{result,
lists:map(fun (Name) -> get_node_stat(Node, Name) end,
Names)}
end;
-get_local_stats(_Server, _, _) ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}.
+get_local_stats(_Server, _, _, Lang) ->
+ Txt = <<"No statistics found for this item">>,
+ {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)}.
-define(STATVAL(Val, Unit),
#xmlel{name = <<"stat">>,
diff --git a/src/mod_time.erl b/src/mod_time.erl
index 8bfe9f9f9..740b654a7 100644
--- a/src/mod_time.erl
+++ b/src/mod_time.erl
@@ -51,10 +51,11 @@ stop(Host) ->
?NS_TIME).
process_local_iq(_From, _To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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),
diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl
index db19b5570..e5f5d9e3c 100644
--- a/src/mod_vcard.erl
+++ b/src/mod_vcard.erl
@@ -33,53 +33,31 @@
-behaviour(gen_mod).
-export([start/2, init/3, stop/1, get_sm_features/5,
- process_local_iq/3, process_sm_iq/3, reindex_vcards/0,
+ process_local_iq/3, process_sm_iq/3, string2lower/1,
remove_user/2, export/1, import/1, import/3,
- mod_opt_type/1]).
+ mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
-include("ejabberd.hrl").
-include("logger.hrl").
-
-include("jlib.hrl").
+-include("mod_vcard.hrl").
-define(JUD_MATCHES, 30).
--record(vcard_search,
- {us, user, luser, fn, lfn, family, lfamily, given,
- lgiven, middle, lmiddle, nickname, lnickname, bday,
- lbday, ctry, lctry, locality, llocality, email, lemail,
- orgname, lorgname, orgunit, lorgunit}).
-
--record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(),
- vcard = #xmlel{} :: xmlel()}).
-
-define(PROCNAME, ejabberd_mod_vcard).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #vcard{} | #vcard_search{}) -> ok | pass.
+-callback get_vcard(binary(), binary()) -> [xmlel()] | error.
+-callback set_vcard(binary(), binary(),
+ xmlel(), #vcard_search{}) -> {atomic, any()}.
+-callback search(binary(), [{binary(), [binary()]}], boolean(),
+ infinity | pos_integer()) -> [binary()].
+-callback remove_user(binary(), binary()) -> {atomic, any()}.
+
start(Host, Opts) ->
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(vcard,
- [{disc_only_copies, [node()]},
- {attributes, record_info(fields, vcard)}]),
- mnesia:create_table(vcard_search,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, vcard_search)}]),
- update_tables(),
- mnesia:add_table_index(vcard_search, luser),
- mnesia:add_table_index(vcard_search, lfn),
- mnesia:add_table_index(vcard_search, lfamily),
- mnesia:add_table_index(vcard_search, lgiven),
- mnesia:add_table_index(vcard_search, lmiddle),
- mnesia:add_table_index(vcard_search, lnickname),
- mnesia:add_table_index(vcard_search, lbday),
- mnesia:add_table_index(vcard_search, lctry),
- mnesia:add_table_index(vcard_search, llocality),
- mnesia:add_table_index(vcard_search, lemail),
- mnesia:add_table_index(vcard_search, lorgname),
- mnesia:add_table_index(vcard_search, lorgunit);
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@@ -102,7 +80,7 @@ init(Host, ServerHost, Search) ->
case Search of
false -> loop(Host, ServerHost);
_ ->
- ejabberd_router:register_route(Host),
+ ejabberd_router:register_route(Host, ServerHost),
loop(Host, ServerHost)
end.
@@ -149,7 +127,8 @@ process_local_iq(_From, _To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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 =
@@ -173,7 +152,7 @@ process_local_iq(_From, _To,
end.
process_sm_iq(From, To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
#jid{user = User, lserver = LServer} = From,
@@ -182,14 +161,16 @@ process_sm_iq(From, To,
set_vcard(User, LServer, SubEl),
IQ#iq{type = result, sub_el = []};
false ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ 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, ?ERR_INTERNAL_SERVER_ERROR]};
+ sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
[] ->
IQ#iq{type = result,
sub_el = [#xmlel{name = <<"vCard">>,
@@ -200,68 +181,38 @@ process_sm_iq(From, To,
end.
get_vcard(LUser, LServer) ->
- get_vcard(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_vcard(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () -> mnesia:read({vcard, US}) end,
- case mnesia:transaction(F) of
- {atomic, Rs} ->
- lists:map(fun (R) -> R#vcard.vcard end, Rs);
- {aborted, _Reason} -> error
- end;
-get_vcard(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_vcard(LServer, Username) of
- {selected, [<<"vcard">>], [[SVCARD]]} ->
- case xml_stream:parse_element(SVCARD) of
- {error, _Reason} -> error;
- VCARD -> [VCARD]
- end;
- {selected, [<<"vcard">>], []} -> [];
- _ -> error
- end;
-get_vcard(LUser, LServer, riak) ->
- case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of
- {ok, R} ->
- [R#vcard.vcard];
- {error, notfound} ->
- [];
- _ ->
- error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_vcard(LUser, LServer).
-set_vcard(User, LServer, VCARD) ->
- FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
- Family = xml:get_path_s(VCARD,
+make_vcard_search(User, LUser, LServer, VCARD) ->
+ FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
+ Family = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
- Given = xml:get_path_s(VCARD,
+ Given = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
- Middle = xml:get_path_s(VCARD,
+ Middle = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
- Nickname = xml:get_path_s(VCARD,
+ Nickname = fxml:get_path_s(VCARD,
[{elem, <<"NICKNAME">>}, cdata]),
- BDay = xml:get_path_s(VCARD,
+ BDay = fxml:get_path_s(VCARD,
[{elem, <<"BDAY">>}, cdata]),
- CTRY = xml:get_path_s(VCARD,
+ CTRY = fxml:get_path_s(VCARD,
[{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
- Locality = xml:get_path_s(VCARD,
+ Locality = fxml:get_path_s(VCARD,
[{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
cdata]),
- EMail1 = xml:get_path_s(VCARD,
+ EMail1 = fxml:get_path_s(VCARD,
[{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
- EMail2 = xml:get_path_s(VCARD,
+ EMail2 = fxml:get_path_s(VCARD,
[{elem, <<"EMAIL">>}, cdata]),
- OrgName = xml:get_path_s(VCARD,
+ OrgName = fxml:get_path_s(VCARD,
[{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
- OrgUnit = xml:get_path_s(VCARD,
+ OrgUnit = fxml:get_path_s(VCARD,
[{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
EMail = case EMail1 of
<<"">> -> EMail2;
_ -> EMail1
end,
- LUser = jid:nodeprep(User),
LFN = string2lower(FN),
LFamily = string2lower(Family),
LGiven = string2lower(Given),
@@ -273,105 +224,42 @@ set_vcard(User, LServer, VCARD) ->
LEMail = string2lower(EMail),
LOrgName = string2lower(OrgName),
LOrgUnit = string2lower(OrgUnit),
- if (LUser == error) ->
- {error, badarg};
- true ->
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:write(#vcard{us = US, vcard = VCARD}),
- mnesia:write(#vcard_search{us = US,
- 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,
- mnesia:transaction(F);
- riak ->
- US = {LUser, LServer},
- ejabberd_riak:put(#vcard{us = US, vcard = VCARD},
- vcard_schema(),
- [{'2i', [{<<"user">>, User},
- {<<"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}]}]);
- odbc ->
- Username = ejabberd_odbc:escape(User),
- LUsername = ejabberd_odbc:escape(LUser),
- SVCARD =
- ejabberd_odbc:escape(xml:element_to_binary(VCARD)),
- SFN = ejabberd_odbc:escape(FN),
- SLFN = ejabberd_odbc:escape(LFN),
- SFamily = ejabberd_odbc:escape(Family),
- SLFamily = ejabberd_odbc:escape(LFamily),
- SGiven = ejabberd_odbc:escape(Given),
- SLGiven = ejabberd_odbc:escape(LGiven),
- SMiddle = ejabberd_odbc:escape(Middle),
- SLMiddle = ejabberd_odbc:escape(LMiddle),
- SNickname = ejabberd_odbc:escape(Nickname),
- SLNickname = ejabberd_odbc:escape(LNickname),
- SBDay = ejabberd_odbc:escape(BDay),
- SLBDay = ejabberd_odbc:escape(LBDay),
- SCTRY = ejabberd_odbc:escape(CTRY),
- SLCTRY = ejabberd_odbc:escape(LCTRY),
- SLocality = ejabberd_odbc:escape(Locality),
- SLLocality = ejabberd_odbc:escape(LLocality),
- SEMail = ejabberd_odbc:escape(EMail),
- SLEMail = ejabberd_odbc:escape(LEMail),
- SOrgName = ejabberd_odbc:escape(OrgName),
- SLOrgName = ejabberd_odbc:escape(LOrgName),
- SOrgUnit = ejabberd_odbc:escape(OrgUnit),
- SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
- odbc_queries:set_vcard(LServer, LUsername, SBDay, SCTRY,
- SEMail, SFN, SFamily, SGiven, SLBDay,
- SLCTRY, SLEMail, SLFN, SLFamily,
- SLGiven, SLLocality, SLMiddle,
- SLNickname, SLOrgName, SLOrgUnit,
- SLocality, SMiddle, SNickname, SOrgName,
- SOrgUnit, SVCARD, Username)
- end,
- ejabberd_hooks:run(vcard_set, LServer,
- [LUser, LServer, VCARD])
+ US = {LUser, LServer},
+ #vcard_search{us = US,
+ 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}.
+
+set_vcard(User, LServer, VCARD) ->
+ case jid:nodeprep(User) of
+ error ->
+ {error, badarg};
+ LUser ->
+ VCardSearch = make_vcard_search(User, LUser, LServer, VCARD),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_vcard(LUser, LServer, VCARD, VCardSearch),
+ ejabberd_hooks:run(vcard_set, LServer,
+ [LUser, LServer, VCARD])
end.
string2lower(String) ->
@@ -445,15 +333,17 @@ do_route(ServerHost, From, To, Packet) ->
XDataEl = find_xdata_el(SubEl),
case XDataEl of
false ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_BAD_REQUEST),
+ 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 ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_BAD_REQUEST),
+ 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,
@@ -493,7 +383,8 @@ do_route(ServerHost, From, To, Packet) ->
#iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
case Type of
set ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ 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,
@@ -539,10 +430,11 @@ do_route(ServerHost, From, To, Packet) ->
++ Info}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
end;
- #iq{type = Type, xmlns = ?NS_DISCO_ITEMS} ->
+ #iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} ->
case Type of
set ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ 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,
@@ -587,7 +479,7 @@ find_xdata_el1([]) -> false;
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
children = SubEls}
| Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
#xmlel{name = Name, attrs = Attrs, children = SubEls};
_ -> find_xdata_el1(Els)
@@ -671,498 +563,36 @@ record_to_item(_LServer, #vcard_search{} = R) ->
?FIELD(<<"orgunit">>, (R#vcard_search.orgunit))]}.
search(LServer, Data) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- MatchSpec = make_matchspec(LServer, Data, DBType),
+ 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,
false),
- search(LServer, MatchSpec, AllowReturnAll, DBType).
-
-search(LServer, MatchSpec, AllowReturnAll, mnesia) ->
- if (MatchSpec == #vcard_search{_ = '_'}) and
- not AllowReturnAll ->
- [];
- true ->
- case catch mnesia:dirty_select(vcard_search,
- [{MatchSpec, [], ['$_']}])
- of
- {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
- Rs ->
- case gen_mod:get_module_opt(LServer, ?MODULE, matches,
- fun(infinity) -> infinity;
- (I) when is_integer(I),
- I>0 ->
- I
- end, ?JUD_MATCHES) of
- infinity ->
- Rs;
- Val ->
- lists:sublist(Rs, Val)
- end
- end
- end;
-search(LServer, MatchSpec, AllowReturnAll, odbc) ->
- if (MatchSpec == <<"">>) and not AllowReturnAll -> [];
- true ->
- Limit = case gen_mod:get_module_opt(LServer, ?MODULE, matches,
- fun(infinity) -> infinity;
- (I) when is_integer(I),
- I>0 ->
- I
- end, ?JUD_MATCHES) of
- infinity ->
- <<"">>;
- Val ->
- [<<" LIMIT ">>,
- jlib:integer_to_binary(Val)]
- end,
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select username, fn, family, given, "
- "middle, nickname, bday, ctry, "
- "locality, email, orgname, orgunit "
- "from vcard_search ">>,
- MatchSpec, Limit, <<";">>])
- of
- {selected,
- [<<"username">>, <<"fn">>, <<"family">>, <<"given">>,
- <<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>,
- <<"locality">>, <<"email">>, <<"orgname">>,
- <<"orgunit">>],
- Rs}
- when is_list(Rs) ->
- Rs;
- Error -> ?ERROR_MSG("~p", [Error]), []
- end
- end;
-search(_LServer, _MatchSpec, _AllowReturnAll, riak) ->
- [].
-
-make_matchspec(LServer, Data, mnesia) ->
- GlobMatch = #vcard_search{_ = '_'},
- Match = filter_fields(Data, GlobMatch, LServer, mnesia),
- Match;
-make_matchspec(LServer, Data, odbc) ->
- filter_fields(Data, <<"">>, LServer, odbc);
-make_matchspec(_LServer, _Data, riak) ->
- [].
-
-filter_fields([], Match, _LServer, mnesia) -> Match;
-filter_fields([], Match, _LServer, odbc) ->
- case Match of
- <<"">> -> <<"">>;
- _ -> [<<" where ">>, Match]
- end;
-filter_fields([{SVar, [Val]} | Ds], Match, LServer,
- mnesia)
- when is_binary(Val) and (Val /= <<"">>) ->
- LVal = string2lower(Val),
- NewMatch = case SVar of
- <<"user">> ->
- case gen_mod:get_module_opt(LServer, ?MODULE,
- search_all_hosts,
- fun(B) when is_boolean(B) ->
- B
- end, true)
- of
- true -> Match#vcard_search{luser = make_val(LVal)};
- false ->
- Host = find_my_host(LServer),
- Match#vcard_search{us = {make_val(LVal), Host}}
- end;
- <<"fn">> -> Match#vcard_search{lfn = make_val(LVal)};
- <<"last">> ->
- Match#vcard_search{lfamily = make_val(LVal)};
- <<"first">> ->
- Match#vcard_search{lgiven = make_val(LVal)};
- <<"middle">> ->
- Match#vcard_search{lmiddle = make_val(LVal)};
- <<"nick">> ->
- Match#vcard_search{lnickname = make_val(LVal)};
- <<"bday">> ->
- Match#vcard_search{lbday = make_val(LVal)};
- <<"ctry">> ->
- Match#vcard_search{lctry = make_val(LVal)};
- <<"locality">> ->
- Match#vcard_search{llocality = make_val(LVal)};
- <<"email">> ->
- Match#vcard_search{lemail = make_val(LVal)};
- <<"orgname">> ->
- Match#vcard_search{lorgname = make_val(LVal)};
- <<"orgunit">> ->
- Match#vcard_search{lorgunit = make_val(LVal)};
- _ -> Match
- end,
- filter_fields(Ds, NewMatch, LServer, mnesia);
-filter_fields([{SVar, [Val]} | Ds], Match, LServer,
- odbc)
- when is_binary(Val) and (Val /= <<"">>) ->
- LVal = string2lower(Val),
- NewMatch = case SVar of
- <<"user">> -> make_val(Match, <<"lusername">>, LVal);
- <<"fn">> -> make_val(Match, <<"lfn">>, LVal);
- <<"last">> -> make_val(Match, <<"lfamily">>, LVal);
- <<"first">> -> make_val(Match, <<"lgiven">>, LVal);
- <<"middle">> -> make_val(Match, <<"lmiddle">>, LVal);
- <<"nick">> -> make_val(Match, <<"lnickname">>, LVal);
- <<"bday">> -> make_val(Match, <<"lbday">>, LVal);
- <<"ctry">> -> make_val(Match, <<"lctry">>, LVal);
- <<"locality">> ->
- make_val(Match, <<"llocality">>, LVal);
- <<"email">> -> make_val(Match, <<"lemail">>, LVal);
- <<"orgname">> -> make_val(Match, <<"lorgname">>, LVal);
- <<"orgunit">> -> make_val(Match, <<"lorgunit">>, LVal);
- _ -> Match
- end,
- filter_fields(Ds, NewMatch, LServer, odbc);
-filter_fields([_ | Ds], Match, LServer, DBType) ->
- filter_fields(Ds, Match, LServer, DBType).
-
-make_val(Match, Field, Val) ->
- Condition = case str:suffix(<<"*">>, Val) of
- true ->
- Val1 = str:substr(Val, 1, byte_size(Val) - 1),
- SVal = <<(ejabberd_odbc:escape_like(Val1))/binary,
- "%">>,
- [Field, <<" LIKE '">>, SVal, <<"'">>];
- _ ->
- SVal = ejabberd_odbc:escape(Val),
- [Field, <<" = '">>, SVal, <<"'">>]
- end,
- case Match of
- <<"">> -> Condition;
- _ -> [Match, <<" and ">>, Condition]
- end.
-
-make_val(Val) ->
- case str:suffix(<<"*">>, Val) of
- true -> [str:substr(Val, 1, byte_size(Val) - 1)] ++ '_';
- _ -> Val
- end.
-
-find_my_host(LServer) ->
- Parts = str:tokens(LServer, <<".">>),
- find_my_host(Parts, ?MYHOSTS).
-
-find_my_host([], _Hosts) -> ?MYNAME;
-find_my_host([_ | Tail] = Parts, Hosts) ->
- Domain = parts_to_string(Parts),
- case lists:member(Domain, Hosts) of
- true -> Domain;
- false -> find_my_host(Tail, Hosts)
- end.
-
-parts_to_string(Parts) ->
- str:strip(list_to_binary(
- lists:map(fun (S) -> <<S/binary, $.>> end, Parts)),
- right, $.).
+ MaxMatch = gen_mod:get_module_opt(LServer, ?MODULE, matches,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I),
+ I>0 ->
+ I
+ end, ?JUD_MATCHES),
+ Mod:search(LServer, Data, AllowReturnAll, MaxMatch).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-set_vcard_t(R, _) ->
- US = R#vcard.us,
- User = US,
- VCARD = R#vcard.vcard,
- FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
- Family = xml:get_path_s(VCARD,
- [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
- Given = xml:get_path_s(VCARD,
- [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
- Middle = xml:get_path_s(VCARD,
- [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
- Nickname = xml:get_path_s(VCARD,
- [{elem, <<"NICKNAME">>}, cdata]),
- BDay = xml:get_path_s(VCARD,
- [{elem, <<"BDAY">>}, cdata]),
- CTRY = xml:get_path_s(VCARD,
- [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
- Locality = xml:get_path_s(VCARD,
- [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
- cdata]),
- EMail = xml:get_path_s(VCARD,
- [{elem, <<"EMAIL">>}, cdata]),
- OrgName = xml:get_path_s(VCARD,
- [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
- OrgUnit = xml:get_path_s(VCARD,
- [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
- {LUser, _LServer} = US,
- LFN = string2lower(FN),
- LFamily = string2lower(Family),
- LGiven = string2lower(Given),
- LMiddle = string2lower(Middle),
- LNickname = string2lower(Nickname),
- LBDay = string2lower(BDay),
- LCTRY = string2lower(CTRY),
- LLocality = string2lower(Locality),
- LEMail = string2lower(EMail),
- LOrgName = string2lower(OrgName),
- LOrgUnit = string2lower(OrgUnit),
- mnesia:write(#vcard_search{us = US, user = User,
- 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}).
-
-reindex_vcards() ->
- F = fun () -> mnesia:foldl(fun set_vcard_t/2, [], vcard)
- end,
- mnesia:transaction(F).
-
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- remove_user(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_user(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:delete({vcard, US}),
- mnesia:delete({vcard_search, US})
- end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- ejabberd_odbc:sql_transaction(LServer,
- [[<<"delete from vcard where username='">>,
- Username, <<"';">>],
- [<<"delete from vcard_search where lusername='">>,
- Username, <<"';">>]]);
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
-update_tables() ->
- update_vcard_table(),
- update_vcard_search_table().
-
-update_vcard_table() ->
- Fields = record_info(fields, vcard),
- case mnesia:table_info(vcard, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- vcard, Fields, set,
- fun(#vcard{us = {U, _}}) -> U end,
- fun(#vcard{us = {U, S}, vcard = El} = R) ->
- R#vcard{us = {iolist_to_binary(U),
- iolist_to_binary(S)},
- vcard = xml:to_xmlel(El)}
- end);
- _ ->
- ?INFO_MSG("Recreating vcard table", []),
- mnesia:transform_table(vcard, ignore, Fields)
- end.
-
-update_vcard_search_table() ->
- Fields = record_info(fields, vcard_search),
- case mnesia:table_info(vcard_search, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- vcard_search, Fields, set,
- fun(#vcard_search{us = {U, _}}) -> U end,
- fun(#vcard_search{} = VS) ->
- [vcard_search | L] = tuple_to_list(VS),
- NewL = lists:map(
- fun({U, S}) ->
- {iolist_to_binary(U),
- iolist_to_binary(S)};
- (Str) ->
- iolist_to_binary(Str)
- end, L),
- list_to_tuple([vcard_search | NewL])
- end);
- _ ->
- ?INFO_MSG("Recreating vcard_search table", []),
- mnesia:transform_table(vcard_search, ignore, Fields)
- end.
-
-vcard_schema() ->
- {record_info(fields, vcard), #vcard{}}.
-
-export(_Server) ->
- [{vcard,
- fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SVCARD =
- ejabberd_odbc:escape(xml:element_to_binary(VCARD)),
- [[<<"delete from vcard where username='">>, Username, <<"';">>],
- [<<"insert into vcard(username, vcard) values ('">>,
- Username, <<"', '">>, SVCARD, <<"');">>]];
- (_Host, _R) ->
- []
- end},
- {vcard_search,
- fun(Host, #vcard_search{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})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(User),
- LUsername = ejabberd_odbc:escape(LUser),
- SFN = ejabberd_odbc:escape(FN),
- SLFN = ejabberd_odbc:escape(LFN),
- SFamily = ejabberd_odbc:escape(Family),
- SLFamily = ejabberd_odbc:escape(LFamily),
- SGiven = ejabberd_odbc:escape(Given),
- SLGiven = ejabberd_odbc:escape(LGiven),
- SMiddle = ejabberd_odbc:escape(Middle),
- SLMiddle = ejabberd_odbc:escape(LMiddle),
- SNickname = ejabberd_odbc:escape(Nickname),
- SLNickname = ejabberd_odbc:escape(LNickname),
- SBDay = ejabberd_odbc:escape(BDay),
- SLBDay = ejabberd_odbc:escape(LBDay),
- SCTRY = ejabberd_odbc:escape(CTRY),
- SLCTRY = ejabberd_odbc:escape(LCTRY),
- SLocality = ejabberd_odbc:escape(Locality),
- SLLocality = ejabberd_odbc:escape(LLocality),
- SEMail = ejabberd_odbc:escape(EMail),
- SLEMail = ejabberd_odbc:escape(LEMail),
- SOrgName = ejabberd_odbc:escape(OrgName),
- SLOrgName = ejabberd_odbc:escape(LOrgName),
- SOrgUnit = ejabberd_odbc:escape(OrgUnit),
- SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
- [[<<"delete from vcard_search where lusername='">>,
- LUsername, <<"';">>],
- [<<"insert into vcard_search( username, "
- "lusername, fn, lfn, family, lfamily, "
- " given, lgiven, middle, lmiddle, "
- "nickname, lnickname, bday, lbday, "
- "ctry, lctry, locality, llocality, "
- " email, lemail, orgname, lorgname, "
- "orgunit, lorgunit)values (">>,
- <<" '">>, Username, <<"', '">>, LUsername,
- <<"', '">>, SFN, <<"', '">>, SLFN,
- <<"', '">>, SFamily, <<"', '">>, SLFamily,
- <<"', '">>, SGiven, <<"', '">>, SLGiven,
- <<"', '">>, SMiddle, <<"', '">>, SLMiddle,
- <<"', '">>, SNickname, <<"', '">>, SLNickname,
- <<"', '">>, SBDay, <<"', '">>, SLBDay,
- <<"', '">>, SCTRY, <<"', '">>, SLCTRY,
- <<"', '">>, SLocality, <<"', '">>, SLLocality,
- <<"', '">>, SEMail, <<"', '">>, SLEMail,
- <<"', '">>, SOrgName, <<"', '">>, SLOrgName,
- <<"', '">>, SOrgUnit, <<"', '">>, SLOrgUnit,
- <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username, vcard from vcard;">>,
- fun([LUser, SVCard]) ->
- #xmlel{} = VCARD = xml_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}].
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-import(_LServer, mnesia, #vcard{} = VCard) ->
- mnesia:dirty_write(VCard);
-import(_LServer, mnesia, #vcard_search{} = S) ->
- mnesia:dirty_write(S);
-import(_LServer, riak, #vcard{us = {LUser, _}, vcard = El} = VCard) ->
- FN = xml:get_path_s(El, [{elem, <<"FN">>}, cdata]),
- Family = xml:get_path_s(El,
- [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
- Given = xml:get_path_s(El,
- [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
- Middle = xml:get_path_s(El,
- [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
- Nickname = xml:get_path_s(El,
- [{elem, <<"NICKNAME">>}, cdata]),
- BDay = xml:get_path_s(El,
- [{elem, <<"BDAY">>}, cdata]),
- CTRY = xml:get_path_s(El,
- [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
- Locality = xml:get_path_s(El,
- [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
- cdata]),
- EMail1 = xml:get_path_s(El,
- [{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
- EMail2 = xml:get_path_s(El,
- [{elem, <<"EMAIL">>}, cdata]),
- OrgName = xml:get_path_s(El,
- [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
- OrgUnit = xml:get_path_s(El,
- [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
- EMail = case EMail1 of
- <<"">> -> EMail2;
- _ -> EMail1
- end,
- LFN = string2lower(FN),
- LFamily = string2lower(Family),
- LGiven = string2lower(Given),
- LMiddle = string2lower(Middle),
- LNickname = string2lower(Nickname),
- LBDay = string2lower(BDay),
- LCTRY = string2lower(CTRY),
- LLocality = string2lower(Locality),
- LEMail = string2lower(EMail),
- LOrgName = string2lower(OrgName),
- LOrgUnit = string2lower(OrgUnit),
- ejabberd_riak:put(VCard, vcard_schema(),
- [{'2i', [{<<"user">>, LUser},
- {<<"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}]}]);
-import(_LServer, riak, #vcard_search{}) ->
- ok;
-import(_, _, _) ->
- pass.
+import(LServer, DBType, VCard) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, VCard).
mod_opt_type(allow_return_all) ->
fun (B) when is_boolean(B) -> B end;
diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl
index b24356d8e..46f81af09 100644
--- a/src/mod_vcard_ldap.erl
+++ b/src/mod_vcard_ldap.erl
@@ -21,7 +21,7 @@
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
-%%%----------------------------------------------------------------------
+%%%---------------------u-------------------------------------------------
-module(mod_vcard_ldap).
@@ -173,7 +173,7 @@ init([Host, Opts]) ->
State#state.password, State#state.tls_options),
case State#state.search of
true ->
- ejabberd_router:register_route(State#state.myhost);
+ ejabberd_router:register_route(State#state.myhost, Host);
_ -> ok
end,
{ok, State}.
@@ -206,7 +206,8 @@ process_local_iq(_From, _To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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 =
@@ -240,10 +241,11 @@ process_sm_iq(_From, #jid{lserver = LServer} = To,
process_vcard_ldap(To, IQ, Server) ->
{ok, State} = eldap_utils:get_state(Server, ?PROCNAME),
- #iq{type = Type, sub_el = SubEl} = IQ,
+ #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ,
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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,
@@ -455,15 +457,17 @@ route(State, From, To, Packet) ->
XDataEl = find_xdata_el(SubEl),
case XDataEl of
false ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_BAD_REQUEST),
+ 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 ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_BAD_REQUEST),
+ 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,
@@ -505,7 +509,8 @@ route(State, From, To, Packet) ->
#iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
case Type of
set ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ 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,
@@ -545,10 +550,11 @@ route(State, From, To, Packet) ->
++ Info}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
end;
- #iq{type = Type, xmlns = ?NS_DISCO_ITEMS} ->
+ #iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} ->
case Type of
set ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ 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,
@@ -723,7 +729,7 @@ find_xdata_el1([]) -> false;
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
children = SubEls}
| Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
#xmlel{name = Name, attrs = Attrs, children = SubEls};
_ -> find_xdata_el1(Els)
diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl
new file mode 100644
index 000000000..781a135c8
--- /dev/null
+++ b/src/mod_vcard_mnesia.erl
@@ -0,0 +1,213 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_mnesia).
+
+-behaviour(mod_vcard).
+
+%% API
+-export([init/2, import/2, get_vcard/2, set_vcard/4, search/4, remove_user/2]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("mod_vcard.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(vcard,
+ [{disc_only_copies, [node()]},
+ {attributes, record_info(fields, vcard)}]),
+ mnesia:create_table(vcard_search,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, vcard_search)}]),
+ update_tables(),
+ mnesia:add_table_index(vcard_search, luser),
+ mnesia:add_table_index(vcard_search, lfn),
+ mnesia:add_table_index(vcard_search, lfamily),
+ mnesia:add_table_index(vcard_search, lgiven),
+ mnesia:add_table_index(vcard_search, lmiddle),
+ mnesia:add_table_index(vcard_search, lnickname),
+ mnesia:add_table_index(vcard_search, lbday),
+ mnesia:add_table_index(vcard_search, lctry),
+ mnesia:add_table_index(vcard_search, llocality),
+ mnesia:add_table_index(vcard_search, lemail),
+ mnesia:add_table_index(vcard_search, lorgname),
+ mnesia:add_table_index(vcard_search, lorgunit).
+
+get_vcard(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () -> mnesia:read({vcard, US}) end,
+ case mnesia:transaction(F) of
+ {atomic, Rs} ->
+ lists:map(fun (R) -> R#vcard.vcard end, Rs);
+ {aborted, _Reason} -> error
+ end.
+
+set_vcard(LUser, LServer, VCARD, VCardSearch) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:write(#vcard{us = US, vcard = VCARD}),
+ mnesia:write(VCardSearch)
+ end,
+ mnesia:transaction(F).
+
+search(LServer, Data, AllowReturnAll, MaxMatch) ->
+ MatchSpec = make_matchspec(LServer, Data),
+ if (MatchSpec == #vcard_search{_ = '_'}) and
+ not AllowReturnAll ->
+ [];
+ true ->
+ case catch mnesia:dirty_select(vcard_search,
+ [{MatchSpec, [], ['$_']}]) of
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p", [Reason]), [];
+ Rs ->
+ case MaxMatch of
+ infinity ->
+ Rs;
+ Val ->
+ lists:sublist(Rs, Val)
+ end
+ end
+ end.
+
+remove_user(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:delete({vcard, US}),
+ mnesia:delete({vcard_search, US})
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #vcard{} = VCard) ->
+ mnesia:dirty_write(VCard);
+import(_LServer, #vcard_search{} = S) ->
+ mnesia:dirty_write(S).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_tables() ->
+ update_vcard_table(),
+ update_vcard_search_table().
+
+update_vcard_table() ->
+ Fields = record_info(fields, vcard),
+ case mnesia:table_info(vcard, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ vcard, Fields, set,
+ fun(#vcard{us = {U, _}}) -> U end,
+ fun(#vcard{us = {U, S}, vcard = El} = R) ->
+ R#vcard{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ vcard = fxml:to_xmlel(El)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating vcard table", []),
+ mnesia:transform_table(vcard, ignore, Fields)
+ end.
+
+update_vcard_search_table() ->
+ Fields = record_info(fields, vcard_search),
+ case mnesia:table_info(vcard_search, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ vcard_search, Fields, set,
+ fun(#vcard_search{us = {U, _}}) -> U end,
+ fun(#vcard_search{} = VS) ->
+ [vcard_search | L] = tuple_to_list(VS),
+ NewL = lists:map(
+ fun({U, S}) ->
+ {iolist_to_binary(U),
+ iolist_to_binary(S)};
+ (Str) ->
+ iolist_to_binary(Str)
+ end, L),
+ list_to_tuple([vcard_search | NewL])
+ end);
+ _ ->
+ ?INFO_MSG("Recreating vcard_search table", []),
+ mnesia:transform_table(vcard_search, ignore, Fields)
+ end.
+
+make_matchspec(LServer, Data) ->
+ GlobMatch = #vcard_search{_ = '_'},
+ Match = filter_fields(Data, GlobMatch, LServer),
+ Match.
+
+filter_fields([], Match, _LServer) ->
+ Match;
+filter_fields([{SVar, [Val]} | Ds], Match, LServer)
+ when is_binary(Val) and (Val /= <<"">>) ->
+ LVal = mod_vcard:string2lower(Val),
+ NewMatch = case SVar of
+ <<"user">> ->
+ case gen_mod:get_module_opt(LServer, ?MODULE,
+ search_all_hosts,
+ fun(B) when is_boolean(B) ->
+ B
+ end, true) of
+ true -> Match#vcard_search{luser = make_val(LVal)};
+ false ->
+ Host = find_my_host(LServer),
+ Match#vcard_search{us = {make_val(LVal), Host}}
+ end;
+ <<"fn">> -> Match#vcard_search{lfn = make_val(LVal)};
+ <<"last">> ->
+ Match#vcard_search{lfamily = make_val(LVal)};
+ <<"first">> ->
+ Match#vcard_search{lgiven = make_val(LVal)};
+ <<"middle">> ->
+ Match#vcard_search{lmiddle = make_val(LVal)};
+ <<"nick">> ->
+ Match#vcard_search{lnickname = make_val(LVal)};
+ <<"bday">> ->
+ Match#vcard_search{lbday = make_val(LVal)};
+ <<"ctry">> ->
+ Match#vcard_search{lctry = make_val(LVal)};
+ <<"locality">> ->
+ Match#vcard_search{llocality = make_val(LVal)};
+ <<"email">> ->
+ Match#vcard_search{lemail = make_val(LVal)};
+ <<"orgname">> ->
+ Match#vcard_search{lorgname = make_val(LVal)};
+ <<"orgunit">> ->
+ Match#vcard_search{lorgunit = make_val(LVal)};
+ _ -> Match
+ end,
+ filter_fields(Ds, NewMatch, LServer);
+filter_fields([_ | Ds], Match, LServer) ->
+ filter_fields(Ds, Match, LServer).
+
+make_val(Val) ->
+ case str:suffix(<<"*">>, Val) of
+ true -> [str:substr(Val, 1, byte_size(Val) - 1)] ++ '_';
+ _ -> Val
+ end.
+
+find_my_host(LServer) ->
+ Parts = str:tokens(LServer, <<".">>),
+ find_my_host(Parts, ?MYHOSTS).
+
+find_my_host([], _Hosts) -> ?MYNAME;
+find_my_host([_ | Tail] = Parts, Hosts) ->
+ Domain = parts_to_string(Parts),
+ case lists:member(Domain, Hosts) of
+ true -> Domain;
+ false -> find_my_host(Tail, Hosts)
+ end.
+
+parts_to_string(Parts) ->
+ str:strip(list_to_binary(
+ lists:map(fun (S) -> <<S/binary, $.>> end, Parts)),
+ right, $.).
diff --git a/src/mod_vcard_riak.erl b/src/mod_vcard_riak.erl
new file mode 100644
index 000000000..386347387
--- /dev/null
+++ b/src/mod_vcard_riak.erl
@@ -0,0 +1,151 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_riak).
+
+-behaviour(mod_vcard).
+
+%% API
+-export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2,
+ import/2]).
+
+-include("jlib.hrl").
+-include("mod_vcard.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_vcard(LUser, LServer) ->
+ case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of
+ {ok, R} ->
+ [R#vcard.vcard];
+ {error, notfound} ->
+ [];
+ _ ->
+ error
+ end.
+
+set_vcard(LUser, LServer, VCARD,
+ #vcard_search{user = {User, _},
+ 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}) ->
+ US = {LUser, LServer},
+ {atomic,
+ ejabberd_riak:put(#vcard{us = US, vcard = VCARD},
+ vcard_schema(),
+ [{'2i', [{<<"user">>, User},
+ {<<"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}]}])}.
+
+search(_LServer, _Data, _AllowReturnAll, _MaxMatch) ->
+ [].
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}.
+
+import(_LServer, #vcard{us = {LUser, LServer}, vcard = El} = VCard) ->
+ #vcard_search{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} =
+ mod_vcard:make_vcard_search(LUser, LUser, LServer, El),
+ ejabberd_riak:put(VCard, vcard_schema(),
+ [{'2i', [{<<"user">>, LUser},
+ {<<"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}]}]);
+import(_LServer, #vcard_search{}) ->
+ ok.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+vcard_schema() ->
+ {record_info(fields, vcard), #vcard{}}.
diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl
new file mode 100644
index 000000000..9fcd5cd5e
--- /dev/null
+++ b/src/mod_vcard_sql.erl
@@ -0,0 +1,268 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_sql).
+
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
+-behaviour(mod_vcard).
+
+%% API
+-export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2,
+ import/1, import/2, export/1]).
+
+-include("jlib.hrl").
+-include("mod_vcard.hrl").
+-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_vcard(LUser, LServer) ->
+ case catch sql_queries:get_vcard(LServer, LUser) of
+ {selected, [{SVCARD}]} ->
+ case fxml_stream:parse_element(SVCARD) of
+ {error, _Reason} -> error;
+ VCARD -> [VCARD]
+ end;
+ {selected, []} -> [];
+ _ -> error
+ end.
+
+set_vcard(LUser, LServer, VCARD,
+ #vcard_search{user = {User, _},
+ 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}) ->
+ SVCARD = fxml:element_to_binary(VCARD),
+ sql_queries:set_vcard(LServer, LUser, BDay, CTRY,
+ EMail, FN, Family, Given, LBDay,
+ LCTRY, LEMail, LFN, LFamily,
+ LGiven, LLocality, LMiddle,
+ LNickname, LOrgName, LOrgUnit,
+ Locality, Middle, Nickname, OrgName,
+ OrgUnit, SVCARD, User).
+
+search(LServer, Data, AllowReturnAll, MaxMatch) ->
+ MatchSpec = make_matchspec(LServer, Data),
+ if (MatchSpec == <<"">>) and not AllowReturnAll -> [];
+ true ->
+ Limit = case MaxMatch of
+ infinity ->
+ <<"">>;
+ Val ->
+ [<<" LIMIT ">>, jlib:integer_to_binary(Val)]
+ end,
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ [<<"select username, fn, family, given, "
+ "middle, nickname, bday, ctry, "
+ "locality, email, orgname, orgunit "
+ "from vcard_search ">>,
+ MatchSpec, Limit, <<";">>]) of
+ {selected,
+ [<<"username">>, <<"fn">>, <<"family">>, <<"given">>,
+ <<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>,
+ <<"locality">>, <<"email">>, <<"orgname">>,
+ <<"orgunit">>], Rs} when is_list(Rs) ->
+ Rs;
+ Error ->
+ ?ERROR_MSG("~p", [Error]), []
+ end
+ end.
+
+remove_user(LUser, LServer) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ fun() ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from vcard where username=%(LUser)s")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from vcard_search where lusername=%(LUser)s"))
+ end).
+
+export(_Server) ->
+ [{vcard,
+ fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ SVCARD =
+ ejabberd_sql:escape(fxml:element_to_binary(VCARD)),
+ [[<<"delete from vcard where username='">>, Username, <<"';">>],
+ [<<"insert into vcard(username, vcard) values ('">>,
+ Username, <<"', '">>, SVCARD, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end},
+ {vcard_search,
+ fun(Host, #vcard_search{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})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(User),
+ LUsername = ejabberd_sql:escape(LUser),
+ SFN = ejabberd_sql:escape(FN),
+ SLFN = ejabberd_sql:escape(LFN),
+ SFamily = ejabberd_sql:escape(Family),
+ SLFamily = ejabberd_sql:escape(LFamily),
+ SGiven = ejabberd_sql:escape(Given),
+ SLGiven = ejabberd_sql:escape(LGiven),
+ SMiddle = ejabberd_sql:escape(Middle),
+ SLMiddle = ejabberd_sql:escape(LMiddle),
+ SNickname = ejabberd_sql:escape(Nickname),
+ SLNickname = ejabberd_sql:escape(LNickname),
+ SBDay = ejabberd_sql:escape(BDay),
+ SLBDay = ejabberd_sql:escape(LBDay),
+ SCTRY = ejabberd_sql:escape(CTRY),
+ SLCTRY = ejabberd_sql:escape(LCTRY),
+ SLocality = ejabberd_sql:escape(Locality),
+ SLLocality = ejabberd_sql:escape(LLocality),
+ SEMail = ejabberd_sql:escape(EMail),
+ SLEMail = ejabberd_sql:escape(LEMail),
+ SOrgName = ejabberd_sql:escape(OrgName),
+ SLOrgName = ejabberd_sql:escape(LOrgName),
+ SOrgUnit = ejabberd_sql:escape(OrgUnit),
+ SLOrgUnit = ejabberd_sql:escape(LOrgUnit),
+ [[<<"delete from vcard_search where lusername='">>,
+ LUsername, <<"';">>],
+ [<<"insert into vcard_search( username, "
+ "lusername, fn, lfn, family, lfamily, "
+ " given, lgiven, middle, lmiddle, "
+ "nickname, lnickname, bday, lbday, "
+ "ctry, lctry, locality, llocality, "
+ " email, lemail, orgname, lorgname, "
+ "orgunit, lorgunit)values (">>,
+ <<" '">>, Username, <<"', '">>, LUsername,
+ <<"', '">>, SFN, <<"', '">>, SLFN,
+ <<"', '">>, SFamily, <<"', '">>, SLFamily,
+ <<"', '">>, SGiven, <<"', '">>, SLGiven,
+ <<"', '">>, SMiddle, <<"', '">>, SLMiddle,
+ <<"', '">>, SNickname, <<"', '">>, SLNickname,
+ <<"', '">>, SBDay, <<"', '">>, SLBDay,
+ <<"', '">>, SCTRY, <<"', '">>, SLCTRY,
+ <<"', '">>, SLocality, <<"', '">>, SLLocality,
+ <<"', '">>, SEMail, <<"', '">>, SLEMail,
+ <<"', '">>, SOrgName, <<"', '">>, SLOrgName,
+ <<"', '">>, SOrgUnit, <<"', '">>, SLOrgUnit,
+ <<"');">>]];
+ (_Host, _R) ->
+ []
+ 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.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+make_matchspec(LServer, Data) ->
+ filter_fields(Data, <<"">>, LServer).
+
+filter_fields([], Match, _LServer) ->
+ case Match of
+ <<"">> -> <<"">>;
+ _ -> [<<" where ">>, Match]
+ end;
+filter_fields([{SVar, [Val]} | Ds], Match, LServer)
+ when is_binary(Val) and (Val /= <<"">>) ->
+ LVal = mod_vcard:string2lower(Val),
+ NewMatch = case SVar of
+ <<"user">> -> make_val(Match, <<"lusername">>, LVal);
+ <<"fn">> -> make_val(Match, <<"lfn">>, LVal);
+ <<"last">> -> make_val(Match, <<"lfamily">>, LVal);
+ <<"first">> -> make_val(Match, <<"lgiven">>, LVal);
+ <<"middle">> -> make_val(Match, <<"lmiddle">>, LVal);
+ <<"nick">> -> make_val(Match, <<"lnickname">>, LVal);
+ <<"bday">> -> make_val(Match, <<"lbday">>, LVal);
+ <<"ctry">> -> make_val(Match, <<"lctry">>, LVal);
+ <<"locality">> ->
+ make_val(Match, <<"llocality">>, LVal);
+ <<"email">> -> make_val(Match, <<"lemail">>, LVal);
+ <<"orgname">> -> make_val(Match, <<"lorgname">>, LVal);
+ <<"orgunit">> -> make_val(Match, <<"lorgunit">>, LVal);
+ _ -> Match
+ end,
+ filter_fields(Ds, NewMatch, LServer);
+filter_fields([_ | Ds], Match, LServer) ->
+ filter_fields(Ds, Match, LServer).
+
+make_val(Match, Field, Val) ->
+ Condition = case str:suffix(<<"*">>, Val) of
+ true ->
+ Val1 = str:substr(Val, 1, byte_size(Val) - 1),
+ SVal = <<(ejabberd_sql:escape_like(Val1))/binary,
+ "%">>,
+ [Field, <<" LIKE '">>, SVal, <<"'">>];
+ _ ->
+ SVal = ejabberd_sql:escape(Val),
+ [Field, <<" = '">>, SVal, <<"'">>]
+ end,
+ case Match of
+ <<"">> -> Condition;
+ _ -> [Match, <<" and ">>, Condition]
+ end.
diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl
index 96ee09d87..198312c36 100644
--- a/src/mod_vcard_xupdate.erl
+++ b/src/mod_vcard_xupdate.erl
@@ -17,26 +17,22 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-
+-include("mod_vcard_xupdate.hrl").
-include("jlib.hrl").
--record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()},
- hash = <<>> :: binary()}).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #vcard_xupdate{}) -> ok | pass.
+-callback add_xupdate(binary(), binary(), binary()) -> {atomic, any()}.
+-callback get_xupdate(binary(), binary()) -> binary() | undefined.
+-callback remove_xupdate(binary(), binary()) -> {atomic, any()}.
%%====================================================================
%% gen_mod callbacks
%%====================================================================
start(Host, Opts) ->
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(vcard_xupdate,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, vcard_xupdate)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(c2s_update_presence, Host, ?MODULE,
update_presence, 100),
ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set,
@@ -56,7 +52,7 @@ stop(Host) ->
update_presence(#xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
User, Host) ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<>> -> presence_with_xupdate(Packet, User, Host);
_ -> Packet
end;
@@ -64,7 +60,7 @@ update_presence(Packet, _User, _Host) -> Packet.
vcard_set(LUser, LServer, VCARD) ->
US = {LUser, LServer},
- case xml:get_path_s(VCARD,
+ case fxml:get_path_s(VCARD,
[{elem, <<"PHOTO">>}, {elem, <<"BINVAL">>}, cdata])
of
<<>> -> remove_xupdate(LUser, LServer);
@@ -79,75 +75,16 @@ vcard_set(LUser, LServer, VCARD) ->
%%====================================================================
add_xupdate(LUser, LServer, Hash) ->
- add_xupdate(LUser, LServer, Hash,
- gen_mod:db_type(LServer, ?MODULE)).
-
-add_xupdate(LUser, LServer, Hash, mnesia) ->
- F = fun () ->
- mnesia:write(#vcard_xupdate{us = {LUser, LServer},
- hash = Hash})
- end,
- mnesia:transaction(F);
-add_xupdate(LUser, LServer, Hash, riak) ->
- {atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer},
- hash = Hash},
- vcard_xupdate_schema())};
-add_xupdate(LUser, LServer, Hash, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SHash = ejabberd_odbc:escape(Hash),
- F = fun () ->
- odbc_queries:update_t(<<"vcard_xupdate">>,
- [<<"username">>, <<"hash">>],
- [Username, SHash],
- [<<"username='">>, Username, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:add_xupdate(LUser, LServer, Hash).
get_xupdate(LUser, LServer) ->
- get_xupdate(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_xupdate(LUser, LServer, mnesia) ->
- case mnesia:dirty_read(vcard_xupdate, {LUser, LServer})
- of
- [#vcard_xupdate{hash = Hash}] -> Hash;
- _ -> undefined
- end;
-get_xupdate(LUser, LServer, riak) ->
- case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(),
- {LUser, LServer}) of
- {ok, #vcard_xupdate{hash = Hash}} -> Hash;
- _ -> undefined
- end;
-get_xupdate(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case ejabberd_odbc:sql_query(LServer,
- [<<"select hash from vcard_xupdate where "
- "username='">>,
- Username, <<"';">>])
- of
- {selected, [<<"hash">>], [[Hash]]} -> Hash;
- _ -> undefined
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_xupdate(LUser, LServer).
remove_xupdate(LUser, LServer) ->
- remove_xupdate(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_xupdate(LUser, LServer, mnesia) ->
- F = fun () ->
- mnesia:delete({vcard_xupdate, {LUser, LServer}})
- end,
- mnesia:transaction(F);
-remove_xupdate(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})};
-remove_xupdate(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- F = fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from vcard_xupdate where username='">>,
- Username, <<"';">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_xupdate(LUser, LServer).
%%%----------------------------------------------------------------------
%%% Presence stanza rebuilding
@@ -184,53 +121,17 @@ build_xphotoel(User, Host) ->
attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}],
children = PhotoEl}.
-vcard_xupdate_schema() ->
- {record_info(fields, vcard_xupdate), #vcard_xupdate{}}.
-
-update_table() ->
- Fields = record_info(fields, vcard_xupdate),
- case mnesia:table_info(vcard_xupdate, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- vcard_xupdate, Fields, set,
- fun(#vcard_xupdate{us = {U, _}}) -> U end,
- fun(#vcard_xupdate{us = {U, S}, hash = Hash} = R) ->
- R#vcard_xupdate{us = {iolist_to_binary(U),
- iolist_to_binary(S)},
- hash = iolist_to_binary(Hash)}
- end);
- _ ->
- ?INFO_MSG("Recreating vcard_xupdate table", []),
- mnesia:transform_table(vcard_xupdate, ignore, Fields)
- end.
-
-export(_Server) ->
- [{vcard_xupdate,
- fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SHash = ejabberd_odbc:escape(Hash),
- [[<<"delete from vcard_xupdate where username='">>,
- Username, <<"';">>],
- [<<"insert into vcard_xupdate(username, "
- "hash) values ('">>,
- Username, <<"', '">>, SHash, <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username, hash from vcard_xupdate;">>,
- fun([LUser, Hash]) ->
- #vcard_xupdate{us = {LUser, LServer}, hash = Hash}
- end}].
-
-import(_LServer, mnesia, #vcard_xupdate{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, riak, #vcard_xupdate{} = R) ->
- ejabberd_riak:put(R, vcard_xupdate_schema());
-import(_, _, _) ->
- pass.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+import(LServer, DBType, LA) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, LA).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(_) -> [db_type].
diff --git a/src/mod_vcard_xupdate_mnesia.erl b/src/mod_vcard_xupdate_mnesia.erl
new file mode 100644
index 000000000..f1b1693e4
--- /dev/null
+++ b/src/mod_vcard_xupdate_mnesia.erl
@@ -0,0 +1,69 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_xupdate_mnesia).
+-behaviour(mod_vcard_xupdate).
+
+%% API
+-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
+
+-include("mod_vcard_xupdate.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(vcard_xupdate,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, vcard_xupdate)}]),
+ update_table().
+
+add_xupdate(LUser, LServer, Hash) ->
+ F = fun () ->
+ mnesia:write(#vcard_xupdate{us = {LUser, LServer},
+ hash = Hash})
+ end,
+ mnesia:transaction(F).
+
+get_xupdate(LUser, LServer) ->
+ case mnesia:dirty_read(vcard_xupdate, {LUser, LServer})
+ of
+ [#vcard_xupdate{hash = Hash}] -> Hash;
+ _ -> undefined
+ end.
+
+remove_xupdate(LUser, LServer) ->
+ F = fun () ->
+ mnesia:delete({vcard_xupdate, {LUser, LServer}})
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #vcard_xupdate{} = R) ->
+ mnesia:dirty_write(R).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, vcard_xupdate),
+ case mnesia:table_info(vcard_xupdate, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ vcard_xupdate, Fields, set,
+ fun(#vcard_xupdate{us = {U, _}}) -> U end,
+ fun(#vcard_xupdate{us = {U, S}, hash = Hash} = R) ->
+ R#vcard_xupdate{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ hash = iolist_to_binary(Hash)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating vcard_xupdate table", []),
+ mnesia:transform_table(vcard_xupdate, ignore, Fields)
+ end.
diff --git a/src/mod_vcard_xupdate_riak.erl b/src/mod_vcard_xupdate_riak.erl
new file mode 100644
index 000000000..129a0c6a2
--- /dev/null
+++ b/src/mod_vcard_xupdate_riak.erl
@@ -0,0 +1,44 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_xupdate_riak).
+
+%% API
+-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
+
+-include("mod_vcard_xupdate.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+add_xupdate(LUser, LServer, Hash) ->
+ {atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer},
+ hash = Hash},
+ vcard_xupdate_schema())}.
+
+get_xupdate(LUser, LServer) ->
+ case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(),
+ {LUser, LServer}) of
+ {ok, #vcard_xupdate{hash = Hash}} -> Hash;
+ _ -> undefined
+ end.
+
+remove_xupdate(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})}.
+
+import(_LServer, #vcard_xupdate{} = R) ->
+ ejabberd_riak:put(R, vcard_xupdate_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+vcard_xupdate_schema() ->
+ {record_info(fields, vcard_xupdate), #vcard_xupdate{}}.
diff --git a/src/mod_vcard_xupdate_sql.erl b/src/mod_vcard_xupdate_sql.erl
new file mode 100644
index 000000000..7f0079dd0
--- /dev/null
+++ b/src/mod_vcard_xupdate_sql.erl
@@ -0,0 +1,79 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_xupdate_sql).
+
+%% API
+-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2,
+ import/1, export/1]).
+
+-include("mod_vcard_xupdate.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+add_xupdate(LUser, LServer, Hash) ->
+ Username = ejabberd_sql:escape(LUser),
+ SHash = ejabberd_sql:escape(Hash),
+ F = fun () ->
+ sql_queries:update_t(<<"vcard_xupdate">>,
+ [<<"username">>, <<"hash">>],
+ [Username, SHash],
+ [<<"username='">>, Username, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+get_xupdate(LUser, LServer) ->
+ Username = ejabberd_sql:escape(LUser),
+ case ejabberd_sql:sql_query(LServer,
+ [<<"select hash from vcard_xupdate where "
+ "username='">>,
+ Username, <<"';">>])
+ of
+ {selected, [<<"hash">>], [[Hash]]} -> Hash;
+ _ -> undefined
+ end.
+
+remove_xupdate(LUser, LServer) ->
+ Username = ejabberd_sql:escape(LUser),
+ F = fun () ->
+ ejabberd_sql:sql_query_t([<<"delete from vcard_xupdate where username='">>,
+ Username, <<"';">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+export(_Server) ->
+ [{vcard_xupdate,
+ fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ SHash = ejabberd_sql:escape(Hash),
+ [[<<"delete from vcard_xupdate where username='">>,
+ Username, <<"';">>],
+ [<<"insert into vcard_xupdate(username, "
+ "hash) values ('">>,
+ Username, <<"', '">>, SHash, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
+
+import(LServer) ->
+ [{<<"select username, hash from vcard_xupdate;">>,
+ fun([LUser, Hash]) ->
+ #vcard_xupdate{us = {LUser, LServer}, hash = Hash}
+ end}].
+
+import(_LServer, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_version.erl b/src/mod_version.erl
index 0e3b96bd1..7f7759f2d 100644
--- a/src/mod_version.erl
+++ b/src/mod_version.erl
@@ -52,11 +52,12 @@ stop(Host) ->
process_local_iq(_From, To,
#iq{id = _ID, type = Type, xmlns = _XMLNS,
- sub_el = SubEl} =
+ sub_el = SubEl, lang = Lang} =
IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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,
diff --git a/src/node_buddy.erl b/src/node_buddy.erl
index 1ebef4e15..aeacacda1 100644
--- a/src/node_buddy.erl
+++ b/src/node_buddy.erl
@@ -65,7 +65,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_club.erl b/src/node_club.erl
index a7ef35bd3..64eff7791 100644
--- a/src/node_club.erl
+++ b/src/node_club.erl
@@ -65,7 +65,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_dag.erl b/src/node_dag.erl
index cbb8e18c6..09ee85f91 100644
--- a/src/node_dag.erl
+++ b/src/node_dag.erl
@@ -77,8 +77,9 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
#pubsub_node{options = Options} ->
case find_opt(node_type, Options) of
collection ->
+ Txt = <<"Publishing items to collection node is not allowed">>,
{error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"publish">>)};
+ ?ERR_EXTENDED(?ERRT_NOT_ALLOWED(?MYLANG, Txt), <<"publish">>)};
_ ->
node_hometree:publish_item(Nidx, Publisher, Model,
MaxItems, ItemId, Payload)
diff --git a/src/node_dispatch.erl b/src/node_dispatch.erl
index f9bfaf4f2..178292d31 100644
--- a/src/node_dispatch.erl
+++ b/src/node_dispatch.erl
@@ -71,7 +71,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_flat.erl b/src/node_flat.erl
index 3687bb640..c2efe3e12 100644
--- a/src/node_flat.erl
+++ b/src/node_flat.erl
@@ -84,7 +84,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_flat_odbc.erl b/src/node_flat_sql.erl
index 794d3f986..5baabcfcf 100644
--- a/src/node_flat_odbc.erl
+++ b/src/node_flat_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : node_flat_odbc.erl
+%%% File : node_flat_sql.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Standard PubSub node plugin with ODBC backend
%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
@@ -29,7 +29,7 @@
%%% types.</p>
%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
--module(node_flat_odbc).
+-module(node_flat_sql).
-behaviour(gen_pubsub_node).
-author('christophe.romain@process-one.net').
@@ -56,14 +56,14 @@
encode_host/1]).
init(_Host, _ServerHost, _Opts) ->
- %%pubsub_subscription_odbc:init(),
+ %%pubsub_subscription_sql:init(),
ok.
terminate(_Host, _ServerHost) ->
ok.
options() ->
- [{odbc, true}, {rsm, true} | node_flat:options()].
+ [{sql, true}, {rsm, true} | node_flat:options()].
features() ->
[<<"rsm">> | node_flat:features()].
@@ -74,14 +74,14 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
create_node(Nidx, Owner) ->
{_U, _S, _R} = OwnerKey = jid:tolower(jid:remove_resource(Owner)),
State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner},
- catch ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ catch ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values(">>, state_to_raw(Nidx, State), <<");">>]),
{result, {default, broadcast}}.
delete_node(Nodes) ->
Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
Subscriptions = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, subscriptions "
+ ejabberd_sql:sql_query_t([<<"select jid, subscriptions "
"from pubsub_state where nodeid='">>, Nidx, <<"';">>])
of
{selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
@@ -130,12 +130,12 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
%% % Requesting entity is anonymous
%% {error, ?ERR_FORBIDDEN};
true ->
- %%{result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, Nidx, Options),
+ %%{result, SubId} = pubsub_subscription_sql:subscribe_node(Subscriber, Nidx, Options),
{NewSub, SubId} = case Subscriptions of
[{subscribed, Id}|_] ->
{subscribed, Id};
[] ->
- Id = pubsub_subscription_odbc:make_subid(),
+ Id = pubsub_subscription_sql:make_subid(),
Sub = case AccessModel of
authorize -> pending;
_ -> subscribed
@@ -209,7 +209,7 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) ->
NewSubs = Subscriptions -- [{Subscription, SubId}],
- %%pubsub_subscription_odbc:unsubscribe_node(SubKey, Nidx, SubId),
+ %%pubsub_subscription_sql:unsubscribe_node(SubKey, Nidx, SubId),
case {Affiliation, NewSubs} of
{none, []} -> del_state(Nidx, SubKey);
_ -> update_subscription(Nidx, SubKey, NewSubs)
@@ -296,12 +296,12 @@ get_entity_affiliations(Host, Owner) ->
H = encode_host(Host),
J = encode_jid(GenKey),
Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation "
+ ejabberd_sql:sql_query_t([<<"select node, type, i.nodeid, affiliation "
"from pubsub_state i, pubsub_node n where "
"i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>])
of
{selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} ->
- [{nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)}
+ [{nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)}
|| [N, T, I, A] <- RItems];
_ ->
[]
@@ -310,7 +310,7 @@ get_entity_affiliations(Host, Owner) ->
get_node_affiliations(Nidx) ->
Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state "
+ ejabberd_sql:sql_query_t([<<"select jid, affiliation from pubsub_state "
"where nodeid='">>, Nidx, <<"';">>])
of
{selected, [<<"jid">>, <<"affiliation">>], RItems} ->
@@ -325,7 +325,7 @@ get_affiliation(Nidx, Owner) ->
GenKey = jid:remove_resource(SubKey),
J = encode_jid(GenKey),
Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state "
+ ejabberd_sql:sql_query_t([<<"select affiliation from pubsub_state "
"where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
of
{selected, [<<"affiliation">>], [[A]]} ->
@@ -360,11 +360,11 @@ get_entity_subscriptions(Host, Owner) ->
"from pubsub_state i, pubsub_node n "
"where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
lists:foldl(fun ([N, T, I, J, S], Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
+ Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]),
Jid = decode_jid(J),
case decode_subscriptions(S) of
[] ->
@@ -411,11 +411,11 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
"and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
lists:foldl(fun ([N, T, I, J, S], Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
+ Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]),
Jid = decode_jid(J),
case decode_subscriptions(S) of
[] ->
@@ -435,7 +435,7 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
get_node_subscriptions(Nidx) ->
Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state "
+ ejabberd_sql:sql_query_t([<<"select jid, subscriptions from pubsub_state "
"where nodeid='">>, Nidx, <<"';">>])
of
{selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
@@ -461,7 +461,7 @@ get_subscriptions(Nidx, Owner) ->
SubKey = jid:tolower(Owner),
J = encode_jid(SubKey),
Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state where "
+ ejabberd_sql:sql_query_t([<<"select subscriptions from pubsub_state where "
"nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
of
{selected, [<<"subscriptions">>], [[S]]} ->
@@ -507,14 +507,14 @@ replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
new_subscription(_Nidx, _Owner, Subscription, SubState) ->
- %%{result, SubId} = pubsub_subscription_odbc:subscribe_node(Owner, Nidx, []),
- SubId = pubsub_subscription_odbc:make_subid(),
+ %%{result, SubId} = pubsub_subscription_sql:subscribe_node(Owner, Nidx, []),
+ SubId = pubsub_subscription_sql:make_subid(),
Subscriptions = [{Subscription, SubId} | SubState#pubsub_state.subscriptions],
set_state(SubState#pubsub_state{subscriptions = Subscriptions}),
{Subscription, SubId}.
unsub_with_subid(Nidx, SubId, SubState) ->
- %%pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId),
+ %%pubsub_subscription_sql:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId),
NewSubs = [{S, Sid}
|| {S, Sid} <- SubState#pubsub_state.subscriptions,
SubId =/= Sid],
@@ -561,7 +561,7 @@ get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}
get_states(Nidx) ->
case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions "
"from pubsub_state where nodeid='">>, Nidx, <<"';">>])
of
{selected,
@@ -591,7 +591,7 @@ get_state(Nidx, JID) ->
get_state_without_itemids(Nidx, JID) ->
J = encode_jid(JID),
case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions "
"from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>])
of
{selected,
@@ -613,14 +613,14 @@ set_state(Nidx, State) ->
S = encode_subscriptions(State#pubsub_state.subscriptions),
A = encode_affiliation(State#pubsub_state.affiliation),
case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A,
+ ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A,
<<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
of
{updated, 1} ->
ok;
_ ->
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values('">>,
Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>])
end,
@@ -628,13 +628,13 @@ set_state(Nidx, State) ->
del_state(Nidx, JID) ->
J = encode_jid(JID),
- catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>,
+ catch ejabberd_sql:sql_query_t([<<"delete from pubsub_state where jid='">>,
J, <<"' and nodeid='">>, Nidx, <<"';">>]),
ok.
%get_items(Nidx, _From) ->
% case catch
-% ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
+% ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
% "from pubsub_item where nodeid='">>, Nidx,
% <<"' order by modification desc;">>])
% of
@@ -647,7 +647,7 @@ del_state(Nidx, JID) ->
get_items(Nidx, From, none) ->
MaxItems = case catch
- ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option "
+ ejabberd_sql:sql_query_t([<<"select val from pubsub_node_option "
"where nodeid='">>, Nidx, <<"' and name='max_items';">>])
of
{selected, [<<"val">>], [[Value]]} ->
@@ -659,7 +659,7 @@ get_items(Nidx, From, none) ->
get_items(Nidx, From, #rsm_in{max = MaxItems});
get_items(Nidx, _From,
#rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) ->
- Max = ejabberd_odbc:escape(jlib:i2l(M)),
+ Max = ejabberd_sql:escape(jlib:i2l(M)),
{Way, Order} = case Direction of
% aft -> {<<"<">>, <<"desc">>};
% before when I == <<>> -> {<<"is not">>, <<"asc">>};
@@ -672,11 +672,11 @@ get_items(Nidx, _From,
[AttrName, Id] = case I of
undefined when IncIndex =/= undefined ->
case catch
- ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item pi "
+ ejabberd_sql:sql_query_t([<<"select modification from pubsub_item pi "
"where exists ( select count(*) as count1 "
"from pubsub_item where nodeid='">>, Nidx,
<<"' and modification > pi.modification having count1 = ">>,
- ejabberd_odbc:escape(jlib:i2l(IncIndex)), <<" );">>])
+ ejabberd_sql:escape(jlib:i2l(IncIndex)), <<" );">>])
of
{selected, [_], [[O]]} ->
[<<"modification">>, <<"'", O/binary, "'">>];
@@ -688,27 +688,36 @@ get_items(Nidx, _From,
<<>> ->
[<<"modification">>, <<"null">>];
I ->
- [A, B] = str:tokens(ejabberd_odbc:escape(jlib:i2l(I)), <<"@">>),
+ [A, B] = str:tokens(ejabberd_sql:escape(jlib:i2l(I)), <<"@">>),
[A, <<"'", B/binary, "'">>]
end,
Count = case catch
- ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
+ ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
of
{selected, [_], [[C]]} -> C;
_ -> <<"0">>
end,
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
- AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
- of
+ Query = fun(mssql, _) ->
+ ejabberd_sql:sql_query_t(
+ [<<"select top ">>, jlib:i2l(Max),
+ <<" itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
+ AttrName, <<" ">>, Order, <<";">>]);
+ (_, _) ->
+ ejabberd_sql:sql_query_t(
+ [<<"select itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
+ AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
+ end,
+ case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
case RItems of
[[_, _, _, F, _]|_] ->
Index = case catch
- ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item "
+ ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item "
"where nodeid='">>, Nidx, <<"' and ">>,
AttrName, <<" > '">>, F, <<"';">>])
of
@@ -760,11 +769,20 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM
end.
get_last_items(Nidx, _From, Count) ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' order by modification desc limit ">>, jlib:i2l(Count), <<";">>])
- of
+ Limit = jlib:i2l(Count),
+ Query = fun(mssql, _) ->
+ ejabberd_sql:sql_query_t(
+ [<<"select top ">>, Limit,
+ <<" itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' order by modification desc ;">>]);
+ (_, _) ->
+ ejabberd_sql:sql_query_t(
+ [<<"select itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' order by modification desc limit ">>, Limit, <<";">>])
+ end,
+ case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
{result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
@@ -773,17 +791,19 @@ get_last_items(Nidx, _From, Count) ->
end.
get_item(Nidx, ItemId) ->
- I = ejabberd_odbc:escape(ItemId),
+ I = ejabberd_sql:escape(ItemId),
case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, "
"modification, payload from pubsub_item "
"where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
of
{selected,
[<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} ->
{result, raw_to_item(Nidx, RItem)};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {selected, _, []} ->
+ {error, ?ERR_ITEM_NOT_FOUND};
+ {'EXIT', _} ->
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
end.
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
@@ -819,17 +839,17 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub
set_item(Item) ->
{ItemId, Nidx} = Item#pubsub_item.itemid,
- I = ejabberd_odbc:escape(ItemId),
+ I = ejabberd_sql:escape(ItemId),
{C, _} = Item#pubsub_item.creation,
{M, JID} = Item#pubsub_item.modification,
P = encode_jid(JID),
Payload = Item#pubsub_item.payload,
- XML = ejabberd_odbc:escape(str:join([xml:element_to_binary(X) || X<-Payload], <<>>)),
+ XML = ejabberd_sql:escape(str:join([fxml:element_to_binary(X) || X<-Payload], <<>>)),
S = fun ({T1, T2, T3}) ->
str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>)
end,
case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>, P,
+ ejabberd_sql:sql_query_t([<<"update pubsub_item set publisher='">>, P,
<<"', modification='">>, S(M),
<<"', payload='">>, XML,
<<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
@@ -838,7 +858,7 @@ set_item(Item) ->
ok;
_ ->
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
"publisher, creation, modification, payload) "
"values('">>, Nidx, <<"', '">>, I, <<"', '">>, P,
<<"', '">>, S(C), <<"', '">>, S(M),
@@ -847,8 +867,8 @@ set_item(Item) ->
ok.
del_item(Nidx, ItemId) ->
- I = ejabberd_odbc:escape(ItemId),
- catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>,
+ I = ejabberd_sql:escape(ItemId),
+ catch ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid='">>,
I, <<"' and nodeid='">>, Nidx, <<"';">>]).
del_items(_, []) ->
@@ -856,9 +876,9 @@ del_items(_, []) ->
del_items(Nidx, [ItemId]) ->
del_item(Nidx, ItemId);
del_items(Nidx, ItemIds) ->
- I = str:join([[<<"'">>, ejabberd_odbc:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
+ I = str:join([[<<"'">>, ejabberd_sql:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
catch
- ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
+ ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
I, <<") and nodeid='">>, Nidx, <<"';">>]).
get_item_name(_Host, _Node, Id) ->
@@ -883,7 +903,7 @@ itemids(Nidx, {U, S, R}) ->
itemids(Nidx, encode_jid({U, S, R}));
itemids(Nidx, SJID) ->
case catch
- ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where "
+ ejabberd_sql:sql_query_t([<<"select itemid from pubsub_item where "
"nodeid='">>, Nidx, <<"' and publisher like '">>, SJID,
<<"%' order by modification desc;">>])
of
@@ -896,7 +916,7 @@ itemids(Nidx, SJID) ->
select_affiliation_subscriptions(Nidx, JID) ->
J = encode_jid(JID),
case catch
- ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from "
+ ejabberd_sql:sql_query_t([<<"select affiliation,subscriptions from "
"pubsub_state where nodeid='">>,
Nidx, <<"' and jid='">>, J, <<"';">>])
of
@@ -917,7 +937,7 @@ update_affiliation(Nidx, JID, Affiliation) ->
J = encode_jid(JID),
A = encode_affiliation(Affiliation),
case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>,
+ ejabberd_sql:sql_query_t([<<"update pubsub_state set affiliation='">>,
A, <<"' where nodeid='">>, Nidx,
<<"' and jid='">>, J, <<"';">>])
of
@@ -925,7 +945,7 @@ update_affiliation(Nidx, JID, Affiliation) ->
ok;
_ ->
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>])
end.
@@ -933,14 +953,14 @@ update_subscription(Nidx, JID, Subscription) ->
J = encode_jid(JID),
S = encode_subscriptions(Subscription),
case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
+ ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
<<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
of
{updated, 1} ->
ok;
_ ->
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>])
end.
@@ -989,14 +1009,14 @@ decode_subscriptions(Subscriptions) ->
-> binary()
).
encode_jid(JID) ->
- ejabberd_odbc:escape(jid:to_string(JID)).
+ ejabberd_sql:escape(jid:to_string(JID)).
-spec(encode_host/1 ::
( Host :: host())
-> binary()
).
encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID);
-encode_host(Host) -> ejabberd_odbc:escape(Host).
+encode_host(Host) -> ejabberd_sql:escape(Host).
-spec(encode_affiliation/1 ::
( Arg :: atom())
@@ -1041,7 +1061,7 @@ raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
[T1, T2, T3] = str:tokens(Str, <<":">>),
{jlib:l2i(T1), jlib:l2i(T2), jlib:l2i(T3)}
end,
- Payload = case xml_stream:parse_element(XML) of
+ Payload = case fxml_stream:parse_element(XML) of
{error, _Reason} -> [];
El -> [El]
end,
diff --git a/src/node_hometree_odbc.erl b/src/node_hometree_sql.erl
index 6ac5c37b8..4ce6f8554 100644
--- a/src/node_hometree_odbc.erl
+++ b/src/node_hometree_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : node_hometree_odbc.erl
+%%% File : node_hometree_sql.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Standard tree ordered node plugin with ODBC backend
%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(node_hometree_odbc).
+-module(node_hometree_sql).
-behaviour(gen_pubsub_node).
-author('christophe.romain@process-one.net').
@@ -45,17 +45,17 @@
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
init(Host, ServerHost, Opts) ->
- node_flat_odbc:init(Host, ServerHost, Opts),
+ node_flat_sql:init(Host, ServerHost, Opts),
Owner = mod_pubsub:service_jid(Host),
mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>),
mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>),
ok.
terminate(Host, ServerHost) ->
- node_flat_odbc:terminate(Host, ServerHost).
+ node_flat_sql:terminate(Host, ServerHost).
options() ->
- [{odbc, true}, {rsm, true} | node_hometree:options()].
+ [{sql, true}, {rsm, true} | node_hometree:options()].
features() ->
[<<"rsm">> | node_hometree:features()].
@@ -64,92 +64,92 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_flat_odbc:create_node(Nidx, Owner).
+ node_flat_sql:create_node(Nidx, Owner).
delete_node(Nodes) ->
- node_flat_odbc:delete_node(Nodes).
+ node_flat_sql:delete_node(Nodes).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_flat_odbc:purge_node(Nidx, Owner).
+ node_flat_sql:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- node_flat_odbc:get_entity_affiliations(Host, Owner).
+ node_flat_sql:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- node_flat_odbc:get_node_affiliations(Nidx).
+ node_flat_sql:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_flat_odbc:get_affiliation(Nidx, Owner).
+ node_flat_sql:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat_sql:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_flat_odbc:get_entity_subscriptions(Host, Owner).
+ node_flat_sql:get_entity_subscriptions(Host, Owner).
get_entity_subscriptions_for_send_last(Host, Owner) ->
- node_flat_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
+ node_flat_sql:get_entity_subscriptions_for_send_last(Host, Owner).
get_node_subscriptions(Nidx) ->
- node_flat_odbc:get_node_subscriptions(Nidx).
+ node_flat_sql:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_flat_odbc:get_subscriptions(Nidx, Owner).
+ node_flat_sql:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_flat_odbc:get_pending_nodes(Host, Owner).
+ node_flat_sql:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_flat_odbc:get_states(Nidx).
+ node_flat_sql:get_states(Nidx).
get_state(Nidx, JID) ->
- node_flat_odbc:get_state(Nidx, JID).
+ node_flat_sql:get_state(Nidx, JID).
set_state(State) ->
- node_flat_odbc:set_state(State).
+ node_flat_sql:set_state(State).
get_items(Nidx, From, RSM) ->
- node_flat_odbc:get_items(Nidx, From, RSM).
+ node_flat_sql:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_flat_odbc:get_items(Nidx, JID, AccessModel,
+ node_flat_sql:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_flat_odbc:get_item(Nidx, ItemId).
+ node_flat_sql:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_flat_odbc:get_item(Nidx, ItemId, JID,
+ node_flat_sql:get_item(Nidx, ItemId, JID,
AccessModel, PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_flat_odbc:set_item(Item).
+ node_flat_sql:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_flat_odbc:get_item_name(Host, Node, Id).
+ node_flat_sql:get_item_name(Host, Node, Id).
get_last_items(Nidx, From, Count) ->
- node_flat_odbc:get_last_items(Nidx, From, Count).
+ node_flat_sql:get_last_items(Nidx, From, Count).
node_to_path(Node) ->
node_hometree:node_to_path(Node).
diff --git a/src/node_mb.erl b/src/node_mb.erl
index 1213805c4..0bfb51ecc 100644
--- a/src/node_mb.erl
+++ b/src/node_mb.erl
@@ -78,7 +78,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, true}].
+ {presence_based_delivery, true},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_mix.erl b/src/node_mix.erl
new file mode 100644
index 000000000..b57d58493
--- /dev/null
+++ b/src/node_mix.erl
@@ -0,0 +1,168 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(node_mix).
+
+-behaviour(gen_pubsub_node).
+
+%% API
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/7, get_items/3, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
+
+-include("pubsub.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(Host, ServerHost, Opts) ->
+ node_flat:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+ node_flat:terminate(Host, ServerHost).
+
+options() ->
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, true},
+ {purge_offline, false},
+ {persist_items, true},
+ {max_items, ?MAXITEMS},
+ {subscribe, true},
+ {access_model, open},
+ {roster_groups_allowed, []},
+ {publish_model, open},
+ {notification_type, headline},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, never},
+ {deliver_notifications, true},
+ {broadcast_all_resources, true},
+ {presence_based_delivery, false},
+ {itemreply, none}].
+
+features() ->
+ [<<"create-nodes">>,
+ <<"delete-nodes">>,
+ <<"delete-items">>,
+ <<"instant-nodes">>,
+ <<"item-ids">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Nidx, Owner) ->
+ node_flat:create_node(Nidx, Owner).
+
+delete_node(Removed) ->
+ node_flat:delete_node(Removed).
+
+subscribe_node(Nidx, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options).
+
+unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Nidx, MaxItems, ItemIds) ->
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+
+delete_item(Nidx, Publisher, PublishModel, ItemId) ->
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
+
+purge_node(Nidx, Owner) ->
+ node_flat:purge_node(Nidx, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+ node_flat:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Nidx) ->
+ node_flat:get_node_affiliations(Nidx).
+
+get_affiliation(Nidx, Owner) ->
+ node_flat:get_affiliation(Nidx, Owner).
+
+set_affiliation(Nidx, Owner, Affiliation) ->
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+ node_flat:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Nidx) ->
+ node_flat:get_node_subscriptions(Nidx).
+
+get_subscriptions(Nidx, Owner) ->
+ node_flat:get_subscriptions(Nidx, Owner).
+
+set_subscriptions(Nidx, Owner, Subscription, SubId) ->
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
+
+get_pending_nodes(Host, Owner) ->
+ node_flat:get_pending_nodes(Host, Owner).
+
+get_states(Nidx) ->
+ node_flat:get_states(Nidx).
+
+get_state(Nidx, JID) ->
+ node_flat:get_state(Nidx, JID).
+
+set_state(State) ->
+ node_flat:set_state(State).
+
+get_items(Nidx, From, RSM) ->
+ node_flat:get_items(Nidx, From, RSM).
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
+
+get_item(Nidx, ItemId) ->
+ node_flat:get_item(Nidx, ItemId).
+
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
+
+set_item(Item) ->
+ node_flat:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_flat:get_item_name(Host, Node, Id).
+
+node_to_path(Node) ->
+ node_flat:node_to_path(Node).
+
+path_to_node(Path) ->
+ node_flat:path_to_node(Path).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/node_mix_sql.erl b/src/node_mix_sql.erl
new file mode 100644
index 000000000..b5b9a94eb
--- /dev/null
+++ b/src/node_mix_sql.erl
@@ -0,0 +1,171 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(node_mix_sql).
+
+-behaviour(gen_pubsub_node).
+
+%% API
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/7, get_items/3, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1, get_entity_subscriptions_for_send_last/2]).
+
+-include("pubsub.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(Host, ServerHost, Opts) ->
+ node_flat_sql:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+ node_flat_sql:terminate(Host, ServerHost).
+
+options() ->
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, true},
+ {purge_offline, false},
+ {persist_items, true},
+ {max_items, ?MAXITEMS},
+ {subscribe, true},
+ {access_model, open},
+ {roster_groups_allowed, []},
+ {publish_model, open},
+ {notification_type, headline},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, never},
+ {deliver_notifications, true},
+ {broadcast_all_resources, true},
+ {presence_based_delivery, false},
+ {itemreply, none}].
+
+features() ->
+ [<<"create-nodes">>,
+ <<"delete-nodes">>,
+ <<"delete-items">>,
+ <<"instant-nodes">>,
+ <<"item-ids">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+ node_flat_sql:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Nidx, Owner) ->
+ node_flat_sql:create_node(Nidx, Owner).
+
+delete_node(Removed) ->
+ node_flat_sql:delete_node(Removed).
+
+subscribe_node(Nidx, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options).
+
+unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
+ node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Nidx, MaxItems, ItemIds) ->
+ node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
+
+delete_item(Nidx, Publisher, PublishModel, ItemId) ->
+ node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId).
+
+purge_node(Nidx, Owner) ->
+ node_flat_sql:purge_node(Nidx, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+ node_flat_sql:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Nidx) ->
+ node_flat_sql:get_node_affiliations(Nidx).
+
+get_affiliation(Nidx, Owner) ->
+ node_flat_sql:get_affiliation(Nidx, Owner).
+
+set_affiliation(Nidx, Owner, Affiliation) ->
+ node_flat_sql:set_affiliation(Nidx, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+ node_flat_sql:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Nidx) ->
+ node_flat_sql:get_node_subscriptions(Nidx).
+
+get_subscriptions(Nidx, Owner) ->
+ node_flat_sql:get_subscriptions(Nidx, Owner).
+
+set_subscriptions(Nidx, Owner, Subscription, SubId) ->
+ node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId).
+
+get_pending_nodes(Host, Owner) ->
+ node_flat_sql:get_pending_nodes(Host, Owner).
+
+get_states(Nidx) ->
+ node_flat_sql:get_states(Nidx).
+
+get_state(Nidx, JID) ->
+ node_flat_sql:get_state(Nidx, JID).
+
+set_state(State) ->
+ node_flat_sql:set_state(State).
+
+get_items(Nidx, From, RSM) ->
+ node_flat_sql:get_items(Nidx, From, RSM).
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat_sql:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
+
+get_item(Nidx, ItemId) ->
+ node_flat_sql:get_item(Nidx, ItemId).
+
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat_sql:get_item(Nidx, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
+
+set_item(Item) ->
+ node_flat_sql:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_flat_sql:get_item_name(Host, Node, Id).
+
+node_to_path(Node) ->
+ node_flat_sql:node_to_path(Node).
+
+path_to_node(Path) ->
+ node_flat_sql:path_to_node(Path).
+
+get_entity_subscriptions_for_send_last(Host, Owner) ->
+ node_flat_sql:get_entity_subscriptions_for_send_last(Host, Owner).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/node_online.erl b/src/node_online.erl
index 1e9e29533..ea492fc85 100644
--- a/src/node_online.erl
+++ b/src/node_online.erl
@@ -76,7 +76,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, true}].
+ {presence_based_delivery, true},
+ {itemreply, none}].
features() ->
node_flat:features().
diff --git a/src/node_pep.erl b/src/node_pep.erl
index 726146b90..2ac4da627 100644
--- a/src/node_pep.erl
+++ b/src/node_pep.erl
@@ -72,7 +72,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, true}].
+ {presence_based_delivery, true},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
@@ -257,7 +258,7 @@ complain_if_modcaps_disabled(ServerHost) ->
false ->
?WARNING_MSG("The PEP plugin is enabled in mod_pubsub "
"of host ~p. This plugin requires mod_caps "
- "to be enabled, but it isn't.",
+ "but it does not seems enabled, please check config.",
[ServerHost]);
true -> ok
end.
diff --git a/src/node_pep_odbc.erl b/src/node_pep_sql.erl
index 6eb0de040..c5b31d15f 100644
--- a/src/node_pep_odbc.erl
+++ b/src/node_pep_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : node_pep_odbc.erl
+%%% File : node_pep_sql.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Standard PubSub PEP plugin with ODBC backend
%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
@@ -26,7 +26,7 @@
%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
--module(node_pep_odbc).
+-module(node_pep_sql).
-behaviour(gen_pubsub_node).
-author('christophe.romain@process-one.net').
@@ -49,16 +49,16 @@
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
init(Host, ServerHost, Opts) ->
- node_flat_odbc:init(Host, ServerHost, Opts),
+ node_flat_sql:init(Host, ServerHost, Opts),
complain_if_modcaps_disabled(ServerHost),
ok.
terminate(Host, ServerHost) ->
- node_flat_odbc:terminate(Host, ServerHost),
+ node_flat_sql:terminate(Host, ServerHost),
ok.
options() ->
- [{odbc, true}, {rsm, true} | node_pep:options()].
+ [{sql, true}, {rsm, true} | node_pep:options()].
features() ->
[<<"rsm">> | node_pep:features()].
@@ -67,56 +67,56 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_pep:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_flat_odbc:create_node(Nidx, Owner),
+ node_flat_sql:create_node(Nidx, Owner),
{result, {default, broadcast}}.
delete_node(Nodes) ->
- {result, {_, _, Result}} = node_flat_odbc:delete_node(Nodes),
+ {result, {_, _, Result}} = node_flat_sql:delete_node(Nodes),
{result, {[], Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- case node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
+ case node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_flat_odbc:purge_node(Nidx, Owner).
+ node_flat_sql:purge_node(Nidx, Owner).
get_entity_affiliations(_Host, Owner) ->
OwnerKey = jid:tolower(jid:remove_resource(Owner)),
- node_flat_odbc:get_entity_affiliations(OwnerKey, Owner).
+ node_flat_sql:get_entity_affiliations(OwnerKey, Owner).
get_node_affiliations(Nidx) ->
- node_flat_odbc:get_node_affiliations(Nidx).
+ node_flat_sql:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_flat_odbc:get_affiliation(Nidx, Owner).
+ node_flat_sql:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat_sql:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(_Host, Owner) ->
SubKey = jid:tolower(Owner),
GenKey = jid:remove_resource(SubKey),
- Host = node_flat_odbc:encode_host(element(2, SubKey)),
- SJ = node_flat_odbc:encode_jid(SubKey),
- GJ = node_flat_odbc:encode_jid(GenKey),
+ Host = node_flat_sql:encode_host(element(2, SubKey)),
+ SJ = node_flat_sql:encode_jid(SubKey),
+ GJ = node_flat_sql:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -129,16 +129,16 @@ get_entity_subscriptions(_Host, Owner) ->
"where i.nodeid = n.nodeid and jid "
"in ('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>]
end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>],
RItems} ->
lists:map(fun ([H, N, T, I, J, S]) ->
- O = node_flat_odbc:decode_jid(H),
- Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]),
+ O = node_flat_sql:decode_jid(H),
+ Node = nodetree_tree_sql:raw_to_node(O, [N, <<"">>, T, I]),
{Node,
- node_flat_odbc:decode_subscriptions(S),
- node_flat_odbc:decode_jid(J)}
+ node_flat_sql:decode_subscriptions(S),
+ node_flat_sql:decode_jid(J)}
end,
RItems);
_ ->
@@ -149,9 +149,9 @@ get_entity_subscriptions(_Host, Owner) ->
get_entity_subscriptions_for_send_last(_Host, Owner) ->
SubKey = jid:tolower(Owner),
GenKey = jid:remove_resource(SubKey),
- Host = node_flat_odbc:encode_host(element(2, SubKey)),
- SJ = node_flat_odbc:encode_jid(SubKey),
- GJ = node_flat_odbc:encode_jid(GenKey),
+ Host = node_flat_sql:encode_host(element(2, SubKey)),
+ SJ = node_flat_sql:encode_jid(SubKey),
+ GJ = node_flat_sql:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -168,16 +168,16 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
"val='on_sub_and_presence' and jid in ",
"('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>]
end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>],
RItems} ->
lists:map(fun ([H, N, T, I, J, S]) ->
- O = node_flat_odbc:decode_jid(H),
- Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]),
+ O = node_flat_sql:decode_jid(H),
+ Node = nodetree_tree_sql:raw_to_node(O, [N, <<"">>, T, I]),
{Node,
- node_flat_odbc:decode_subscriptions(S),
- node_flat_odbc:decode_jid(J)}
+ node_flat_sql:decode_subscriptions(S),
+ node_flat_sql:decode_jid(J)}
end,
RItems);
_ ->
@@ -186,54 +186,54 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
{result, Reply}.
get_node_subscriptions(Nidx) ->
- node_flat_odbc:get_node_subscriptions(Nidx).
+ node_flat_sql:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_flat_odbc:get_subscriptions(Nidx, Owner).
+ node_flat_sql:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_flat_odbc:get_pending_nodes(Host, Owner).
+ node_flat_sql:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_flat_odbc:get_states(Nidx).
+ node_flat_sql:get_states(Nidx).
get_state(Nidx, JID) ->
- node_flat_odbc:get_state(Nidx, JID).
+ node_flat_sql:get_state(Nidx, JID).
set_state(State) ->
- node_flat_odbc:set_state(State).
+ node_flat_sql:set_state(State).
get_items(Nidx, From, RSM) ->
- node_flat_odbc:get_items(Nidx, From, RSM).
+ node_flat_sql:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_flat_odbc:get_items(Nidx, JID, AccessModel,
+ node_flat_sql:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_last_items(Nidx, JID, Count) ->
- node_flat_odbc:get_last_items(Nidx, JID, Count).
+ node_flat_sql:get_last_items(Nidx, JID, Count).
get_item(Nidx, ItemId) ->
- node_flat_odbc:get_item(Nidx, ItemId).
+ node_flat_sql:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_flat_odbc:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat_sql:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_flat_odbc:set_item(Item).
+ node_flat_sql:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_flat_odbc:get_item_name(Host, Node, Id).
+ node_flat_sql:get_item_name(Host, Node, Id).
node_to_path(Node) ->
- node_flat_odbc:node_to_path(Node).
+ node_flat_sql:node_to_path(Node).
path_to_node(Path) ->
- node_flat_odbc:path_to_node(Path).
+ node_flat_sql:path_to_node(Path).
%%%
%%% Internal
diff --git a/src/node_private.erl b/src/node_private.erl
index de80d870f..79dc5ed81 100644
--- a/src/node_private.erl
+++ b/src/node_private.erl
@@ -65,7 +65,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, false},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_public.erl b/src/node_public.erl
index df4f1c080..cdb8abca3 100644
--- a/src/node_public.erl
+++ b/src/node_public.erl
@@ -65,7 +65,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/nodetree_dag.erl b/src/nodetree_dag.erl
index 8ac56b27d..a105db832 100644
--- a/src/nodetree_dag.erl
+++ b/src/nodetree_dag.erl
@@ -69,13 +69,13 @@ create_node(Key, Node, Type, Owner, Options, Parents) ->
Other -> Other
end;
_ ->
- {error, ?ERR_CONFLICT}
+ {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)}
end.
delete_node(Key, Node) ->
case find_node(Key, Node) of
false ->
- {error, ?ERR_ITEM_NOT_FOUND};
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
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, ?ERR_ITEM_NOT_FOUND};
+ false -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
Record -> Record
end.
@@ -115,7 +115,7 @@ get_nodes(Key) ->
get_parentnodes(Host, Node, _From) ->
case find_node(Host, Node) of
false ->
- {error, ?ERR_ITEM_NOT_FOUND};
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
#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, ?ERR_ITEM_NOT_FOUND};
+ false -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
_ -> get_subnodes_helper(Host, Node)
end.
@@ -238,7 +238,7 @@ validate_parentage(Key, Owners, [<<>> | T]) ->
validate_parentage(Key, Owners, [ParentID | T]) ->
case find_node(Key, ParentID) of
false ->
- {error, ?ERR_ITEM_NOT_FOUND};
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
#pubsub_node{owners = POwners, options = POptions} ->
NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions),
MutualOwners = [O || O <- Owners, PO <- POwners, O == PO],
diff --git a/src/nodetree_tree.erl b/src/nodetree_tree.erl
index e3f7e251e..69b50ff9f 100644
--- a/src/nodetree_tree.erl
+++ b/src/nodetree_tree.erl
@@ -74,15 +74,15 @@ get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
- case catch mnesia:read({pubsub_node, {Host, Node}}) of
+ case mnesia:read({pubsub_node, {Host, Node}}) of
[Record] when is_record(Record, pubsub_node) -> Record;
- _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ _ -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
end.
get_node(Nidx) ->
- case catch mnesia:index_read(pubsub_node, Nidx, #pubsub_node.id) of
+ case mnesia:index_read(pubsub_node, Nidx, #pubsub_node.id) of
[Record] when is_record(Record, pubsub_node) -> Record;
- _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ _ -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
end.
get_nodes(Host, _From) ->
@@ -147,7 +147,7 @@ get_subnodes_tree(Host, Node) ->
create_node(Host, Node, Type, Owner, Options, Parents) ->
BJID = jid:tolower(jid:remove_resource(Owner)),
- case catch mnesia:read({pubsub_node, {Host, Node}}) of
+ case mnesia:read({pubsub_node, {Host, Node}}) of
[] ->
ParentExists = case Host of
{_U, _S, _R} ->
@@ -160,7 +160,7 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
true;
[Parent | _] ->
case catch mnesia:read({pubsub_node, {Host, Parent}}) of
- [#pubsub_node{owners = [{[], Host, []}]}] ->
+ [#pubsub_node{owners = [{<<>>, Host, <<>>}]}] ->
true;
[#pubsub_node{owners = Owners}] ->
lists:member(BJID, Owners);
@@ -183,7 +183,7 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
{error, ?ERR_FORBIDDEN}
end;
_ ->
- {error, ?ERR_CONFLICT}
+ {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)}
end.
delete_node(Host, Node) ->
diff --git a/src/nodetree_tree_odbc.erl b/src/nodetree_tree_sql.erl
index ef1c20b6b..b56543395 100644
--- a/src/nodetree_tree_odbc.erl
+++ b/src/nodetree_tree_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : nodetree_tree_odbc.erl
+%%% File : nodetree_tree_sql.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Standard node tree plugin with ODBC backend
%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
@@ -33,7 +33,7 @@
%%% useable and useful as is. Please, send us comments, feedback and
%%% improvements.</p>
--module(nodetree_tree_odbc).
+-module(nodetree_tree_sql).
-behaviour(gen_pubsub_nodetree).
-author('christophe.romain@process-one.net').
@@ -55,7 +55,7 @@ terminate(_Host, _ServerHost) ->
ok.
options() ->
- [{odbc, true} | nodetree_tree:options()].
+ [{sql, true} | nodetree_tree:options()].
set_node(Record) when is_record(Record, pubsub_node) ->
{Host, Node} = Record#pubsub_node.nodeid,
@@ -64,16 +64,16 @@ set_node(Record) when is_record(Record, pubsub_node) ->
[First | _] -> First
end,
Type = Record#pubsub_node.type,
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
- P = ejabberd_odbc:escape(Parent),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
+ P = ejabberd_sql:escape(Parent),
Nidx = case nodeidx(Host, Node) of
{result, OldNidx} ->
catch
- ejabberd_odbc:sql_query_t([<<"delete from pubsub_node_option where "
+ ejabberd_sql:sql_query_t([<<"delete from pubsub_node_option where "
"nodeid='">>, OldNidx, <<"';">>]),
catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_node set host='">>,
+ ejabberd_sql:sql_query_t([<<"update pubsub_node set host='">>,
H, <<"' node='">>, N,
<<"' parent='">>, P,
<<"' type='">>, Type,
@@ -82,7 +82,7 @@ set_node(Record) when is_record(Record, pubsub_node) ->
OldNidx;
_ ->
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_node(host, node, "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_node(host, node, "
"parent, type) values('">>,
H, <<"', '">>, N, <<"', '">>, P,
<<"', '">>, Type, <<"');">>]),
@@ -93,15 +93,16 @@ set_node(Record) when is_record(Record, pubsub_node) ->
end,
case Nidx of
none ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ Txt = <<"Node index not found">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, Txt)};
_ ->
lists:foreach(fun ({Key, Value}) ->
SKey = iolist_to_binary(atom_to_list(Key)),
- SValue = ejabberd_odbc:escape(
+ SValue = ejabberd_sql:escape(
list_to_binary(
lists:flatten(io_lib:fwrite("~p", [Value])))),
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_node_option(nodeid, "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_node_option(nodeid, "
"name, val) values('">>,
Nidx, <<"', '">>,
SKey, <<"', '">>, SValue, <<"');">>])
@@ -114,10 +115,10 @@ get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
case catch
- ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>,
H, <<"' and node='">>, N, <<"';">>])
of
@@ -125,14 +126,14 @@ get_node(Host, Node) ->
[<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], [RItem]} ->
raw_to_node(Host, RItem);
{'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
_ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
end.
get_node(Nidx) ->
case catch
- ejabberd_odbc:sql_query_t([<<"select host, node, parent, type from "
+ ejabberd_sql:sql_query_t([<<"select host, node, parent, type from "
"pubsub_node where nodeid='">>,
Nidx, <<"';">>])
of
@@ -140,18 +141,18 @@ get_node(Nidx) ->
[<<"host">>, <<"node">>, <<"parent">>, <<"type">>], [[Host, Node, Parent, Type]]} ->
raw_to_node(Host, [Node, Parent, Type, Nidx]);
{'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
_ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
end.
get_nodes(Host, _From) ->
get_nodes(Host).
get_nodes(Host) ->
- H = node_flat_odbc:encode_host(Host),
+ H = node_flat_sql:encode_host(Host),
case catch
- ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>, H, <<"';">>])
of
{selected,
@@ -176,10 +177,10 @@ get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, Node) ->
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
case catch
- ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>,
H, <<"' and parent='">>, N, <<"';">>])
of
@@ -194,10 +195,10 @@ get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
get_subnodes_tree(Host, Node) ->
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
case catch
- ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>,
H, <<"' and node like '">>, N, <<"%';">>])
of
@@ -211,7 +212,7 @@ get_subnodes_tree(Host, Node) ->
create_node(Host, Node, Type, Owner, Options, Parents) ->
BJID = jid:tolower(jid:remove_resource(Owner)),
case nodeidx(Host, Node) of
- {error, ?ERR_ITEM_NOT_FOUND} ->
+ {error, not_found} ->
ParentExists = case Host of
{_U, _S, _R} ->
%% This is special case for PEP handling
@@ -248,23 +249,23 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
{error, ?ERR_FORBIDDEN}
end;
{result, _} ->
- {error, ?ERR_CONFLICT};
- Error ->
- Error
+ {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)};
+ {error, db_fail} ->
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
end.
delete_node(Host, Node) ->
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
Removed = get_subnodes_tree(Host, Node),
- catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_node where host='">>,
+ catch ejabberd_sql:sql_query_t([<<"delete from pubsub_node where host='">>,
H, <<"' and node like '">>, N, <<"%';">>]),
Removed.
%% helpers
raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
Options = case catch
- ejabberd_odbc:sql_query_t([<<"select name,val from pubsub_node_option "
+ ejabberd_sql:sql_query_t([<<"select name,val from pubsub_node_option "
"where nodeid='">>, Nidx, <<"';">>])
of
{selected, [<<"name">>, <<"val">>], ROptions} ->
@@ -275,7 +276,7 @@ raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
{RKey, RValue}
end,
ROptions),
- Module = jlib:binary_to_atom(<<"node_", Type/binary, "_odbc">>),
+ Module = jlib:binary_to_atom(<<"node_", Type/binary, "_sql">>),
StdOpts = Module:options(),
lists:foldl(fun ({Key, Value}, Acc) ->
lists:keyreplace(Key, 1, Acc, {Key, Value})
@@ -293,21 +294,21 @@ raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
id = Nidx, type = Type, options = Options}.
nodeidx(Host, Node) ->
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
case catch
- ejabberd_odbc:sql_query_t([<<"select nodeid from pubsub_node where "
+ ejabberd_sql:sql_query_t([<<"select nodeid from pubsub_node where "
"host='">>,
H, <<"' and node='">>, N, <<"';">>])
of
{selected, [<<"nodeid">>], [[Nidx]]} ->
{result, Nidx};
{'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, db_fail};
_ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {error, not_found}
end.
nodeowners(Nidx) ->
- {result, Res} = node_flat_odbc:get_node_affiliations(Nidx),
+ {result, Res} = node_flat_sql:get_node_affiliations(Nidx),
[LJID || {LJID, Aff} <- Res, Aff =:= owner].
diff --git a/src/odbc_queries.erl b/src/odbc_queries.erl
deleted file mode 100644
index 2f488a0bd..000000000
--- a/src/odbc_queries.erl
+++ /dev/null
@@ -1,659 +0,0 @@
-%%%----------------------------------------------------------------------
-%%% File : odbc_queries.erl
-%%% Author : Mickael Remond <mremond@process-one.net>
-%%% Purpose : ODBC queries dependind on back-end
-%%% Created : by Mickael Remond <mremond@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
-%%%
-%%% This program is free software; you can redistribute it and/or
-%%% modify it under the terms of the GNU General Public License as
-%%% published by the Free Software Foundation; either version 2 of the
-%%% License, or (at your option) any later version.
-%%%
-%%% This program is distributed in the hope that it will be useful,
-%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
-%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%%% General Public License for more details.
-%%%
-%%% You should have received a copy of the GNU General Public License along
-%%% with this program; if not, write to the Free Software Foundation, Inc.,
-%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-%%%
-%%%----------------------------------------------------------------------
-
--module(odbc_queries).
-
--behaviour(ejabberd_config).
-
--author("mremond@process-one.net").
-
--export([get_db_type/0, update/5, update_t/4,
- sql_transaction/2, get_last/2, set_last_t/4, del_last/2,
- get_password/2, get_password_scram/2, set_password_t/3,
- set_password_scram_t/6, add_user/3, add_user_scram/6,
- del_user/2, del_user_return_password/3, list_users/1,
- list_users/2, users_number/1, users_number/2,
- add_spool_sql/2, add_spool/2, get_and_del_spool_msg_t/2,
- del_spool_msg/2, get_roster/2, get_roster_jid_groups/2,
- get_roster_groups/3, del_user_roster_t/2,
- get_roster_by_jid/3, get_rostergroup_by_jid/3,
- del_roster/3, del_roster_sql/2, update_roster/5,
- update_roster_sql/4, roster_subscribe/4,
- get_subscription/3, set_private_data/4,
- set_private_data_sql/3, get_private_data/3,
- get_private_data/2, del_user_private_storage/2,
- get_default_privacy_list/2,
- get_default_privacy_list_t/1, get_privacy_list_names/2,
- get_privacy_list_names_t/1, get_privacy_list_id/3,
- get_privacy_list_id_t/2, get_privacy_list_data/3,
- get_privacy_list_data_by_id/2,
- get_privacy_list_data_t/2,
- get_privacy_list_data_by_id_t/1,
- set_default_privacy_list/2,
- unset_default_privacy_list/2, remove_privacy_list/2,
- add_privacy_list/2, set_privacy_list/2,
- del_privacy_lists/3, set_vcard/26, get_vcard/2,
- escape/1, count_records_where/3, get_roster_version/2,
- set_roster_version/2, opt_type/1]).
-
--include("ejabberd.hrl").
--include("logger.hrl").
-
-%% Almost a copy of string:join/2.
-%% We use this version because string:join/2 is relatively
-%% new function (introduced in R12B-0).
-join([], _Sep) -> [];
-join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
-
-get_db_type() -> generic.
-
-%% Safe atomic update.
-update_t(Table, Fields, Vals, Where) ->
- UPairs = lists:zipwith(fun (A, B) ->
- <<A/binary, "='", B/binary, "'">>
- end,
- Fields, Vals),
- case ejabberd_odbc:sql_query_t([<<"update ">>, Table,
- <<" set ">>, join(UPairs, <<", ">>),
- <<" where ">>, Where, <<";">>])
- of
- {updated, 1} -> ok;
- _ ->
- Res = ejabberd_odbc:sql_query_t([<<"insert into ">>, Table,
- <<"(">>, join(Fields, <<", ">>),
- <<") values ('">>, join(Vals, <<"', '">>),
- <<"');">>]),
- case Res of
- {updated,1} -> ok;
- _ -> Res
- end
- end.
-
-update(LServer, Table, Fields, Vals, Where) ->
- UPairs = lists:zipwith(fun (A, B) ->
- <<A/binary, "='", B/binary, "'">>
- end,
- Fields, Vals),
- case ejabberd_odbc:sql_query(LServer,
- [<<"update ">>, Table, <<" set ">>,
- join(UPairs, <<", ">>), <<" where ">>, Where,
- <<";">>])
- of
- {updated, 1} -> ok;
- _ ->
- Res = ejabberd_odbc:sql_query(LServer,
- [<<"insert into ">>, Table, <<"(">>,
- join(Fields, <<", ">>), <<") values ('">>,
- join(Vals, <<"', '">>), <<"');">>]),
- case Res of
- {updated,1} -> ok;
- _ -> Res
- end
- end.
-
-%% F can be either a fun or a list of queries
-%% TODO: We should probably move the list of queries transaction
-%% wrapper from the ejabberd_odbc module to this one (odbc_queries)
-sql_transaction(LServer, F) ->
- ejabberd_odbc:sql_transaction(LServer, F).
-
-get_last(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select seconds, state from last where "
- "username='">>,
- Username, <<"'">>]).
-
-set_last_t(LServer, Username, Seconds, State) ->
- update(LServer, <<"last">>,
- [<<"username">>, <<"seconds">>, <<"state">>],
- [Username, Seconds, State],
- [<<"username='">>, Username, <<"'">>]).
-
-del_last(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from last where username='">>, Username,
- <<"'">>]).
-
-get_password(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select password from users where username='">>,
- Username, <<"';">>]).
-
-get_password_scram(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- [<<"select password, serverkey, salt, iterationcount from users where "
- "username='">>, Username, <<"';">>]).
-
-set_password_t(LServer, Username, Pass) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- update_t(<<"users">>,
- [<<"username">>,
- <<"password">>],
- [Username, Pass],
- [<<"username='">>, Username,
- <<"'">>])
- end).
-
-set_password_scram_t(LServer, Username,
- StoredKey, ServerKey, Salt, IterationCount) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- update_t(<<"users">>,
- [<<"username">>,
- <<"password">>,
- <<"serverkey">>,
- <<"salt">>,
- <<"iterationcount">>],
- [Username, StoredKey,
- ServerKey, Salt,
- IterationCount],
- [<<"username='">>, Username,
- <<"'">>])
- end).
-
-add_user(LServer, Username, Pass) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"insert into users(username, password) "
- "values ('">>,
- Username, <<"', '">>, Pass, <<"');">>]).
-
-add_user_scram(LServer, Username,
- StoredKey, ServerKey, Salt, IterationCount) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"insert into users(username, password, serverkey, salt, iterationcount) "
- "values ('">>,
- Username, <<"', '">>, StoredKey, <<"', '">>,
- ServerKey, <<"', '">>,
- Salt, <<"', '">>,
- IterationCount, <<"');">>]).
-
-del_user(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from users where username='">>, Username,
- <<"';">>]).
-
-del_user_return_password(_LServer, Username, Pass) ->
- P =
- ejabberd_odbc:sql_query_t([<<"select password from users where username='">>,
- Username, <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from users where username='">>,
- Username, <<"' and password='">>, Pass,
- <<"';">>]),
- P.
-
-list_users(LServer) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select username from users">>]).
-
-list_users(LServer, [{from, Start}, {to, End}])
- when is_integer(Start) and is_integer(End) ->
- list_users(LServer,
- [{limit, End - Start + 1}, {offset, Start - 1}]);
-list_users(LServer,
- [{prefix, Prefix}, {from, Start}, {to, End}])
- when is_binary(Prefix) and is_integer(Start) and
- is_integer(End) ->
- list_users(LServer,
- [{prefix, Prefix}, {limit, End - Start + 1},
- {offset, Start - 1}]);
-list_users(LServer, [{limit, Limit}, {offset, Offset}])
- when is_integer(Limit) and is_integer(Offset) ->
- ejabberd_odbc:sql_query(LServer,
- [list_to_binary(
- io_lib:format(
- "select username from users " ++
- "order by username " ++
- "limit ~w offset ~w",
- [Limit, Offset]))]);
-list_users(LServer,
- [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
- when is_binary(Prefix) and is_integer(Limit) and
- is_integer(Offset) ->
- ejabberd_odbc:sql_query(LServer,
- [list_to_binary(
- io_lib:format(
- "select username from users " ++
- "where username like '~s%' " ++
- "order by username " ++
- "limit ~w offset ~w ",
- [Prefix, Limit, Offset]))]).
-
-users_number(LServer) ->
- Type = ejabberd_config:get_option({odbc_type, LServer},
- fun(pgsql) -> pgsql;
- (mysql) -> mysql;
- (sqlite) -> sqlite;
- (odbc) -> odbc
- end, odbc),
- case Type of
- pgsql ->
- case
- ejabberd_config:get_option(
- {pgsql_users_number_estimate, LServer},
- fun(V) when is_boolean(V) -> V end,
- false)
- of
- true ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select reltuples from pg_class where "
- "oid = 'users'::regclass::oid">>]);
- _ ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from users">>])
- end;
- _ ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from users">>])
- end.
-
-users_number(LServer, [{prefix, Prefix}])
- when is_binary(Prefix) ->
- ejabberd_odbc:sql_query(LServer,
- [list_to_binary(
- io_lib:fwrite(
- "select count(*) from users " ++
- %% Warning: Escape prefix at higher level to prevent SQL
- %% injection.
- "where username like '~s%'",
- [Prefix]))]);
-users_number(LServer, []) ->
- users_number(LServer).
-
-
-add_spool_sql(Username, XML) ->
- [<<"insert into spool(username, xml) values ('">>,
- Username, <<"', '">>, XML, <<"');">>].
-
-add_spool(LServer, Queries) ->
- ejabberd_odbc:sql_transaction(LServer, Queries).
-
-get_and_del_spool_msg_t(LServer, Username) ->
- F = fun () ->
- Result =
- ejabberd_odbc:sql_query_t([<<"select username, xml from spool where "
- "username='">>,
- Username,
- <<"' order by seq;">>]),
- ejabberd_odbc:sql_query_t([<<"delete from spool where username='">>,
- Username, <<"';">>]),
- Result
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
-
-del_spool_msg(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from spool where username='">>, Username,
- <<"';">>]).
-
-get_roster(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select username, jid, nick, subscription, "
- "ask, askmessage, server, subscribe, "
- "type from rosterusers where username='">>,
- Username, <<"'">>]).
-
-get_roster_jid_groups(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select jid, grp from rostergroups where "
- "username='">>,
- Username, <<"'">>]).
-
-get_roster_groups(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t([<<"select grp from rostergroups where username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>]).
-
-del_user_roster_t(LServer, Username) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from rosterusers where "
- "username='">>,
- Username,
- <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
- "username='">>,
- Username,
- <<"';">>])
- end).
-
-get_roster_by_jid(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t([<<"select username, jid, nick, subscription, "
- "ask, askmessage, server, subscribe, "
- "type from rosterusers where username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>]).
-
-get_rostergroup_by_jid(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select grp from rostergroups where username='">>,
- Username, <<"' and jid='">>, SJID, <<"'">>]).
-
-del_roster(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t([<<"delete from rosterusers where "
- "username='">>,
- Username, <<"' and jid='">>, SJID,
- <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
- "username='">>,
- Username, <<"' and jid='">>, SJID,
- <<"';">>]).
-
-del_roster_sql(Username, SJID) ->
- [[<<"delete from rosterusers where "
- "username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>],
- [<<"delete from rostergroups where "
- "username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>]].
-
-update_roster(_LServer, Username, SJID, ItemVals,
- ItemGroups) ->
- update_t(<<"rosterusers">>,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- ItemVals,
- [<<"username='">>, Username, <<"' and jid='">>, SJID,
- <<"'">>]),
- ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
- "username='">>,
- Username, <<"' and jid='">>, SJID,
- <<"';">>]),
- lists:foreach(fun (ItemGroup) ->
- ejabberd_odbc:sql_query_t([<<"insert into rostergroups( "
- " username, jid, grp) values ('">>,
- join(ItemGroup,
- <<"', '">>),
- <<"');">>])
- end,
- ItemGroups).
-
-update_roster_sql(Username, SJID, ItemVals,
- ItemGroups) ->
- [[<<"delete from rosterusers where "
- "username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>],
- [<<"insert into rosterusers( "
- " username, jid, nick, "
- " subscription, ask, askmessage, "
- " server, subscribe, type) "
- "values ('">>,
- join(ItemVals, <<"', '">>), <<"');">>],
- [<<"delete from rostergroups where "
- "username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>]]
- ++
- [[<<"insert into rostergroups( "
- " username, jid, grp) values ('">>,
- join(ItemGroup, <<"', '">>), <<"');">>]
- || ItemGroup <- ItemGroups].
-
-roster_subscribe(_LServer, Username, SJID, ItemVals) ->
- update_t(<<"rosterusers">>,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- ItemVals,
- [<<"username='">>, Username, <<"' and jid='">>, SJID,
- <<"'">>]).
-
-get_subscription(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select subscription from rosterusers "
- "where username='">>,
- Username, <<"' and jid='">>, SJID, <<"'">>]).
-
-set_private_data(_LServer, Username, LXMLNS, SData) ->
- update_t(<<"private_storage">>,
- [<<"username">>, <<"namespace">>, <<"data">>],
- [Username, LXMLNS, SData],
- [<<"username='">>, Username, <<"' and namespace='">>,
- LXMLNS, <<"'">>]).
-
-set_private_data_sql(Username, LXMLNS, SData) ->
- [[<<"delete from private_storage where username='">>,
- Username, <<"' and namespace='">>, LXMLNS, <<"';">>],
- [<<"insert into private_storage(username, "
- "namespace, data) values ('">>,
- Username, <<"', '">>, LXMLNS, <<"', '">>, SData,
- <<"');">>]].
-
-get_private_data(LServer, Username, LXMLNS) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select data from private_storage where "
- "username='">>,
- Username, <<"' and namespace='">>, LXMLNS,
- <<"';">>]).
-
-get_private_data(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select namespace, data from private_storage "
- "where username='">>, Username, <<"';">>]).
-
-del_user_private_storage(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from private_storage where username='">>,
- Username, <<"';">>]).
-
-set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN,
- SFamily, SGiven, SLBDay, SLCTRY, SLEMail, SLFN,
- SLFamily, SLGiven, SLLocality, SLMiddle, SLNickname,
- SLOrgName, SLOrgUnit, SLocality, SMiddle, SNickname,
- SOrgName, SOrgUnit, SVCARD, Username) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- update_t(<<"vcard">>,
- [<<"username">>,
- <<"vcard">>],
- [LUsername, SVCARD],
- [<<"username='">>, LUsername,
- <<"'">>]),
- update_t(<<"vcard_search">>,
- [<<"username">>,
- <<"lusername">>, <<"fn">>,
- <<"lfn">>, <<"family">>,
- <<"lfamily">>, <<"given">>,
- <<"lgiven">>, <<"middle">>,
- <<"lmiddle">>,
- <<"nickname">>,
- <<"lnickname">>, <<"bday">>,
- <<"lbday">>, <<"ctry">>,
- <<"lctry">>, <<"locality">>,
- <<"llocality">>,
- <<"email">>, <<"lemail">>,
- <<"orgname">>,
- <<"lorgname">>,
- <<"orgunit">>,
- <<"lorgunit">>],
- [Username, LUsername, SFN,
- SLFN, SFamily, SLFamily,
- SGiven, SLGiven, SMiddle,
- SLMiddle, SNickname,
- SLNickname, SBDay, SLBDay,
- SCTRY, SLCTRY, SLocality,
- SLLocality, SEMail, SLEMail,
- SOrgName, SLOrgName,
- SOrgUnit, SLOrgUnit],
- [<<"lusername='">>,
- LUsername, <<"'">>])
- end).
-
-get_vcard(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select vcard from vcard where username='">>,
- Username, <<"';">>]).
-
-get_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select name from privacy_default_list "
- "where username='">>,
- Username, <<"';">>]).
-
-get_default_privacy_list_t(Username) ->
- ejabberd_odbc:sql_query_t([<<"select name from privacy_default_list "
- "where username='">>,
- Username, <<"';">>]).
-
-get_privacy_list_names(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select name from privacy_list where "
- "username='">>,
- Username, <<"';">>]).
-
-get_privacy_list_names_t(Username) ->
- ejabberd_odbc:sql_query_t([<<"select name from privacy_list where "
- "username='">>,
- Username, <<"';">>]).
-
-get_privacy_list_id(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select id from privacy_list where username='">>,
- Username, <<"' and name='">>, SName, <<"';">>]).
-
-get_privacy_list_id_t(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"select id from privacy_list where username='">>,
- Username, <<"' and name='">>, SName, <<"';">>]).
-
-get_privacy_list_data(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id = (select id from privacy_list "
- "where username='">>,
- Username, <<"' and name='">>, SName,
- <<"') order by ord;">>]).
-
-get_privacy_list_data_t(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id = (select id from privacy_list "
- "where username='">>,
- Username, <<"' and name='">>, SName,
- <<"') order by ord;">>]).
-
-get_privacy_list_data_by_id(LServer, ID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id='">>,
- ID, <<"' order by ord;">>]).
-
-get_privacy_list_data_by_id_t(ID) ->
- ejabberd_odbc:sql_query_t([<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id='">>,
- ID, <<"' order by ord;">>]).
-
-set_default_privacy_list(Username, SName) ->
- update_t(<<"privacy_default_list">>,
- [<<"username">>, <<"name">>], [Username, SName],
- [<<"username='">>, Username, <<"'">>]).
-
-unset_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_default_list "
- " where username='">>,
- Username, <<"';">>]).
-
-remove_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"delete from privacy_list where username='">>,
- Username, <<"' and name='">>, SName, <<"';">>]).
-
-add_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"insert into privacy_list(username, name) "
- "values ('">>,
- Username, <<"', '">>, SName, <<"');">>]).
-
-set_privacy_list(ID, RItems) ->
- ejabberd_odbc:sql_query_t([<<"delete from privacy_list_data where "
- "id='">>,
- ID, <<"';">>]),
- lists:foreach(fun (Items) ->
- ejabberd_odbc:sql_query_t([<<"insert into privacy_list_data(id, t, "
- "value, action, ord, match_all, match_iq, "
- "match_message, match_presence_in, match_prese"
- "nce_out ) values ('">>,
- ID, <<"', '">>,
- join(Items, <<"', '">>),
- <<"');">>])
- end,
- RItems).
-
-del_privacy_lists(LServer, Server, Username) ->
-%% Characters to escape
-%% Count number of records in a table given a where clause
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_list where username='">>,
- Username, <<"';">>]),
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_list_data where "
- "value='">>,
- <<Username/binary, "@", Server/binary>>,
- <<"';">>]),
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_default_list where "
- "username='">>,
- Username, <<"';">>]).
-
-escape($\000) -> <<"\\0">>;
-escape($\n) -> <<"\\n">>;
-escape($\t) -> <<"\\t">>;
-escape($\b) -> <<"\\b">>;
-escape($\r) -> <<"\\r">>;
-escape($') -> <<"''">>;
-escape($") -> <<"\\\"">>;
-escape($\\) -> <<"\\\\">>;
-escape(C) -> <<C>>.
-
-count_records_where(LServer, Table, WhereClause) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from ">>, Table, <<" ">>,
- WhereClause, <<";">>]).
-
-get_roster_version(LServer, LUser) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select version from roster_version where "
- "username = '">>,
- LUser, <<"'">>]).
-
-set_roster_version(LUser, Version) ->
- update_t(<<"roster_version">>,
- [<<"username">>, <<"version">>], [LUser, Version],
- [<<"username = '">>, LUser, <<"'">>]).
-
-opt_type(odbc_type) ->
- fun (pgsql) -> pgsql;
- (mysql) -> mysql;
- (sqlite) -> sqlite;
- (mssql) -> mssql;
- (odbc) -> odbc
- end;
-opt_type(pgsql_users_number_estimate) ->
- fun (V) when is_boolean(V) -> V end;
-opt_type(_) -> [odbc_type, pgsql_users_number_estimate].
diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl
new file mode 100644
index 000000000..204cfec2f
--- /dev/null
+++ b/src/prosody2ejabberd.erl
@@ -0,0 +1,341 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 20 Jan 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(prosody2ejabberd).
+
+%% API
+-export([from_dir/1]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("logger.hrl").
+-include("mod_roster.hrl").
+-include("mod_offline.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+from_dir(ProsodyDir) ->
+ case file:list_dir(ProsodyDir) of
+ {ok, HostDirs} ->
+ lists:foreach(
+ fun(HostDir) ->
+ Host = list_to_binary(HostDir),
+ lists:foreach(
+ fun(SubDir) ->
+ Path = filename:join(
+ [ProsodyDir, HostDir, SubDir]),
+ convert_dir(Path, Host, SubDir)
+ end, ["vcard", "accounts", "roster",
+ "private", "config", "offline",
+ "privacy"])
+ end, HostDirs);
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to list ~s: ~s",
+ [ProsodyDir, file:format_error(Why)]),
+ Err
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+convert_dir(Path, Host, Type) ->
+ case file:list_dir(Path) of
+ {ok, Files} ->
+ lists:foreach(
+ fun(File) ->
+ FilePath = filename:join(Path, File),
+ case eval_file(FilePath) of
+ {ok, Data} ->
+ Name = iolist_to_binary(filename:rootname(File)),
+ convert_data(Host, Type, Name, Data);
+ Err ->
+ Err
+ end
+ end, Files);
+ {error, enoent} ->
+ ok;
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to list ~s: ~s",
+ [Path, file:format_error(Why)]),
+ Err
+ end.
+
+eval_file(Path) ->
+ case file:read_file(Path) of
+ {ok, Data} ->
+ State0 = luerl:init(),
+ State1 = luerl:set_table([item],
+ fun([X], State) -> {[X], State} end,
+ State0),
+ NewData = case filename:extension(Path) of
+ ".list" ->
+ <<"return {", Data/binary, "};">>;
+ _ ->
+ Data
+ end,
+ case luerl:eval(NewData, State1) of
+ {ok, _} = Res ->
+ Res;
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to eval ~s: ~p", [Path, Why]),
+ Err
+ end;
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to read file ~s: ~s",
+ [Path, file:format_error(Why)]),
+ Err
+ end.
+
+convert_data(Host, "accounts", User, [Data]) ->
+ Password = proplists:get_value(<<"password">>, Data, <<>>),
+ case ejabberd_auth:try_register(User, Host, Password) of
+ {atomic, ok} ->
+ ok;
+ Err ->
+ ?ERROR_MSG("failed to register user ~s@~s: ~p",
+ [User, Host, Err]),
+ Err
+ end;
+convert_data(Host, "roster", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ Rosters =
+ lists:flatmap(
+ fun({<<"pending">>, L}) ->
+ convert_pending_item(LUser, LServer, L);
+ ({S, L}) when is_binary(S) ->
+ convert_roster_item(LUser, LServer, S, L);
+ (_) ->
+ []
+ end, Data),
+ lists:foreach(fun mod_roster:set_roster/1, Rosters);
+convert_data(Host, "private", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ PrivData = lists:flatmap(
+ fun({_TagXMLNS, Raw}) ->
+ case deserialize(Raw) of
+ [El] ->
+ XMLNS = fxml:get_tag_attr_s(<<"xmlns">>, El),
+ [{XMLNS, El}];
+ _ ->
+ []
+ end
+ end, Data),
+ mod_private:set_data(LUser, LServer, PrivData);
+convert_data(Host, "vcard", User, [Data]) ->
+ LServer = jid:nameprep(Host),
+ case deserialize(Data) of
+ [VCard] ->
+ mod_vcard:set_vcard(User, LServer, VCard);
+ _ ->
+ ok
+ end;
+convert_data(_Host, "config", _User, [Data]) ->
+ RoomJID = jid:from_string(proplists:get_value(<<"jid">>, Data, <<"">>)),
+ Config = proplists:get_value(<<"_data">>, Data, []),
+ RoomCfg = convert_room_config(Data),
+ case proplists:get_bool(<<"persistent">>, Config) of
+ true when RoomJID /= error ->
+ mod_muc:store_room(?MYNAME, RoomJID#jid.lserver,
+ RoomJID#jid.luser, RoomCfg);
+ _ ->
+ ok
+ end;
+convert_data(Host, "offline", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ Msgs = lists:flatmap(
+ fun({_, RawXML}) ->
+ case deserialize(RawXML) of
+ [El] -> el_to_offline_msg(LUser, LServer, El);
+ _ -> []
+ end
+ end, Data),
+ mod_offline:store_offline_msg(
+ LServer, {LUser, LServer}, Msgs, length(Msgs), infinity);
+convert_data(Host, "privacy", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ Lists = proplists:get_value(<<"lists">>, Data, []),
+ Priv = #privacy{
+ us = {LUser, LServer},
+ default = proplists:get_value(<<"default">>, Data, none),
+ lists = lists:flatmap(
+ fun({Name, Vals}) ->
+ Items = proplists:get_value(<<"items">>, Vals, []),
+ case lists:map(fun convert_privacy_item/1,
+ Items) of
+ [] -> [];
+ ListItems -> [{Name, ListItems}]
+ end
+ end, Lists)},
+ mod_privacy:set_privacy_list(Priv);
+convert_data(_Host, _Type, _User, _Data) ->
+ ok.
+
+convert_pending_item(LUser, LServer, LuaList) ->
+ lists:flatmap(
+ fun({S, true}) ->
+ case jid:from_string(S) of
+ #jid{} = J ->
+ LJID = jid:tolower(J),
+ [#roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer},
+ jid = LJID,
+ ask = in}];
+ error ->
+ []
+ end;
+ (_) ->
+ []
+ end, LuaList).
+
+convert_roster_item(LUser, LServer, JIDstring, LuaList) ->
+ case jid:from_string(JIDstring) of
+ #jid{} = JID ->
+ LJID = jid:tolower(JID),
+ InitR = #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer},
+ jid = LJID},
+ Roster =
+ lists:foldl(
+ fun({<<"groups">>, Val}, R) ->
+ Gs = lists:flatmap(
+ fun({G, true}) -> [G];
+ (_) -> []
+ end, Val),
+ R#roster{groups = Gs};
+ ({<<"subscription">>, Sub}, R) ->
+ R#roster{subscription = jlib:binary_to_atom(Sub)};
+ ({<<"ask">>, <<"subscribe">>}, R) ->
+ R#roster{ask = out};
+ ({<<"name">>, Name}, R) ->
+ R#roster{name = Name}
+ end, InitR, LuaList),
+ [Roster];
+ error ->
+ []
+ end.
+
+convert_room_affiliations(Data) ->
+ lists:flatmap(
+ fun({J, Aff}) ->
+ case jid:from_string(J) of
+ #jid{luser = U, lserver = S} ->
+ [{{U, S, <<>>}, jlib:binary_to_atom(Aff)}];
+ error ->
+ []
+ end
+ end, proplists:get_value(<<"_affiliations">>, Data, [])).
+
+convert_room_config(Data) ->
+ Config = proplists:get_value(<<"_data">>, Data, []),
+ Pass = case proplists:get_value(<<"password">>, Config, <<"">>) of
+ <<"">> ->
+ [];
+ Password ->
+ [{password_protected, true},
+ {password, Password}]
+ end,
+ Subj = case jid:from_string(
+ proplists:get_value(
+ <<"subject_from">>, Config, <<"">>)) of
+ #jid{lresource = Nick} when Nick /= <<"">> ->
+ [{subject, proplists:get_value(<<"subject">>, Config, <<"">>)},
+ {subject_author, Nick}];
+ _ ->
+ []
+ end,
+ Anonymous = case proplists:get_value(<<"whois">>, Config, <<"moderators">>) of
+ <<"moderators">> -> true;
+ _ -> false
+ end,
+ [{affiliations, convert_room_affiliations(Data)},
+ {allow_change_subj, proplists:get_bool(<<"changesubject">>, Config)},
+ {description, proplists:get_value(<<"description">>, Config, <<"">>)},
+ {members_only, proplists:get_bool(<<"members_only">>, Config)},
+ {moderated, proplists:get_bool(<<"moderated">>, Config)},
+ {anonymous, Anonymous}] ++ Pass ++ Subj.
+
+convert_privacy_item({_, Item}) ->
+ Action = proplists:get_value(<<"action">>, Item, <<"allow">>),
+ Order = proplists:get_value(<<"order">>, Item, 0),
+ T = jlib:binary_to_atom(proplists:get_value(<<"type">>, Item, <<"none">>)),
+ V = proplists:get_value(<<"value">>, Item, <<"">>),
+ MatchIQ = proplists:get_bool(<<"iq">>, Item),
+ MatchMsg = proplists:get_bool(<<"message">>, Item),
+ MatchPresIn = proplists:get_bool(<<"presence-in">>, Item),
+ MatchPresOut = proplists:get_bool(<<"presence-out">>, Item),
+ MatchAll = if (MatchIQ == false) and (MatchMsg == false) and
+ (MatchPresIn == false) and (MatchPresOut == false) ->
+ true;
+ true ->
+ false
+ end,
+ {Type, Value} = try case T of
+ none -> {T, none};
+ group -> {T, V};
+ jid -> {T, jid:tolower(jid:from_string(V))};
+ subscription -> {T, jlib:binary_to_atom(V)}
+ end
+ catch _:_ ->
+ {none, none}
+ end,
+ #listitem{type = Type,
+ value = Value,
+ action = jlib:binary_to_atom(Action),
+ order = erlang:trunc(Order),
+ match_all = MatchAll,
+ match_iq = MatchIQ,
+ match_message = MatchMsg,
+ match_presence_in = MatchPresIn,
+ match_presence_out = MatchPresOut}.
+
+el_to_offline_msg(LUser, LServer, #xmlel{attrs = Attrs} = El) ->
+ case jlib:datetime_string_to_timestamp(
+ fxml:get_attr_s(<<"stamp">>, Attrs)) of
+ {_, _, _} = TS ->
+ Attrs1 = lists:filter(
+ fun(<<"stamp">>) -> false;
+ (<<"stamp_legacy">>) -> false;
+ (_) -> true
+ end, Attrs),
+ Packet = El#xmlel{attrs = Attrs1},
+ case {jid:from_string(fxml:get_attr_s(<<"from">>, Attrs)),
+ jid:from_string(fxml:get_attr_s(<<"to">>, Attrs))} of
+ {#jid{} = From, #jid{} = To} ->
+ [#offline_msg{
+ us = {LUser, LServer},
+ timestamp = TS,
+ expire = never,
+ from = From,
+ to = To,
+ packet = Packet}];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end.
+
+deserialize(L) ->
+ deserialize(L, #xmlel{}, []).
+
+deserialize([{<<"attr">>, Attrs}|T], El, Acc) ->
+ deserialize(T, El#xmlel{attrs = Attrs ++ El#xmlel.attrs}, Acc);
+deserialize([{<<"name">>, Name}|T], El, Acc) ->
+ deserialize(T, El#xmlel{name = Name}, Acc);
+deserialize([{_, S}|T], #xmlel{children = Els} = El, Acc) when is_binary(S) ->
+ deserialize(T, El#xmlel{children = [{xmlcdata, S}|Els]}, Acc);
+deserialize([{_, L}|T], #xmlel{children = Els} = El, Acc) when is_list(L) ->
+ deserialize(T, El#xmlel{children = deserialize(L) ++ Els}, Acc);
+deserialize([], El, Acc) ->
+ [El|Acc].
diff --git a/src/pubsub_db_odbc.erl b/src/pubsub_db_sql.erl
index cfdeda1ef..b910a5e7d 100644
--- a/src/pubsub_db_odbc.erl
+++ b/src/pubsub_db_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : pubsub_db_odbc.erl
+%%% File : pubsub_db_sql.erl
%%% Author : Pablo Polvorin <pablo.polvorin@process-one.net>
%%% Purpose : Provide helpers for PubSub ODBC backend
%%% Created : 7 Aug 2009 by Pablo Polvorin <pablo.polvorin@process-one.net>
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(pubsub_db_odbc).
+-module(pubsub_db_sql).
-author("pablo.polvorin@process-one.net").
@@ -37,16 +37,16 @@
%% -spec read_subscription(SubID :: string()) -> {ok, #pubsub_subscription{}} | notfound.
read_subscription(SubID) ->
case
- ejabberd_odbc:sql_query_t([<<"select opt_name, opt_value from pubsub_subscr"
+ ejabberd_sql:sql_query_t([<<"select opt_name, opt_value from pubsub_subscr"
"iption_opt where subid = '">>,
- ejabberd_odbc:escape(SubID), <<"'">>])
+ ejabberd_sql:escape(SubID), <<"'">>])
of
{selected, [<<"opt_name">>, <<"opt_value">>], []} ->
notfound;
{selected, [<<"opt_name">>, <<"opt_value">>], Options} ->
{ok,
#pubsub_subscription{subid = SubID,
- options = lists:map(fun subscription_opt_from_odbc/1, Options)}}
+ options = lists:map(fun subscription_opt_from_sql/1, Options)}}
end.
%% -spec delete_subscription(SubID :: string()) -> ok.
@@ -54,19 +54,19 @@ delete_subscription(SubID) ->
%% -spec update_subscription(#pubsub_subscription{}) -> ok .
%% -spec add_subscription(#pubsub_subscription{}) -> ok.
%% -------------- Internal utilities -----------------------
- ejabberd_odbc:sql_query_t([<<"delete from pubsub_subscription_opt "
+ ejabberd_sql:sql_query_t([<<"delete from pubsub_subscription_opt "
"where subid = '">>,
- ejabberd_odbc:escape(SubID), <<"'">>]),
+ ejabberd_sql:escape(SubID), <<"'">>]),
ok.
update_subscription(#pubsub_subscription{subid = SubId} = Sub) ->
delete_subscription(SubId), add_subscription(Sub).
add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) ->
- EscapedSubId = ejabberd_odbc:escape(SubId),
+ EscapedSubId = ejabberd_sql:escape(SubId),
lists:foreach(fun (Opt) ->
- {OdbcOptName, OdbcOptValue} = subscription_opt_to_odbc(Opt),
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_subscription_opt(subid, "
+ {OdbcOptName, OdbcOptValue} = subscription_opt_to_sql(Opt),
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_subscription_opt(subid, "
"opt_name, opt_value)values ('">>,
EscapedSubId, <<"','">>,
OdbcOptName, <<"','">>,
@@ -75,67 +75,67 @@ add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) ->
Opts),
ok.
-subscription_opt_from_odbc({<<"DELIVER">>, Value}) ->
- {deliver, odbc_to_boolean(Value)};
-subscription_opt_from_odbc({<<"DIGEST">>, Value}) ->
- {digest, odbc_to_boolean(Value)};
-subscription_opt_from_odbc({<<"DIGEST_FREQUENCY">>, Value}) ->
- {digest_frequency, odbc_to_integer(Value)};
-subscription_opt_from_odbc({<<"EXPIRE">>, Value}) ->
- {expire, odbc_to_timestamp(Value)};
-subscription_opt_from_odbc({<<"INCLUDE_BODY">>, Value}) ->
- {include_body, odbc_to_boolean(Value)};
+subscription_opt_from_sql({<<"DELIVER">>, Value}) ->
+ {deliver, sql_to_boolean(Value)};
+subscription_opt_from_sql({<<"DIGEST">>, Value}) ->
+ {digest, sql_to_boolean(Value)};
+subscription_opt_from_sql({<<"DIGEST_FREQUENCY">>, Value}) ->
+ {digest_frequency, sql_to_integer(Value)};
+subscription_opt_from_sql({<<"EXPIRE">>, Value}) ->
+ {expire, sql_to_timestamp(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_odbc({<<"SHOW_VALUES">>, Value}) ->
+subscription_opt_from_sql({<<"SHOW_VALUES">>, Value}) ->
{show_values, Value};
-subscription_opt_from_odbc({<<"SUBSCRIPTION_TYPE">>, Value}) ->
+subscription_opt_from_sql({<<"SUBSCRIPTION_TYPE">>, Value}) ->
{subscription_type,
case Value of
<<"items">> -> items;
<<"nodes">> -> nodes
end};
-subscription_opt_from_odbc({<<"SUBSCRIPTION_DEPTH">>, Value}) ->
+subscription_opt_from_sql({<<"SUBSCRIPTION_DEPTH">>, Value}) ->
{subscription_depth,
case Value of
<<"all">> -> all;
- N -> odbc_to_integer(N)
+ N -> sql_to_integer(N)
end}.
-subscription_opt_to_odbc({deliver, Bool}) ->
- {<<"DELIVER">>, boolean_to_odbc(Bool)};
-subscription_opt_to_odbc({digest, Bool}) ->
- {<<"DIGEST">>, boolean_to_odbc(Bool)};
-subscription_opt_to_odbc({digest_frequency, Int}) ->
- {<<"DIGEST_FREQUENCY">>, integer_to_odbc(Int)};
-subscription_opt_to_odbc({expire, Timestamp}) ->
- {<<"EXPIRE">>, timestamp_to_odbc(Timestamp)};
-subscription_opt_to_odbc({include_body, Bool}) ->
- {<<"INCLUDE_BODY">>, boolean_to_odbc(Bool)};
-subscription_opt_to_odbc({show_values, Values}) ->
+subscription_opt_to_sql({deliver, Bool}) ->
+ {<<"DELIVER">>, boolean_to_sql(Bool)};
+subscription_opt_to_sql({digest, Bool}) ->
+ {<<"DIGEST">>, boolean_to_sql(Bool)};
+subscription_opt_to_sql({digest_frequency, Int}) ->
+ {<<"DIGEST_FREQUENCY">>, integer_to_sql(Int)};
+subscription_opt_to_sql({expire, Timestamp}) ->
+ {<<"EXPIRE">>, timestamp_to_sql(Timestamp)};
+subscription_opt_to_sql({include_body, Bool}) ->
+ {<<"INCLUDE_BODY">>, boolean_to_sql(Bool)};
+subscription_opt_to_sql({show_values, Values}) ->
{<<"SHOW_VALUES">>, Values};
-subscription_opt_to_odbc({subscription_type, Type}) ->
+subscription_opt_to_sql({subscription_type, Type}) ->
{<<"SUBSCRIPTION_TYPE">>,
case Type of
items -> <<"items">>;
nodes -> <<"nodes">>
end};
-subscription_opt_to_odbc({subscription_depth, Depth}) ->
+subscription_opt_to_sql({subscription_depth, Depth}) ->
{<<"SUBSCRIPTION_DEPTH">>,
case Depth of
all -> <<"all">>;
- N -> integer_to_odbc(N)
+ N -> integer_to_sql(N)
end}.
-integer_to_odbc(N) -> iolist_to_binary(integer_to_list(N)).
+integer_to_sql(N) -> iolist_to_binary(integer_to_list(N)).
-boolean_to_odbc(true) -> <<"1">>;
-boolean_to_odbc(false) -> <<"0">>.
+boolean_to_sql(true) -> <<"1">>;
+boolean_to_sql(false) -> <<"0">>.
-timestamp_to_odbc(T) -> jlib:now_to_utc_string(T).
+timestamp_to_sql(T) -> jlib:now_to_utc_string(T).
-odbc_to_integer(N) -> jlib:binary_to_integer(N).
+sql_to_integer(N) -> jlib:binary_to_integer(N).
-odbc_to_boolean(B) -> B == <<"1">>.
+sql_to_boolean(B) -> B == <<"1">>.
-odbc_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T).
+sql_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T).
diff --git a/src/pubsub_subscription.erl b/src/pubsub_subscription.erl
index 9918a2c35..f990f6e38 100644
--- a/src/pubsub_subscription.erl
+++ b/src/pubsub_subscription.erl
@@ -126,7 +126,7 @@ get_options_xform(Lang, Options) ->
++ XFields}}.
parse_options_xform(XFields) ->
- case xml:remove_cdata(XFields) of
+ case fxml:remove_cdata(XFields) of
[#xmlel{name = <<"x">>} = XEl] ->
case jlib:parse_xdata_submit(XEl) of
XData when is_list(XData) ->
@@ -237,31 +237,40 @@ var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
var_xfield(_) -> {error, badarg}.
-val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest_frequency, [Val]) ->
+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
N when is_integer(N) -> N;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end;
val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
-val_xfield(include_body, [Val]) -> xopt_to_bool(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, [Depth]) ->
+val_xfield(subscription_depth = Opt, [Depth]) ->
case catch jlib:binary_to_integer(Depth) of
N when is_integer(N) -> N;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end.
%% Convert XForm booleans to Erlang booleans.
-xopt_to_bool(<<"0">>) -> false;
-xopt_to_bool(<<"1">>) -> true;
-xopt_to_bool(<<"false">>) -> false;
-xopt_to_bool(<<"true">>) -> true;
-xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
+xopt_to_bool(_, <<"0">>) -> false;
+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/3 ::
(
diff --git a/src/pubsub_subscription_odbc.erl b/src/pubsub_subscription_sql.erl
index 6791c4ac7..1f82aa008 100644
--- a/src/pubsub_subscription_odbc.erl
+++ b/src/pubsub_subscription_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : pubsub_subscription_odbc.erl
+%%% File : pubsub_subscription_sql.erl
%%% Author : Pablo Polvorin <pablo.polvorin@process-one.net>
%%% Purpose : Handle pubsub subscriptions options with ODBC backend
%%% based on pubsub_subscription.erl by Brian Cully <bjc@kublai.com>
@@ -24,7 +24,7 @@
%%%
%%%----------------------------------------------------------------------
--module(pubsub_subscription_odbc).
+-module(pubsub_subscription_sql).
-author("pablo.polvorin@process-one.net").
%% API
@@ -66,7 +66,7 @@
-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL, <<"Receive notification from direct child nodes only">>).
-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, <<"Receive notification from all descendent nodes">>).
--define(DB_MOD, pubsub_db_odbc).
+-define(DB_MOD, pubsub_db_sql).
%%====================================================================
%% API
%%====================================================================
@@ -151,7 +151,7 @@ get_options_xform(Lang, Options) ->
++ XFields}}.
parse_options_xform(XFields) ->
- case xml:remove_cdata(XFields) of
+ case fxml:remove_cdata(XFields) of
[#xmlel{name = <<"x">>} = XEl] ->
case jlib:parse_xdata_submit(XEl) of
XData when is_list(XData) ->
@@ -200,31 +200,40 @@ var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
var_xfield(_) -> {error, badarg}.
-val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest_frequency, [Val]) ->
+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
N when is_integer(N) -> N;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end;
val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
-val_xfield(include_body, [Val]) -> xopt_to_bool(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, [Depth]) ->
+val_xfield(subscription_depth = Opt, [Depth]) ->
case catch jlib:binary_to_integer(Depth) of
N when is_integer(N) -> N;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end.
%% Convert XForm booleans to Erlang booleans.
-xopt_to_bool(<<"0">>) -> false;
-xopt_to_bool(<<"1">>) -> true;
-xopt_to_bool(<<"false">>) -> false;
-xopt_to_bool(<<"true">>) -> true;
-xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
+xopt_to_bool(_, <<"0">>) -> false;
+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)}.
%% Return a field for an XForm for Key, with data filled in, if
%% applicable, from Options.
diff --git a/src/sql_queries.erl b/src/sql_queries.erl
new file mode 100644
index 000000000..98530a4cf
--- /dev/null
+++ b/src/sql_queries.erl
@@ -0,0 +1,644 @@
+%%%----------------------------------------------------------------------
+%%% File : sql_queries.erl
+%%% Author : Mickael Remond <mremond@process-one.net>
+%%% Purpose : ODBC queries dependind on back-end
+%%% Created : by Mickael Remond <mremond@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License along
+%%% with this program; if not, write to the Free Software Foundation, Inc.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%----------------------------------------------------------------------
+
+-module(sql_queries).
+
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
+-behaviour(ejabberd_config).
+
+-author("mremond@process-one.net").
+
+-export([get_db_type/0, update/5, update_t/4,
+ sql_transaction/2, get_last/2, set_last_t/4, del_last/2,
+ get_password/2, get_password_scram/2, set_password_t/3,
+ set_password_scram_t/6, add_user/3, add_user_scram/6,
+ del_user/2, del_user_return_password/3, list_users/1,
+ list_users/2, users_number/1, users_number/2,
+ add_spool_sql/2, add_spool/2, get_and_del_spool_msg_t/2,
+ del_spool_msg/2, get_roster/2, get_roster_jid_groups/2,
+ get_roster_groups/3, del_user_roster_t/2,
+ get_roster_by_jid/3, get_rostergroup_by_jid/3,
+ del_roster/3, del_roster_sql/2, update_roster/5,
+ update_roster_sql/4, roster_subscribe/1,
+ get_subscription/3, set_private_data/4,
+ set_private_data_sql/3, get_private_data/3,
+ get_private_data/2, del_user_private_storage/2,
+ get_default_privacy_list/2,
+ get_default_privacy_list_t/1, get_privacy_list_names/2,
+ get_privacy_list_names_t/1, get_privacy_list_id/3,
+ get_privacy_list_id_t/2, get_privacy_list_data/3,
+ get_privacy_list_data_by_id/2,
+ get_privacy_list_data_t/2,
+ get_privacy_list_data_by_id_t/1,
+ set_default_privacy_list/2,
+ unset_default_privacy_list/2, remove_privacy_list/2,
+ add_privacy_list/2, set_privacy_list/2,
+ del_privacy_lists/2, set_vcard/26, get_vcard/2,
+ escape/1, count_records_where/3, get_roster_version/2,
+ set_roster_version/2, opt_type/1]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
+
+%% Almost a copy of string:join/2.
+%% We use this version because string:join/2 is relatively
+%% new function (introduced in R12B-0).
+join([], _Sep) -> [];
+join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
+
+get_db_type() -> generic.
+
+%% Safe atomic update.
+update_t(Table, Fields, Vals, Where) ->
+ UPairs = lists:zipwith(fun (A, B) ->
+ <<A/binary, "='", B/binary, "'">>
+ end,
+ Fields, Vals),
+ case ejabberd_sql:sql_query_t([<<"update ">>, Table,
+ <<" set ">>, join(UPairs, <<", ">>),
+ <<" where ">>, Where, <<";">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ Res = ejabberd_sql:sql_query_t([<<"insert into ">>, Table,
+ <<"(">>, join(Fields, <<", ">>),
+ <<") values ('">>, join(Vals, <<"', '">>),
+ <<"');">>]),
+ case Res of
+ {updated,1} -> ok;
+ _ -> Res
+ end
+ end.
+
+update(LServer, Table, Fields, Vals, Where) ->
+ UPairs = lists:zipwith(fun (A, B) ->
+ <<A/binary, "='", B/binary, "'">>
+ end,
+ Fields, Vals),
+ case ejabberd_sql:sql_query(LServer,
+ [<<"update ">>, Table, <<" set ">>,
+ join(UPairs, <<", ">>), <<" where ">>, Where,
+ <<";">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ Res = ejabberd_sql:sql_query(LServer,
+ [<<"insert into ">>, Table, <<"(">>,
+ join(Fields, <<", ">>), <<") values ('">>,
+ join(Vals, <<"', '">>), <<"');">>]),
+ case Res of
+ {updated,1} -> ok;
+ _ -> Res
+ end
+ end.
+
+%% F can be either a fun or a list of queries
+%% TODO: We should probably move the list of queries transaction
+%% wrapper from the ejabberd_sql module to this one (sql_queries)
+sql_transaction(LServer, F) ->
+ ejabberd_sql:sql_transaction(LServer, F).
+
+get_last(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(seconds)d, @(state)s from last"
+ " where username=%(LUser)s")).
+
+set_last_t(LServer, LUser, TimeStamp, Status) ->
+ ?SQL_UPSERT(LServer, "last",
+ ["!username=%(LUser)s",
+ "seconds=%(TimeStamp)d",
+ "state=%(Status)s"]).
+
+del_last(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from last where username=%(LUser)s")).
+
+get_password(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(password)s from users where username=%(LUser)s")).
+
+get_password_scram(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d"
+ " from users"
+ " where username=%(LUser)s")).
+
+set_password_t(LServer, LUser, Password) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ fun () ->
+ ?SQL_UPSERT_T(
+ "users",
+ ["!username=%(LUser)s",
+ "password=%(Password)s"])
+ end).
+
+set_password_scram_t(LServer, LUser,
+ StoredKey, ServerKey, Salt, IterationCount) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ fun () ->
+ ?SQL_UPSERT_T(
+ "users",
+ ["!username=%(LUser)s",
+ "password=%(StoredKey)s",
+ "serverkey=%(ServerKey)s",
+ "salt=%(Salt)s",
+ "iterationcount=%(IterationCount)d"])
+ end).
+
+add_user(LServer, LUser, Password) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("insert into users(username, password) "
+ "values (%(LUser)s, %(Password)s)")).
+
+add_user_scram(LServer, LUser,
+ StoredKey, ServerKey, Salt, IterationCount) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("insert into users(username, password, serverkey, salt, "
+ "iterationcount) "
+ "values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
+ " %(Salt)s, %(IterationCount)d)")).
+
+del_user(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from users where username=%(LUser)s")).
+
+del_user_return_password(_LServer, LUser, Password) ->
+ P =
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(password)s from users where username=%(LUser)s")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from users"
+ " where username=%(LUser)s and password=%(Password)s")),
+ P.
+
+list_users(LServer) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users")).
+
+list_users(LServer, [{from, Start}, {to, End}])
+ when is_integer(Start) and is_integer(End) ->
+ list_users(LServer,
+ [{limit, End - Start + 1}, {offset, Start - 1}]);
+list_users(LServer,
+ [{prefix, Prefix}, {from, Start}, {to, End}])
+ when is_binary(Prefix) and is_integer(Start) and
+ is_integer(End) ->
+ list_users(LServer,
+ [{prefix, Prefix}, {limit, End - Start + 1},
+ {offset, Start - 1}]);
+list_users(LServer, [{limit, Limit}, {offset, Offset}])
+ when is_integer(Limit) and is_integer(Offset) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users "
+ "order by username "
+ "limit %(Limit)d offset %(Offset)d"));
+list_users(LServer,
+ [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
+ when is_binary(Prefix) and is_integer(Limit) and
+ is_integer(Offset) ->
+ SPrefix = ejabberd_sql:escape_like_arg(Prefix),
+ SPrefix2 = <<SPrefix/binary, $%>>,
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users "
+ "where username like %(SPrefix2)s "
+ "order by username "
+ "limit %(Limit)d offset %(Offset)d")).
+
+users_number(LServer) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ fun(pgsql, _) ->
+ case
+ ejabberd_config:get_option(
+ {pgsql_users_number_estimate, LServer},
+ fun(V) when is_boolean(V) -> V end,
+ false) of
+ true ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(reltuples :: bigint)d from pg_class"
+ " where oid = 'users'::regclass::oid"));
+ _ ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(count(*))d from users"))
+ end;
+ (_Type, _) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(count(*))d from users"))
+ end).
+
+users_number(LServer, [{prefix, Prefix}])
+ when is_binary(Prefix) ->
+ SPrefix = ejabberd_sql:escape_like_arg(Prefix),
+ SPrefix2 = <<SPrefix/binary, $%>>,
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(count(*))d from users "
+ "where username like %(SPrefix2)s"));
+users_number(LServer, []) ->
+ users_number(LServer).
+
+add_spool_sql(Username, XML) ->
+ [<<"insert into spool(username, xml) values ('">>,
+ Username, <<"', '">>, XML, <<"');">>].
+
+add_spool(LServer, Queries) ->
+ ejabberd_sql:sql_transaction(LServer, Queries).
+
+get_and_del_spool_msg_t(LServer, LUser) ->
+ F = fun () ->
+ Result =
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(username)s, @(xml)s from spool where "
+ "username=%(LUser)s order by seq;")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from spool where username=%(LUser)s;")),
+ Result
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+del_spool_msg(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from spool where username=%(LUser)s")).
+
+get_roster(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, "
+ "@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, "
+ "@(type)s from rosterusers where username=%(LUser)s")).
+
+get_roster_jid_groups(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(jid)s, @(grp)s from rostergroups where "
+ "username=%(LUser)s")).
+
+get_roster_groups(_LServer, LUser, SJID) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(grp)s from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+del_user_roster_t(LServer, LUser) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ fun () ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from rosterusers where username=%(LUser)s")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from rostergroups where username=%(LUser)s"))
+ end).
+
+get_roster_by_jid(_LServer, LUser, SJID) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s,"
+ " @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s,"
+ " @(type)s from rosterusers"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+get_rostergroup_by_jid(LServer, LUser, SJID) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(grp)s from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+del_roster(_LServer, LUser, SJID) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from rosterusers"
+ " where username=%(LUser)s and jid=%(SJID)s")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+del_roster_sql(Username, SJID) ->
+ [[<<"delete from rosterusers where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>],
+ [<<"delete from rostergroups where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>]].
+
+update_roster(_LServer, LUser, SJID, ItemVals,
+ ItemGroups) ->
+ roster_subscribe(ItemVals),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")),
+ lists:foreach(
+ fun(ItemGroup) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("insert into rostergroups(username, jid, grp) "
+ "values (%(LUser)s, %(SJID)s, %(ItemGroup)s)"))
+ end,
+ ItemGroups).
+
+update_roster_sql(Username, SJID, ItemVals,
+ ItemGroups) ->
+ [[<<"delete from rosterusers where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>],
+ [<<"insert into rosterusers( "
+ " username, jid, nick, "
+ " subscription, ask, askmessage, "
+ " server, subscribe, type) "
+ "values ('">>,
+ join(ItemVals, <<"', '">>), <<"');">>],
+ [<<"delete from rostergroups where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>]]
+ ++
+ [[<<"insert into rostergroups( "
+ " username, jid, grp) values ('">>,
+ join(ItemGroup, <<"', '">>), <<"');">>]
+ || ItemGroup <- ItemGroups].
+
+roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) ->
+ ?SQL_UPSERT_T(
+ "rosterusers",
+ ["!username=%(LUser)s",
+ "!jid=%(SJID)s",
+ "nick=%(Name)s",
+ "subscription=%(SSubscription)s",
+ "ask=%(SAsk)s",
+ "askmessage=%(AskMessage)s",
+ "server='N'",
+ "subscribe=''",
+ "type='item'"]).
+
+get_subscription(LServer, LUser, SJID) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(subscription)s from rosterusers "
+ "where username=%(LUser)s and jid=%(SJID)s")).
+
+set_private_data(_LServer, LUser, XMLNS, SData) ->
+ ?SQL_UPSERT_T(
+ "private_storage",
+ ["!username=%(LUser)s",
+ "!namespace=%(XMLNS)s",
+ "data=%(SData)s"]).
+
+set_private_data_sql(Username, LXMLNS, SData) ->
+ [[<<"delete from private_storage where username='">>,
+ Username, <<"' and namespace='">>, LXMLNS, <<"';">>],
+ [<<"insert into private_storage(username, "
+ "namespace, data) values ('">>,
+ Username, <<"', '">>, LXMLNS, <<"', '">>, SData,
+ <<"');">>]].
+
+get_private_data(LServer, LUser, XMLNS) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(data)s from private_storage"
+ " where username=%(LUser)s and namespace=%(XMLNS)s")).
+
+get_private_data(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(namespace)s, @(data)s from private_storage"
+ " where username=%(LUser)s")).
+
+del_user_private_storage(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from private_storage"
+ " where username=%(LUser)s")).
+
+set_vcard(LServer, LUser, BDay, CTRY, EMail, FN,
+ Family, Given, LBDay, LCTRY, LEMail, LFN,
+ LFamily, LGiven, LLocality, LMiddle, LNickname,
+ LOrgName, LOrgUnit, Locality, Middle, Nickname,
+ OrgName, OrgUnit, SVCARD, User) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ fun() ->
+ ?SQL_UPSERT(LServer, "vcard",
+ ["!username=%(LUser)s",
+ "vcard=%(SVCARD)s"]),
+ ?SQL_UPSERT(LServer, "vcard_search",
+ ["username=%(User)s",
+ "!lusername=%(LUser)s",
+ "fn=%(FN)s",
+ "lfn=%(LFN)s",
+ "family=%(Family)s",
+ "lfamily=%(LFamily)s",
+ "given=%(Given)s",
+ "lgiven=%(LGiven)s",
+ "middle=%(Middle)s",
+ "lmiddle=%(LMiddle)s",
+ "nickname=%(Nickname)s",
+ "lnickname=%(LNickname)s",
+ "bday=%(BDay)s",
+ "lbday=%(LBDay)s",
+ "ctry=%(CTRY)s",
+ "lctry=%(LCTRY)s",
+ "locality=%(Locality)s",
+ "llocality=%(LLocality)s",
+ "email=%(EMail)s",
+ "lemail=%(LEMail)s",
+ "orgname=%(OrgName)s",
+ "lorgname=%(LOrgName)s",
+ "orgunit=%(OrgUnit)s",
+ "lorgunit=%(LOrgUnit)s"])
+ end).
+
+get_vcard(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(vcard)s from vcard where username=%(LUser)s")).
+
+get_default_privacy_list(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(name)s from privacy_default_list "
+ "where username=%(LUser)s")).
+
+get_default_privacy_list_t(LUser) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(name)s from privacy_default_list "
+ "where username=%(LUser)s")).
+
+get_privacy_list_names(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(name)s from privacy_list"
+ " where username=%(LUser)s")).
+
+get_privacy_list_names_t(LUser) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(name)s from privacy_list"
+ " where username=%(LUser)s")).
+
+get_privacy_list_id(LServer, LUser, Name) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(id)d from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s")).
+
+get_privacy_list_id_t(LUser, Name) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(id)d from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s")).
+
+get_privacy_list_data(LServer, LUser, Name) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id ="
+ " (select id from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s) "
+ "order by ord")).
+
+%% Not used?
+get_privacy_list_data_t(LUser, Name) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id ="
+ " (select id from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s) "
+ "order by ord")).
+
+get_privacy_list_data_by_id(LServer, ID) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id=%(ID)d order by ord")).
+
+get_privacy_list_data_by_id_t(ID) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id=%(ID)d order by ord")).
+
+set_default_privacy_list(LUser, Name) ->
+ ?SQL_UPSERT_T(
+ "privacy_default_list",
+ ["!username=%(LUser)s",
+ "name=%(Name)s"]).
+
+unset_default_privacy_list(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from privacy_default_list"
+ " where username=%(LUser)s")).
+
+remove_privacy_list(LUser, Name) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from privacy_list where"
+ " username=%(LUser)s and name=%(Name)s")).
+
+add_privacy_list(LUser, Name) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("insert into privacy_list(username, name) "
+ "values (%(LUser)s, %(Name)s)")).
+
+set_privacy_list(ID, RItems) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from privacy_list_data where id=%(ID)d")),
+ lists:foreach(
+ fun({SType, SValue, SAction, Order, MatchAll, MatchIQ,
+ MatchMessage, MatchPresenceIn, MatchPresenceOut}) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("insert into privacy_list_data(id, t, "
+ "value, action, ord, match_all, match_iq, "
+ "match_message, match_presence_in, match_presence_out) "
+ "values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
+ " %(Order)d, %(MatchAll)b, %(MatchIQ)b,"
+ " %(MatchMessage)b, %(MatchPresenceIn)b,"
+ " %(MatchPresenceOut)b)"))
+ end,
+ RItems).
+
+del_privacy_lists(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from privacy_list where username=%(LUser)s")),
+ %US = <<LUser/binary, "@", LServer/binary>>,
+ %ejabberd_sql:sql_query(
+ % LServer,
+ % ?SQL("delete from privacy_list_data where value=%(US)s")),
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from privacy_default_list where username=%(LUser)s")).
+
+%% Characters to escape
+escape($\000) -> <<"\\0">>;
+escape($\n) -> <<"\\n">>;
+escape($\t) -> <<"\\t">>;
+escape($\b) -> <<"\\b">>;
+escape($\r) -> <<"\\r">>;
+escape($') -> <<"''">>;
+escape($") -> <<"\\\"">>;
+escape($\\) -> <<"\\\\">>;
+escape(C) -> <<C>>.
+
+%% Count number of records in a table given a where clause
+count_records_where(LServer, Table, WhereClause) ->
+ ejabberd_sql:sql_query(LServer,
+ [<<"select count(*) from ">>, Table, <<" ">>,
+ WhereClause, <<";">>]).
+
+get_roster_version(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(version)s from roster_version"
+ " where username = %(LUser)s")).
+
+set_roster_version(LUser, Version) ->
+ update_t(<<"roster_version">>,
+ [<<"username">>, <<"version">>], [LUser, Version],
+ [<<"username = '">>, LUser, <<"'">>]).
+
+opt_type(sql_type) ->
+ fun (pgsql) -> pgsql;
+ (mysql) -> mysql;
+ (sqlite) -> sqlite;
+ (mssql) -> mssql;
+ (odbc) -> odbc
+ end;
+opt_type(pgsql_users_number_estimate) ->
+ fun (V) when is_boolean(V) -> V end;
+opt_type(_) -> [sql_type, pgsql_users_number_estimate].