diff options
Diffstat (limited to 'src')
87 files changed, 5084 insertions, 2520 deletions
diff --git a/src/adhoc.erl b/src/adhoc.erl index d252a6cb..788bf65a 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 @@ -76,7 +76,7 @@ find_xdata_el(#xmlel{children = SubEls}) -> find_xdata_el1([]) -> false; find_xdata_el1([El | Els]) when is_record(El, xmlel) -> - case xml:get_tag_attr_s(<<"xmlns">>, El) of + case fxml:get_tag_attr_s(<<"xmlns">>, El) of ?NS_XDATA -> El; _ -> find_xdata_el1(Els) end; diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl index 5d2d89d6..cc03a49c 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 %% "" -> diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index 8ccd9095..976e49e8 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -83,9 +83,9 @@ 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), + %DigestURI = fxml:get_attr_s(<<"digest-uri">>, KeyVals), UserName = proplists:get_value(<<"username">>, KeyVals, <<>>), - %UserName = xml:get_attr_s(<<"username">>, KeyVals), + %UserName = fxml:get_attr_s(<<"username">>, KeyVals), case is_digesturi_valid(DigestURI, State#state.host, State#state.hostfqdn) of @@ -97,13 +97,13 @@ 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), + %AuthzId = fxml: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, <<"">>, proplists:get_value(<<"response">>, KeyVals, <<>>), - %xml:get_attr_s(<<"response">>, KeyVals), + %fxml:get_attr_s(<<"response">>, KeyVals), fun (PW) -> response(KeyVals, UserName, diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 4aa5faed..c9e5b178 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -201,6 +201,11 @@ get_commands_spec() -> module = ejabberd_auth_odbc, function = convert_to_scram, args = [{host, binary}], result = {res, rescode}}, + #ejabberd_commands{name = import_prosody, tags = [mnesia, odbc, riak], + desc = "Import data from Prosody", + module = prosody2ejabberd, function = from_dir, + args = [{dir, string}], result = {res, rescode}}, + #ejabberd_commands{name = convert_to_yaml, tags = [config], desc = "Convert the input file from Erlang to YAML format", module = ejabberd_config, function = convert_to_yaml, diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 628b4a8a..e493eac0 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -252,11 +252,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_external.erl b/src/ejabberd_auth_external.erl index 2a1cbf08..2944ac3f 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -222,7 +222,7 @@ check_password_cache(User, Server, Password, get_password_internal(User, Server) -> ejabberd_auth_internal:get_password(User, Server). -%% @spec (User, Server, CacheTime) -> false | Password::string() +-spec get_password_cache(User::binary(), Server::binary(), CacheTime::integer()) -> Password::string() | false. get_password_cache(User, Server, CacheTime) -> case get_last_access(User, Server) of online -> get_password_internal(User, Server); @@ -273,10 +273,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_odbc.erl b/src/ejabberd_auth_odbc.erl index b8b4594b..18d06201 100644 --- a/src/ejabberd_auth_odbc.erl +++ b/src/ejabberd_auth_odbc.erl @@ -72,22 +72,18 @@ check_password(User, Server, Password) -> (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 odbc_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 +92,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 odbc_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 @@ -124,10 +120,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 odbc_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 +130,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 @@ -158,26 +153,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( 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) + LUser, Password) of {atomic, ok} -> ok; Other -> {error, Other} @@ -194,26 +187,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( 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 odbc_queries:add_user(LServer, LUser, + Password) of {updated, 1} -> {atomic, ok}; _ -> {atomic, exists} end @@ -228,35 +218,51 @@ dirty_get_registered_users() -> 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 odbc_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 odbc_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 odbc_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 odbc_queries:users_number(LServer, Opts) of + {selected, [{Res}]} -> + Res; + _Other -> 0 + end end. get_password(User, Server) -> @@ -267,24 +273,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]]} -> + 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 odbc_queries:get_password(LServer, LUser) of - {selected, [<<"password">>], [[Password]]} -> Password; + {selected, [{Password}]} -> Password; _ -> false end end @@ -300,9 +304,8 @@ get_password_s(User, Server) -> true -> case is_scrammed() of false -> - Username = ejabberd_odbc:escape(LUser), - case catch odbc_queries:get_password(LServer, Username) of - {selected, [<<"password">>], [[Password]]} -> Password; + case catch odbc_queries:get_password(LServer, LUser) of + {selected, [{Password}]} -> Password; _ -> <<"">> end; true -> <<"">> @@ -311,15 +314,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 odbc_queries:get_password(LServer, LUser) of + {selected, [{_Password}]} -> true; %% Account exists - {selected, [<<"password">>], []} -> + {selected, []} -> false; %% Account does not exist {error, Error} -> {error, Error} catch @@ -331,12 +336,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 odbc_queries:del_user(LServer, LUser), ok end. @@ -359,16 +366,12 @@ remove_user(User, Server, Password) -> false -> not_allowed end; false -> - Username = ejabberd_odbc:escape(LUser), - Pass = ejabberd_odbc:escape(Password), F = fun () -> Result = odbc_queries:del_user_return_password( - LServer, Username, Pass), + LServer, LUser, Password), case Result of - {selected, [<<"password">>], - [[Password]]} -> ok; - {selected, [<<"password">>], - []} -> not_exists; + {selected, [{Password}]} -> ok; + {selected, []} -> not_exists; _ -> not_allowed end end, diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 04e41f46..a5802a5b 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>">>). @@ -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 @@ -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, @@ -724,19 +742,19 @@ 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 = fxml:get_attr_s(username, Props), U = proplists:get_value(username, Props, <<>>), - %AuthModule = xml:get_attr_s(auth_module, Props), + %AuthModule = fxml:get_attr_s(auth_module, Props), AuthModule = proplists:get_value(auth_module, Props, undefined), ?INFO_MSG("(~w) Accepted authentication for ~s " "by ~p from ~s", @@ -802,7 +820,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,39 +831,8 @@ wait_for_feature_request({xmlstreamelement, El}, tls_enabled = true}); {?NS_COMPRESS, <<"compress">>} when Zlib == true, - (SockMod == gen_tcp) or (SockMod == p1_tls) -> - case xml:get_subtag(El, <<"method">>) of - false -> - send_element(StateData, - #xmlel{name = <<"failure">>, - attrs = [{<<"xmlns">>, ?NS_COMPRESS}], - children = - [#xmlel{name = <<"setup-failed">>, - attrs = [], children = []}]}), - fsm_next_state(wait_for_feature_request, StateData); - Method -> - case xml:get_tag_cdata(Method) of - <<"zlib">> -> - Socket = StateData#state.socket, - BCompressed = xml:element_to_binary(#xmlel{name = <<"compressed">>, - attrs = [{<<"xmlns">>, ?NS_COMPRESS}]}), - ZlibSocket = (StateData#state.sockmod):compress(Socket, - BCompressed), - fsm_next_state(wait_for_stream, - StateData#state{socket = ZlibSocket, - streamid = new_id()}); - _ -> - send_element(StateData, - #xmlel{name = <<"failure">>, - attrs = [{<<"xmlns">>, ?NS_COMPRESS}], - children = - [#xmlel{name = - <<"unsupported-method">>, - attrs = [], - children = []}]}), - fsm_next_state(wait_for_feature_request, StateData) - end - end; + (SockMod == gen_tcp) or (SockMod == fast_tls) -> + process_compression_request(El, wait_for_feature_request, StateData); _ -> if TLSRequired and not TLSEnabled -> Lang = StateData#state.lang, @@ -880,18 +867,18 @@ 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 = fxml:get_attr_s(username, Props), U = proplists:get_value(username, Props, <<>>), -% AuthModule = xml:get_attr_s(auth_module, Props), +% AuthModule = fxml:get_attr_s(auth_module, Props), AuthModule = proplists:get_value(auth_module, Props, <<>>), ?INFO_MSG("(~w) Accepted authentication for ~s " "by ~p from ~s", @@ -912,9 +899,9 @@ 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 = fxml:get_attr_s(username, Props), U = proplists:get_value(username, Props, <<>>), -% AuthModule = xml:get_attr_s(auth_module, Props), +% AuthModule = fxml:get_attr_s(auth_module, Props), AuthModule = proplists:get_value(auth_module, Props, undefined), ?INFO_MSG("(~w) Accepted authentication for ~s " "by ~p from ~s", @@ -1012,7 +999,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. @@ -1035,12 +1022,11 @@ wait_for_bind({xmlstreamelement, El}, StateData) -> #iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} = IQ -> U = StateData#state.user, - R1 = xml:get_path_s(SubEl, + R1 = fxml:get_path_s(SubEl, [{elem, <<"resource">>}, cdata]), R = case 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 @@ -1088,7 +1074,19 @@ 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}; @@ -1207,24 +1205,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; _ -> @@ -1408,7 +1406,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 +1623,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; @@ -1737,6 +1735,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). @@ -1844,7 +1844,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 +1912,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 +1928,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 +1940,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 +1959,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 +2007,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 +2072,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 +2269,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 @@ -2310,7 +2314,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 +2335,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,21 +2352,21 @@ resend_subscription_requests(#state{user = User, get_showtag(undefined) -> <<"unavailable">>; get_showtag(Presence) -> - case xml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of + case fxml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of <<"">> -> <<"available">>; ShowTag -> ShowTag end. get_statustag(undefined) -> <<"">>; get_statustag(Presence) -> - xml:get_path_s(Presence, [{elem, <<"status">>}, cdata]). + fxml:get_path_s(Presence, [{elem, <<"status">>}, cdata]). process_unauthenticated_stanza(StateData, El) -> - NewEl = case xml:get_tag_attr_s(<<"xml:lang">>, El) of + NewEl = case fxml:get_tag_attr_s(<<"xml:lang">>, El) of <<"">> -> case StateData#state.lang of <<"">> -> El; - Lang -> xml:replace_tag_attr(<<"xml:lang">>, Lang, El) + Lang -> fxml:replace_tag_attr(<<"xml:lang">>, Lang, El) end; _ -> El end, @@ -2462,7 +2468,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 +2511,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 +2614,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 +2642,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 +2667,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 +2710,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 +2720,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 +2856,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,8 +2874,16 @@ handle_unacked_stanzas(StateData) Resend when is_boolean(Resend) -> Resend; if_offline -> - ejabberd_sm:get_user_resources(StateData#state.user, - StateData#state.server) == [] + Resource = StateData#state.resource, + case ejabberd_sm:get_user_resources(StateData#state.user, + StateData#state.server) of + [Resource] -> % Same resource opened new session + true; + [] -> + true; + _ -> + false + end end, ReRoute = case ResendOnTimeout of true -> @@ -2867,7 +2916,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 +2935,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 +2950,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 +3038,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 +3049,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. diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl index cc3e2e9f..157700c4 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_doc.erl b/src/ejabberd_commands_doc.erl index adcbc6d9..dc00c5d2 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,9 +79,9 @@ md_tag(pre, V) -> md_tag(p, V) -> [<<"\n\n">>, V, <<"\n">>]; md_tag(h1, V) -> - [<<"\n## ">>, V, <<"\n">>]; + [<<"\n\n## ">>, V, <<"\n">>]; md_tag(h2, V) -> - [<<"\n### ">>, V, <<"\n">>]; + [<<"\n\n### ">>, V, <<"\n">>]; md_tag(strong, V) -> [<<"*">>, V, <<"*">>]; md_tag(_, 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 8d2d1997..eb06b98f 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -112,7 +112,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 +162,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 +226,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; _ -> @@ -408,7 +408,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 -> diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl index 2bf20c2e..7a26644f 100644 --- a/src/ejabberd_ctl.erl +++ b/src/ejabberd_ctl.erl @@ -324,7 +324,7 @@ format_args(Args, ArgsFormat) -> format_arg(Arg, integer) -> format_arg2(Arg, "~d"); format_arg(Arg, binary) -> - list_to_binary(format_arg(Arg, string)); + unicode:characters_to_binary(Arg, utf8); format_arg("", string) -> ""; format_arg(Arg, string) -> @@ -349,7 +349,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) -> diff --git a/src/ejabberd_frontend_socket.erl b/src/ejabberd_frontend_socket.erl index 09eeded9..b8e706f2 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 b95f9820..d8d1ddd4 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) diff --git a/src/ejabberd_http_bind.erl b/src/ejabberd_http_bind.erl index c4cfdddd..ea8cd792 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 3b50e44a..e66cf33a 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 66f530c8..7c30f3b6 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, @@ -178,6 +178,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 +273,7 @@ do_route(From, To, Packet) -> end; true -> #xmlel{attrs = Attrs} = Packet, - case xml:get_attr_s(<<"type">>, Attrs) of + case fxml:get_attr_s(<<"type">>, Attrs) of <<"error">> -> ok; <<"result">> -> ok; _ -> diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl index 243864c7..795d4f39 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} -> @@ -74,68 +75,6 @@ opt_type(log_rate_limit) -> opt_type(_) -> [log_rotate_date, log_rotate_size, log_rotate_count, log_rate_limit]. -%% Default logger module is LAGER, defined in else clause. -%% TODO: Remove p1_logger usage and allow using Elixir logger if running in Elixir context. --ifdef(P1LOGGER). - -start() -> - set(4), - LogPath = get_log_path(), - error_logger:add_report_handler(p1_logger_h, LogPath), - ok. - -reopen_log() -> - %% TODO: Use the Reopen log API for logger_h ? - p1_logger_h:reopen_log(), - reopen_sasl_log(). - -rotate_log() -> - %% Not implemented. - ok. - -get() -> - p1_loglevel:get(). - -set(LogLevel) when LogLevel >=0, LogLevel =< 5 -> - p1_loglevel:set(LogLevel); -set(LogLevel) -> - throw({wrong_loglevel, LogLevel}). - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== -reopen_sasl_log() -> - case application:get_env(sasl,sasl_error_logger) of - {ok, {file, SASLfile}} -> - error_logger:delete_report_handler(sasl_report_file_h), - rotate_sasl_log(SASLfile), - error_logger:add_report_handler(sasl_report_file_h, - {SASLfile, get_sasl_error_logger_type()}); - _ -> false - end, - ok. - -rotate_sasl_log(Filename) -> - case file:read_file_info(Filename) of - {ok, _FileInfo} -> - file:rename(Filename, [Filename, ".0"]), - ok; - {error, _Reason} -> - ok - end. - -%% Function copied from Erlang/OTP lib/sasl/src/sasl.erl which doesn't export it -get_sasl_error_logger_type () -> - case application:get_env (sasl, errlog_type) of - {ok, error} -> error; - {ok, progress} -> progress; - {ok, all} -> all; - {ok, Bad} -> exit ({bad_config, {sasl, {errlog_type, Bad}}}); - _ -> all - end. - --else. - get_integer_env(Name, Default) -> case application:get_env(ejabberd, Name) of {ok, I} when is_integer(I), I>=0 -> @@ -161,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), @@ -188,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( @@ -201,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"}; @@ -214,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; @@ -224,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; _ -> @@ -234,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)) @@ -243,4 +214,21 @@ set({_LogLevel, _}) -> error_logger:error_msg("custom loglevels are not supported for 'lager'"), {module, lager}. --endif. +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 a688bb39..1925a2f7 100644 --- a/src/ejabberd_oauth.erl +++ b/src/ejabberd_oauth.erl @@ -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_odbc.erl b/src/ejabberd_odbc.erl index d7b8fa83..f756fdeb 100644 --- a/src/ejabberd_odbc.erl +++ b/src/ejabberd_odbc.erl @@ -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,10 +64,12 @@ -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(), @@ -92,6 +95,8 @@ -define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]). +-define(PREPARE_KEY, ejabberd_odbc_prepare). + %%-define(DBGFSM, true). -ifdef(DBGFSM). @@ -116,11 +121,13 @@ start_link(Host, StartInterval) -> [Host, StartInterval], fsm_limit_opts() ++ (?FSMOPTS)). --type sql_query() :: [sql_query() | binary()]. +-type sql_query() :: [sql_query() | binary()] | #sql_query{} | + fun(() -> any()) | fun((atom(), _) -> any()). -type sql_query_result() :: {updated, non_neg_integer()} | {error, binary()} | {selected, [binary()], - [[binary()]]}. + [[binary()]]} | + {selected, [any()]}. -spec sql_query(binary(), sql_query()) -> sql_query_result(). @@ -194,6 +201,13 @@ escape_like($%) -> <<"\\%">>; escape_like($_) -> <<"\\_">>; escape_like(C) when is_integer(C), C >= 0, C =< 255 -> odbc_queries:escape(C). +escape_like_arg(S) when is_binary(S) -> + << <<(escape_like_arg(C))/binary>> || <<C>> <= S >>; +escape_like_arg($%) -> <<"\\%">>; +escape_like_arg($_) -> <<"\\_">>; +escape_like_arg($\\) -> <<"\\\\">>; +escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>. + to_bool(<<"t">>) -> true; to_bool(<<"true">>) -> true; to_bool(<<"1">>) -> true; @@ -259,15 +273,16 @@ connecting(connect, #state{host = Host} = State) -> end, {_, PendingRequests} = State#state.pending_requests, case ConnectRes of - {ok, Ref} -> - erlang:monitor(process, Ref), - lists:foreach(fun (Req) -> - (?GEN_FSM):send_event(self(), Req) - end, - queue:to_list(PendingRequests)), - {next_state, session_established, - State#state{db_ref = Ref, - pending_requests = {0, queue:new()}}}; + {ok, Ref} -> + erlang:monitor(process, Ref), + lists:foreach(fun (Req) -> + (?GEN_FSM):send_event(self(), Req) + end, + queue:to_list(PendingRequests)), + State1 = State#state{db_ref = Ref, + pending_requests = {0, queue:new()}}, + State2 = get_db_version(State1), + {next_state, session_established, State2}; {error, Reason} -> ?INFO_MSG("~p connection failed:~n** Reason: ~p~n** " "Retry after: ~p seconds", @@ -469,6 +484,71 @@ execute_bloc(F) -> Res -> {atomic, Res} end. +execute_fun(F) when is_function(F, 0) -> + F(); +execute_fun(F) when is_function(F, 2) -> + State = get(?STATE_KEY), + F(State#state.db_type, State#state.db_version). + +sql_query_internal([{_, _} | _] = Queries) -> + State = get(?STATE_KEY), + case select_sql_query(Queries, State) of + undefined -> + {error, <<"no matching query for the current DBMS found">>}; + Query -> + sql_query_internal(Query) + end; +sql_query_internal(#sql_query{} = Query) -> + State = get(?STATE_KEY), + Res = + try + case State#state.db_type of + odbc -> + generic_sql_query(Query); + pgsql -> + Key = {?PREPARE_KEY, Query#sql_query.hash}, + case get(Key) of + undefined -> + case pgsql_prepare(Query, State) of + {ok, _, _, _} -> + put(Key, prepared); + {error, Error} -> + ?ERROR_MSG("PREPARE failed for SQL query " + "at ~p: ~p", + [Query#sql_query.loc, Error]), + put(Key, ignore) + end; + _ -> + ok + end, + case get(Key) of + prepared -> + pgsql_execute_sql_query(Query, State); + _ -> + generic_sql_query(Query) + end; + mysql -> + generic_sql_query(Query); + sqlite -> + generic_sql_query(Query) + end + catch + Class:Reason -> + ST = erlang:get_stacktrace(), + ?ERROR_MSG("Internal error while processing SQL query: ~p", + [{Class, Reason, ST}]), + {error, <<"internal error">>} + end, + case Res of + {error, <<"No SQL-driver information available.">>} -> + {updated, 0}; + _Else -> Res + end; +sql_query_internal(F) when is_function(F) -> + case catch execute_fun(F) of + {'EXIT', Reason} -> {error, Reason}; + Res -> Res + end; sql_query_internal(Query) -> State = get(?STATE_KEY), ?DEBUG("SQL: \"~s\"", [Query]), @@ -495,6 +575,93 @@ sql_query_internal(Query) -> _Else -> Res end. +select_sql_query(Queries, State) -> + select_sql_query( + Queries, State#state.db_type, State#state.db_version, undefined). + +select_sql_query([], _Type, _Version, undefined) -> + undefined; +select_sql_query([], _Type, _Version, Query) -> + Query; +select_sql_query([{any, Query} | _], _Type, _Version, _) -> + Query; +select_sql_query([{Type, Query} | _], Type, _Version, _) -> + Query; +select_sql_query([{{Type, _Version1}, Query1} | Rest], Type, undefined, _) -> + select_sql_query(Rest, Type, undefined, Query1); +select_sql_query([{{Type, Version1}, Query1} | Rest], Type, Version, Query) -> + if + Version >= Version1 -> + Query1; + true -> + select_sql_query(Rest, Type, Version, Query) + end; +select_sql_query([{_, _} | Rest], Type, Version, Query) -> + select_sql_query(Rest, Type, Version, Query). + +generic_sql_query(SQLQuery) -> + sql_query_format_res( + sql_query_internal(generic_sql_query_format(SQLQuery)), + SQLQuery). + +generic_sql_query_format(SQLQuery) -> + Args = (SQLQuery#sql_query.args)(generic_escape()), + (SQLQuery#sql_query.format_query)(Args). + +generic_escape() -> + #sql_escape{string = fun(X) -> <<"'", (escape(X))/binary, "'">> end, + integer = fun(X) -> integer_to_binary(X) end, + boolean = fun(true) -> <<"1">>; + (false) -> <<"0">> + end + }. + +pgsql_prepare(SQLQuery, State) -> + Escape = #sql_escape{_ = fun(X) -> X end}, + N = length((SQLQuery#sql_query.args)(Escape)), + Args = [<<$$, (integer_to_binary(I))/binary>> || I <- lists:seq(1, N)], + Query = (SQLQuery#sql_query.format_query)(Args), + pgsql:prepare(State#state.db_ref, SQLQuery#sql_query.hash, Query). + +pgsql_execute_escape() -> + #sql_escape{string = fun(X) -> X end, + integer = fun(X) -> [integer_to_binary(X)] end, + boolean = fun(true) -> "1"; + (false) -> "0" + end + }. + +pgsql_execute_sql_query(SQLQuery, State) -> + Args = (SQLQuery#sql_query.args)(pgsql_execute_escape()), + ExecuteRes = + pgsql:execute(State#state.db_ref, SQLQuery#sql_query.hash, Args), +% {T, ExecuteRes} = +% timer:tc(pgsql, execute, [State#state.db_ref, SQLQuery#sql_query.hash, Args]), +% io:format("T ~s ~p~n", [SQLQuery#sql_query.hash, T]), + Res = pgsql_execute_to_odbc(ExecuteRes), + sql_query_format_res(Res, SQLQuery). + + +sql_query_format_res({selected, _, Rows}, SQLQuery) -> + Res = + lists:flatmap( + fun(Row) -> + try + [(SQLQuery#sql_query.format_res)(Row)] + catch + Class:Reason -> + ST = erlang:get_stacktrace(), + ?ERROR_MSG("Error while processing " + "SQL query result: ~p~n" + "row: ~p", + [{Class, Reason, ST}, Row]), + [] + end + end, Rows), + {selected, Res}; +sql_query_format_res(Res, _SQLQuery) -> + Res. + %% Generate the OTP callback return tuple depending on the driver result. abort_on_driver_error({error, <<"query timed out">>} = Reply, @@ -606,6 +773,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 @@ -618,7 +797,7 @@ mysql_connect(Server, Port, DB, Username, Password) -> of {ok, Ref} -> p1_mysql_conn:fetch( - Ref, [<<"set names 'utf8';">>], self()), + Ref, [<<"set names 'utf8mb4' collate 'utf8mb4_bin';">>], self()), {ok, Ref}; Err -> Err end. @@ -658,6 +837,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); @@ -800,8 +997,17 @@ 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. diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl index 50c8e323..123189dd 100644 --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@ -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_receiver.erl b/src/ejabberd_receiver.erl index 78585529..0a33e30e 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_router.erl b/src/ejabberd_router.erl index 5ca1262e..e29d6acf 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 2f026b32..0eab4633 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,7 +308,7 @@ do_route(From, To, Packet) -> #xmlel{name = Name, attrs = NewAttrs, children = Els}), ok; {aborted, _Reason} -> - case xml:get_tag_attr_s(<<"type">>, Packet) of + case fxml:get_tag_attr_s(<<"type">>, Packet) of <<"error">> -> ok; <<"result">> -> ok; _ -> diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 29dd5e9d..c8d3cd04 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 -> @@ -331,7 +331,7 @@ wait_for_feature_request({xmlstreamelement, El}, end, TLSSocket = (StateData#state.sockmod):starttls(Socket, TLSOpts, - xml:element_to_binary(#xmlel{name + fxml:element_to_binary(#xmlel{name = <<"proceed">>, attrs @@ -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 69618573..594fbb2c 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}). @@ -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 a9b771b8..5caae610 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) @@ -356,7 +356,7 @@ 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 -> @@ -402,7 +402,7 @@ send_text(StateData, Text) -> Text). send_element(StateData, El) -> - send_text(StateData, xml:element_to_binary(El)). + send_text(StateData, fxml:element_to_binary(El)). new_id() -> randoms:get_string(). diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 3045a417..218e657f 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, @@ -133,10 +135,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} -> [] @@ -172,14 +174,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 +192,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 +207,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 +257,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 +266,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 +321,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 +388,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 +405,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", []); @@ -419,10 +427,10 @@ do_route(From, To, #xmlel{} = Packet) -> <<"">> -> case Name of <<"presence">> -> - {Pass, _Subsc} = case xml:get_attr_s(<<"type">>, Attrs) + {Pass, _Subsc} = case fxml:get_attr_s(<<"type">>, Attrs) of <<"subscribe">> -> - Reason = xml:get_path_s(Packet, + Reason = fxml:get_path_s(Packet, [{elem, <<"status">>}, cdata]), @@ -483,7 +491,7 @@ do_route(From, To, #xmlel{} = Packet) -> true -> ok end; <<"message">> -> - case xml:get_attr_s(<<"type">>, Attrs) of + case fxml:get_attr_s(<<"type">>, Attrs) of <<"chat">> -> route_message(From, To, Packet, chat); <<"headline">> -> route_message(From, To, Packet, headline); <<"error">> -> ok; @@ -498,12 +506,12 @@ 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); @@ -514,7 +522,7 @@ do_route(From, To, #xmlel{} = Packet) -> ejabberd_router:route(To, From, Err) end; <<"iq">> -> - case xml:get_attr_s(<<"type">>, Attrs) of + case fxml:get_attr_s(<<"type">>, Attrs) of <<"error">> -> ok; <<"result">> -> ok; _ -> @@ -565,7 +573,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 +655,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; @@ -703,17 +711,17 @@ process_iq(From, To, Packet) -> -spec force_update_presence({binary(), binary()}) -> any(). force_update_presence({LUser, LServer}) -> - Mod = get_sm_backend(), + Mod = get_sm_backend(LServer), Ss = Mod:get_sessions(LUser, LServer), lists:foreach(fun (#session{sid = {_, Pid}}) -> Pid ! {force_update_presence, LUser, 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; @@ -721,6 +729,19 @@ get_sm_backend() -> end, mnesia), list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)). +-spec get_sm_backends() -> [module()]. + +get_sm_backends() -> + lists:usort([get_sm_backend(Host) || Host <- ?MYHOSTS]). + +-spec get_vh_by_backend(module()) -> [binary()]. + +get_vh_by_backend(Mod) -> + lists:filter( + fun(Host) -> + get_sm_backend(Host) == Mod + end, ?MYHOSTS). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% ejabberd commands diff --git a/src/ejabberd_sm_odbc.erl b/src/ejabberd_sm_odbc.erl index debeafe9..698763b5 100644 --- a/src/ejabberd_sm_odbc.erl +++ b/src/ejabberd_sm_odbc.erl @@ -43,7 +43,7 @@ init() -> end; (_, Err) -> Err - end, ok, ?MYHOSTS). + end, ok, ejabberd_sm:get_vh_by_backend(?MODULE)). set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R}, priority = Priority, info = Info}) -> @@ -90,7 +90,7 @@ get_sessions() -> lists:flatmap( fun(LServer) -> get_sessions(LServer) - end, ?MYHOSTS). + end, ejabberd_sm:get_vh_by_backend(?MODULE)). get_sessions(LServer) -> case ejabberd_odbc:sql_query( diff --git a/src/ejabberd_sm_redis.erl b/src/ejabberd_sm_redis.erl index 637e1364..bf9e0eff 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_socket.erl b/src/ejabberd_socket.erl index 16ffd192..887b4a0f 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_sql_pt.erl b/src/ejabberd_sql_pt.erl new file mode 100644 index 00000000..660eac19 --- /dev/null +++ b/src/ejabberd_sql_pt.erl @@ -0,0 +1,544 @@ +%%%------------------------------------------------------------------- +%%% File : ejabberd_sql_pt.erl +%%% Author : Alexey Shchepin <alexey@process-one.net> +%%% Description : Parse transform for SQL queries +%%% +%%% Created : 20 Jan 2016 by Alexey Shchepin <alexey@process-one.net> +%%%------------------------------------------------------------------- +-module(ejabberd_sql_pt). + +%% API +-export([parse_transform/2]). + +-export([parse/2]). + +-include("ejabberd_sql_pt.hrl"). + +-record(state, {loc, + 'query' = [], + params = [], + param_pos = 0, + args = [], + res = [], + res_vars = [], + res_pos = 0}). + +-define(QUERY_RECORD, "sql_query"). + +-define(ESCAPE_RECORD, "sql_escape"). +-define(ESCAPE_VAR, "__SQLEscape"). + +-define(MOD, sql__module_). + +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% Function: +%% Description: +%%-------------------------------------------------------------------- +parse_transform(AST, _Options) -> + %io:format("PT: ~p~nOpts: ~p~n", [AST, Options]), + NewAST = top_transform(AST), + %io:format("NewPT: ~p~n", [NewAST]), + NewAST. + + + +%%==================================================================== +%% Internal functions +%%==================================================================== + + +transform(Form) -> + case erl_syntax:type(Form) of + application -> + case erl_syntax_lib:analyze_application(Form) of + {?SQL_MARK, 1} -> + case erl_syntax:application_arguments(Form) of + [Arg] -> + case erl_syntax:type(Arg) of + string -> + S = erl_syntax:string_value(Arg), + Pos = erl_syntax:get_pos(Arg), + ParseRes = parse(S, Pos), + set_pos(make_sql_query(ParseRes), Pos); + _ -> + throw({error, erl_syntax:get_pos(Form), + "?SQL argument must be " + "a constant string"}) + end; + _ -> + throw({error, erl_syntax:get_pos(Form), + "wrong number of ?SQL args"}) + end; + {?SQL_UPSERT_MARK, 2} -> + case erl_syntax:application_arguments(Form) of + [TableArg, FieldsArg] -> + case {erl_syntax:type(TableArg), + erl_syntax:is_proper_list(FieldsArg)}of + {string, true} -> + Table = erl_syntax:string_value(TableArg), + ParseRes = + parse_upsert( + erl_syntax:list_elements(FieldsArg)), + Pos = erl_syntax:get_pos(Form), + set_pos( + make_sql_upsert(Table, ParseRes, Pos), + Pos); + _ -> + throw({error, erl_syntax:get_pos(Form), + "?SQL_UPSERT arguments must be " + "a constant string and a list"}) + end; + _ -> + throw({error, erl_syntax:get_pos(Form), + "wrong number of ?SQL_UPSERT args"}) + end; + _ -> + Form + end; + attribute -> + case erl_syntax:atom_value(erl_syntax:attribute_name(Form)) of + module -> + case erl_syntax:attribute_arguments(Form) of + [M | _] -> + Module = erl_syntax:atom_value(M), + %io:format("module ~p~n", [Module]), + put(?MOD, Module), + Form; + _ -> + Form + end; + _ -> + Form + end; + _ -> + Form + end. + +top_transform(Forms) when is_list(Forms) -> + lists:map( + fun(Form) -> + try + Form2 = erl_syntax_lib:map( + fun(Node) -> + %io:format("asd ~p~n", [Node]), + transform(Node) + end, Form), + Form3 = erl_syntax:revert(Form2), + Form3 + catch + throw:{error, Line, Error} -> + {error, {Line, erl_parse, Error}} + end + end, Forms). + +parse(S, Loc) -> + parse1(S, [], #state{loc = Loc}). + +parse(S, ParamPos, Loc) -> + parse1(S, [], #state{loc = Loc, param_pos = ParamPos}). + +parse1([], Acc, State) -> + State1 = append_string(lists:reverse(Acc), State), + State1#state{'query' = lists:reverse(State1#state.'query'), + params = lists:reverse(State1#state.params), + args = lists:reverse(State1#state.args), + res = lists:reverse(State1#state.res), + res_vars = lists:reverse(State1#state.res_vars) + }; +parse1([$@, $( | S], Acc, State) -> + State1 = append_string(lists:reverse(Acc), State), + {Name, Type, S1, State2} = parse_name(S, State1), + Var = "__V" ++ integer_to_list(State2#state.res_pos), + EVar = erl_syntax:variable(Var), + Convert = + case Type of + integer -> + erl_syntax:application( + erl_syntax:atom(binary_to_integer), + [EVar]); + string -> + EVar; + boolean -> + erl_syntax:application( + erl_syntax:atom(ejabberd_odbc), + erl_syntax:atom(to_bool), + [EVar]) + end, + State3 = append_string(Name, State2), + State4 = State3#state{res_pos = State3#state.res_pos + 1, + res = [Convert | State3#state.res], + res_vars = [EVar | State3#state.res_vars]}, + parse1(S1, [], State4); +parse1([$%, $( | S], Acc, State) -> + State1 = append_string(lists:reverse(Acc), State), + {Name, Type, S1, State2} = parse_name(S, State1), + Var = State2#state.param_pos, + Convert = + erl_syntax:application( + erl_syntax:record_access( + erl_syntax:variable(?ESCAPE_VAR), + erl_syntax:atom(?ESCAPE_RECORD), + erl_syntax:atom(Type)), + [erl_syntax:variable(Name)]), + State3 = State2, + State4 = + State3#state{'query' = [{var, Var} | State3#state.'query'], + args = [Convert | State3#state.args], + params = [Var | State3#state.params], + param_pos = State3#state.param_pos + 1}, + parse1(S1, [], State4); +parse1([C | S], Acc, State) -> + parse1(S, [C | Acc], State). + +append_string([], State) -> + State; +append_string(S, State) -> + State#state{query = [{str, S} | State#state.query]}. + +parse_name(S, State) -> + parse_name(S, [], 0, State). + +parse_name([], _Acc, _Depth, State) -> + throw({error, State#state.loc, + "expected ')', found end of string"}); +parse_name([$), T | S], Acc, 0, State) -> + Type = + case T of + $d -> integer; + $s -> string; + $b -> boolean; + _ -> + throw({error, State#state.loc, + ["unknown type specifier '", T, "'"]}) + end, + {lists:reverse(Acc), Type, S, State}; +parse_name([$)], _Acc, 0, State) -> + throw({error, State#state.loc, + "expected type specifier, found end of string"}); +parse_name([$( = C | S], Acc, Depth, State) -> + parse_name(S, [C | Acc], Depth + 1, State); +parse_name([$) = C | S], Acc, Depth, State) -> + parse_name(S, [C | Acc], Depth - 1, State); +parse_name([C | S], Acc, Depth, State) -> + parse_name(S, [C | Acc], Depth, State). + + +make_var(V) -> + Var = "__V" ++ integer_to_list(V), + erl_syntax:variable(Var). + + +make_sql_query(State) -> + Hash = erlang:phash2(State#state{loc = undefined}), + SHash = <<"Q", (integer_to_binary(Hash))/binary>>, + Query = pack_query(State#state.'query'), + EQuery = + lists:map( + fun({str, S}) -> + erl_syntax:binary( + [erl_syntax:binary_field( + erl_syntax:string(S))]); + ({var, V}) -> make_var(V) + end, Query), + erl_syntax:record_expr( + erl_syntax:atom(?QUERY_RECORD), + [erl_syntax:record_field( + erl_syntax:atom(hash), + %erl_syntax:abstract(SHash) + erl_syntax:binary( + [erl_syntax:binary_field( + erl_syntax:string(binary_to_list(SHash)))])), + erl_syntax:record_field( + erl_syntax:atom(args), + erl_syntax:fun_expr( + [erl_syntax:clause( + [erl_syntax:variable(?ESCAPE_VAR)], + none, + [erl_syntax:list(State#state.args)] + )])), + erl_syntax:record_field( + erl_syntax:atom(format_query), + erl_syntax:fun_expr( + [erl_syntax:clause( + [erl_syntax:list(lists:map(fun make_var/1, State#state.params))], + none, + [erl_syntax:list(EQuery)] + )])), + erl_syntax:record_field( + erl_syntax:atom(format_res), + erl_syntax:fun_expr( + [erl_syntax:clause( + [erl_syntax:list(State#state.res_vars)], + none, + [erl_syntax:tuple(State#state.res)] + )])), + erl_syntax:record_field( + erl_syntax:atom(loc), + erl_syntax:abstract({get(?MOD), State#state.loc})) + ]). + +pack_query([]) -> + []; +pack_query([{str, S1}, {str, S2} | Rest]) -> + pack_query([{str, S1 ++ S2} | Rest]); +pack_query([X | Rest]) -> + [X | pack_query(Rest)]. + + +parse_upsert(Fields) -> + {Fs, _} = + lists:foldr( + fun(F, {Acc, Param}) -> + case erl_syntax:type(F) of + string -> + V = erl_syntax:string_value(F), + {_, _, State} = Res = + parse_upsert_field( + V, Param, erl_syntax:get_pos(F)), + {[Res | Acc], State#state.param_pos}; + _ -> + throw({error, erl_syntax:get_pos(F), + "?SQL_UPSERT field must be " + "a constant string"}) + end + end, {[], 0}, Fields), + %io:format("asd ~p~n", [{Fields, Fs}]), + Fs. + +parse_upsert_field([$! | S], ParamPos, Loc) -> + {Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc), + {Name, true, ParseState}; +parse_upsert_field(S, ParamPos, Loc) -> + {Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc), + {Name, false, ParseState}. + +parse_upsert_field1([], _Acc, _ParamPos, Loc) -> + throw({error, Loc, + "?SQL_UPSERT fields must have the " + "following form: \"[!]name=value\""}); +parse_upsert_field1([$= | S], Acc, ParamPos, Loc) -> + {lists:reverse(Acc), parse(S, ParamPos, Loc)}; +parse_upsert_field1([C | S], Acc, ParamPos, Loc) -> + parse_upsert_field1(S, [C | Acc], ParamPos, Loc). + + +make_sql_upsert(Table, ParseRes, Pos) -> + check_upsert(ParseRes, Pos), + erl_syntax:fun_expr( + [erl_syntax:clause( + [erl_syntax:atom(pgsql), erl_syntax:variable("__Version")], + [erl_syntax:infix_expr( + erl_syntax:variable("__Version"), + erl_syntax:operator('>='), + erl_syntax:integer(90100))], + [make_sql_upsert_pgsql901(Table, ParseRes), + erl_syntax:atom(ok)]), + erl_syntax:clause( + [erl_syntax:underscore(), erl_syntax:underscore()], + none, + [make_sql_upsert_generic(Table, ParseRes)]) + ]). + +make_sql_upsert_generic(Table, ParseRes) -> + Update = make_sql_query(make_sql_upsert_update(Table, ParseRes)), + Insert = make_sql_query(make_sql_upsert_insert(Table, ParseRes)), + InsertBranch = + erl_syntax:case_expr( + erl_syntax:application( + erl_syntax:atom(ejabberd_odbc), + erl_syntax:atom(sql_query_t), + [Insert]), + [erl_syntax:clause( + [erl_syntax:abstract({updated, 1})], + none, + [erl_syntax:atom(ok)]), + erl_syntax:clause( + [erl_syntax:variable("__UpdateRes")], + none, + [erl_syntax:variable("__UpdateRes")])]), + erl_syntax:case_expr( + erl_syntax:application( + erl_syntax:atom(ejabberd_odbc), + erl_syntax:atom(sql_query_t), + [Update]), + [erl_syntax:clause( + [erl_syntax:abstract({updated, 1})], + none, + [erl_syntax:atom(ok)]), + erl_syntax:clause( + [erl_syntax:underscore()], + none, + [InsertBranch])]). + +make_sql_upsert_update(Table, ParseRes) -> + WPairs = + lists:flatmap( + fun({_Field, false, _ST}) -> + []; + ({Field, true, ST}) -> + [ST#state{ + 'query' = [{str, Field}, {str, "="}] ++ ST#state.'query' + }] + end, ParseRes), + Where = join_states(WPairs, " AND "), + SPairs = + lists:flatmap( + fun({_Field, true, _ST}) -> + []; + ({Field, false, ST}) -> + [ST#state{ + 'query' = [{str, Field}, {str, "="}] ++ ST#state.'query' + }] + end, ParseRes), + Set = join_states(SPairs, ", "), + State = + concat_states( + [#state{'query' = [{str, "UPDATE "}, {str, Table}, {str, " SET "}]}, + Set, + #state{'query' = [{str, " WHERE "}]}, + Where + ]), + State. + +make_sql_upsert_insert(Table, ParseRes) -> + Vals = + lists:map( + fun({_Field, _, ST}) -> + ST + end, ParseRes), + Fields = + lists:map( + fun({Field, _, _ST}) -> + #state{'query' = [{str, Field}]} + end, ParseRes), + State = + concat_states( + [#state{'query' = [{str, "INSERT INTO "}, {str, Table}, {str, "("}]}, + join_states(Fields, ", "), + #state{'query' = [{str, ") VALUES ("}]}, + join_states(Vals, ", "), + #state{'query' = [{str, ")"}]} + ]), + State. + +make_sql_upsert_pgsql901(Table, ParseRes) -> + Update = make_sql_upsert_update(Table, ParseRes), + Vals = + lists:map( + fun({_Field, _, ST}) -> + ST + end, ParseRes), + Fields = + lists:map( + fun({Field, _, _ST}) -> + #state{'query' = [{str, Field}]} + end, ParseRes), + Insert = + concat_states( + [#state{'query' = [{str, "INSERT INTO "}, {str, Table}, {str, "("}]}, + join_states(Fields, ", "), + #state{'query' = [{str, ") SELECT "}]}, + join_states(Vals, ", "), + #state{'query' = [{str, " WHERE NOT EXISTS (SELECT * FROM upsert)"}]} + ]), + State = + concat_states( + [#state{'query' = [{str, "WITH upsert AS ("}]}, + Update, + #state{'query' = [{str, " RETURNING *) "}]}, + Insert + ]), + Upsert = make_sql_query(State), + erl_syntax:application( + erl_syntax:atom(ejabberd_odbc), + erl_syntax:atom(sql_query_t), + [Upsert]). + + +check_upsert(ParseRes, Pos) -> + Set = + lists:filter( + fun({_Field, Match, _ST}) -> + not Match + end, ParseRes), + case Set of + [] -> + throw({error, Pos, + "No ?SQL_UPSERT fields to set, use INSERT instead"}); + _ -> + ok + end, + ok. + + +concat_states(States) -> + lists:foldr( + fun(ST11, ST2) -> + ST1 = resolve_vars(ST11, ST2), + ST1#state{ + 'query' = ST1#state.'query' ++ ST2#state.'query', + params = ST1#state.params ++ ST2#state.params, + args = ST1#state.args ++ ST2#state.args, + res = ST1#state.res ++ ST2#state.res, + res_vars = ST1#state.res_vars ++ ST2#state.res_vars, + loc = case ST1#state.loc of + undefined -> ST2#state.loc; + _ -> ST1#state.loc + end + } + end, #state{}, States). + +resolve_vars(ST1, ST2) -> + Max = lists:max([0 | ST1#state.params ++ ST2#state.params]), + {Map, _} = + lists:foldl( + fun(Var, {Acc, New}) -> + case lists:member(Var, ST2#state.params) of + true -> + {dict:store(Var, New, Acc), New + 1}; + false -> + {Acc, New} + end + end, {dict:new(), Max + 1}, ST1#state.params), + NewParams = + lists:map( + fun(Var) -> + case dict:find(Var, Map) of + {ok, New} -> + New; + error -> + Var + end + end, ST1#state.params), + NewQuery = + lists:map( + fun({var, Var}) -> + case dict:find(Var, Map) of + {ok, New} -> + {var, New}; + error -> + {var, Var} + end; + (S) -> S + end, ST1#state.'query'), + ST1#state{params = NewParams, 'query' = NewQuery}. + + +join_states([], _Sep) -> + #state{}; +join_states([H | T], Sep) -> + J = [[H] | [[#state{'query' = [{str, Sep}]}, X] || X <- T]], + concat_states(lists:append(J)). + + +set_pos(Tree, Pos) -> + erl_syntax_lib:map( + fun(Node) -> + case erl_syntax:get_pos(Node) of + 0 -> erl_syntax:set_pos(Node, Pos); + _ -> Node + end + end, Tree). diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl index f7ae19cd..3c91117d 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 9a0213c1..3f6c0566 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 d711f2fc..11d90ab8 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -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 @@ -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 d9a1bafc..0cdd9bac 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 6b25adc4..c7e72d66 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/elixir_logger_backend.erl b/src/elixir_logger_backend.erl new file mode 100644 index 00000000..c055853f --- /dev/null +++ b/src/elixir_logger_backend.erl @@ -0,0 +1,122 @@ +%%%------------------------------------------------------------------- +%%% @author Mickael Remond <mremond@process-one.net> +%%% @doc +%%% This module bridges lager logs to Elixir Logger. +%%% @end +%%% Created : 9 March 2016 by Mickael Remond <mremond@process-one.net> +%%% +%%% ejabberd, Copyright (C) 2002-2016 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%------------------------------------------------------------------- + +-module(elixir_logger_backend). + +-behaviour(gen_event). + +-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, + code_change/3]). + +-record(state, {level = debug}). + +init(Opts) -> + Level = proplists:get_value(level, Opts, debug), + State = #state{level = Level}, + {ok, State}. + +%% @private +handle_event({log, LagerMsg}, State) -> + #{mode := Mode, truncate := Truncate, level := MinLevel, utc_log := UTCLog} = 'Elixir.Logger.Config':'__data__'(), + MsgLevel = severity_to_level(lager_msg:severity(LagerMsg)), + case {lager_util:is_loggable(LagerMsg, lager_util:level_to_num(State#state.level), ?MODULE), + 'Elixir.Logger':compare_levels(MsgLevel, MinLevel)} of + {_, lt}-> + {ok, State}; + {true, _} -> + Metadata = normalize_pid(lager_msg:metadata(LagerMsg)), + Message = 'Elixir.Logger.Utils':truncate(lager_msg:message(LagerMsg), Truncate), + Timestamp = timestamp(lager_msg:timestamp(LagerMsg), UTCLog), + GroupLeader = case proplists:get_value(pid, Metadata, self()) of + Pid when is_pid(Pid) -> + erlang:process_info(self(), group_leader); + _ -> {group_leader, self()} + end, + notify(Mode, {MsgLevel, GroupLeader, {'Elixir.Logger', Message, Timestamp, Metadata}}), + {ok, State}; + _ -> + {ok, State} + end; +handle_event(_Msg, State) -> + {ok, State}. + +%% @private +%% TODO Handle loglevels +handle_call(get_loglevel, State) -> + {ok, lager_util:config_to_mask(State#state.level), State}; +handle_call({set_loglevel, Config}, State) -> + {ok, ok, State#state{level = Config}}. + +%% @private +handle_info(_Msg, State) -> + {ok, State}. + +%% @private +terminate(_Reason, _State) -> + ok. + +%% @private +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +notify(sync, Msg) -> + gen_event:sync_notify('Elixir.Logger', Msg); +notify(async, Msg) -> + gen_event:notify('Elixir.Logger', Msg). + +normalize_pid(Metadata) -> + case proplists:get_value(pid, Metadata) of + Pid when is_pid(Pid) -> Metadata; + Pid when is_list(Pid) -> + M1 = proplists:delete(pid, Metadata), + case catch erlang:list_to_pid(Pid) of + {'EXIT', _} -> + M1; + PidAsPid -> + [{pid, PidAsPid}|M1] + end; + _ -> + proplists:delete(pid, Metadata) + end. + +%% Return timestamp with milliseconds +timestamp(Time, UTCLog) -> + {_, _, Micro} = p1_time_compat:timestamp(), + {Date, {Hours, Minutes, Seconds}} = + case UTCLog of + true -> calendar:now_to_universal_time(Time); + false -> calendar:now_to_local_time(Time) + end, + {Date, {Hours, Minutes, Seconds, Micro div 1000}}. + + +severity_to_level(debug) -> debug; +severity_to_level(info) -> info; +severity_to_level(notice) -> info; +severity_to_level(warning) -> warn; +severity_to_level(error) -> error; +severity_to_level(critical) -> error; +severity_to_level(alert) -> error; +severity_to_level(emergency) -> error. diff --git a/src/ext_mod.erl b/src/ext_mod.erl index 46ece873..91526e71 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/jd2ejd.erl b/src/jd2ejd.erl index cda8d11c..099387c9 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/jlib.erl b/src/jlib.erl index 61aaf690..8eaebbc8 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_admin_extra.erl b/src/mod_admin_extra.erl index dae775e4..343a1a3a 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -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, 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], @@ -794,7 +861,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 +980,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 +1001,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 +1031,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 +1194,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 +1274,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]), @@ -1333,7 +1401,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 +1412,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 +1426,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( diff --git a/src/mod_announce.erl b/src/mod_announce.erl index 3d0924a6..d30cf57f 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -845,7 +845,7 @@ announce_motd_update(LServer, Packet) -> packet = Packet}, motd_schema())}; odbc -> - XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)), + XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet)), F = fun() -> odbc_queries:update_t( <<"motd">>, @@ -953,7 +953,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>> case catch ejabberd_odbc:sql_query( LServer, [<<"select xml from motd where username='';">>]) of {selected, [<<"xml">>], [[XML]]} -> - case xml_stream:parse_element(XML) of + case fxml_stream:parse_element(XML) of {error, _} -> ok; Packet -> @@ -986,8 +986,8 @@ send_motd(_, odbc) -> get_stored_motd(LServer) -> case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of {ok, Packet} -> - {xml:get_subtag_cdata(Packet, <<"subject">>), - xml:get_subtag_cdata(Packet, <<"body">>)}; + {fxml:get_subtag_cdata(Packet, <<"subject">>), + fxml:get_subtag_cdata(Packet, <<"body">>)}; error -> {<<>>, <<>>} end. @@ -1010,7 +1010,7 @@ get_stored_motd_packet(LServer, odbc) -> case catch ejabberd_odbc:sql_query( LServer, [<<"select xml from motd where username='';">>]) of {selected, [<<"xml">>], [[XML]]} -> - case xml_stream:parse_element(XML) of + case fxml_stream:parse_element(XML) of {error, _} -> error; Packet -> @@ -1067,7 +1067,7 @@ update_motd_table() -> fun(#motd{server = S}) -> S end, fun(#motd{server = S, packet = P} = R) -> NewS = iolist_to_binary(S), - NewP = xml:to_xmlel(P), + NewP = fxml:to_xmlel(P), R#motd{server = NewS, packet = NewP} end); _ -> @@ -1105,7 +1105,7 @@ export(_Server) -> when LServer == Host -> [[<<"delete from motd where username='';">>], [<<"insert into motd(username, xml) values ('', '">>, - ejabberd_odbc:escape(xml:element_to_binary(El)), + ejabberd_odbc:escape(fxml:element_to_binary(El)), <<"');">>]]; (_Host, _R) -> [] @@ -1124,7 +1124,7 @@ export(_Server) -> import(LServer) -> [{<<"select xml from motd where username='';">>, fun([XML]) -> - El = xml_stream:parse_element(XML), + El = fxml_stream:parse_element(XML), #motd{server = LServer, packet = El} end}, {<<"select username from motd where xml='';">>, diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl index 4f9c8273..815884ff 100644 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@ -76,7 +76,7 @@ process_iq_set(_, From, _To, sub_el = #xmlel{name = SubElName, children = SubEls}}) -> #jid{luser = LUser, lserver = LServer} = From, - Res = case {SubElName, xml:remove_cdata(SubEls)} of + Res = case {SubElName, fxml:remove_cdata(SubEls)} of {<<"block">>, []} -> {error, ?ERR_BAD_REQUEST}; {<<"block">>, Els} -> JIDs = parse_blocklist_items(Els, []), @@ -116,7 +116,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]); @@ -223,23 +223,18 @@ process_blocklist_block(LUser, LServer, Filter, odbc) -> Default = case mod_privacy:sql_get_default_privacy_list_t(LUser) of - {selected, [<<"name">>], []} -> + {selected, []} -> Name = <<"Blocked contacts">>, mod_privacy:sql_add_privacy_list(LUser, Name), mod_privacy:sql_set_default_privacy_list(LUser, Name), Name; - {selected, [<<"name">>], [[Name]]} -> Name + {selected, [{Name}]} -> Name end, - {selected, [<<"id">>], [[ID]]} = + {selected, [{ID}]} = mod_privacy:sql_get_privacy_list_id_t(LUser, Default), - case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) - of - {selected, - [<<"t">>, <<"value">>, <<"action">>, <<"ord">>, - <<"match_all">>, <<"match_iq">>, <<"match_message">>, - <<"match_presence_in">>, <<"match_presence_out">>], - RItems = [_ | _]} -> + case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of + {selected, RItems = [_ | _]} -> List = lists:flatmap(fun mod_privacy:raw_to_item/1, RItems); _ -> List = [] end, @@ -345,17 +340,12 @@ unblock_by_filter(LUser, LServer, Filter, odbc) -> F = fun () -> case mod_privacy:sql_get_default_privacy_list_t(LUser) of - {selected, [<<"name">>], []} -> ok; - {selected, [<<"name">>], [[Default]]} -> - {selected, [<<"id">>], [[ID]]} = + {selected, []} -> ok; + {selected, [{Default}]} -> + {selected, [{ID}]} = mod_privacy:sql_get_privacy_list_id_t(LUser, Default), - case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) - of - {selected, - [<<"t">>, <<"value">>, <<"action">>, <<"ord">>, - <<"match_all">>, <<"match_iq">>, <<"match_message">>, - <<"match_presence_in">>, <<"match_presence_out">>], - RItems = [_ | _]} -> + case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of + {selected, RItems = [_ | _]} -> List = lists:flatmap(fun mod_privacy:raw_to_item/1, RItems), NewList = Filter(List), @@ -435,16 +425,12 @@ process_blocklist_get(LUser, LServer, odbc) -> case catch mod_privacy:sql_get_default_privacy_list(LUser, LServer) of - {selected, [<<"name">>], []} -> []; - {selected, [<<"name">>], [[Default]]} -> + {selected, []} -> []; + {selected, [{Default}]} -> case catch mod_privacy:sql_get_privacy_list_data(LUser, LServer, Default) of - {selected, - [<<"t">>, <<"value">>, <<"action">>, <<"ord">>, - <<"match_all">>, <<"match_iq">>, <<"match_message">>, - <<"match_presence_in">>, <<"match_presence_out">>], - RItems} -> + {selected, RItems} -> lists:flatmap(fun mod_privacy:raw_to_item/1, RItems); {'EXIT', _} -> error end; diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 7d764c4b..0646d381 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -120,12 +120,12 @@ read_caps(Els) -> read_caps(Els, nothing). read_caps([#xmlel{name = <<"c">>, attrs = Attrs} | Tail], Result) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_CAPS -> - Node = xml:get_attr_s(<<"node">>, Attrs), - Version = xml:get_attr_s(<<"ver">>, Attrs), - Hash = xml:get_attr_s(<<"hash">>, Attrs), - Exts = str:tokens(xml:get_attr_s(<<"ext">>, Attrs), + Node = fxml:get_attr_s(<<"node">>, Attrs), + Version = fxml:get_attr_s(<<"ver">>, Attrs), + Hash = fxml:get_attr_s(<<"hash">>, Attrs), + Exts = str:tokens(fxml:get_attr_s(<<"ext">>, Attrs), <<" ">>), read_caps(Tail, #caps{node = Node, hash = Hash, version = Version, @@ -135,7 +135,7 @@ read_caps([#xmlel{name = <<"c">>, attrs = Attrs} read_caps([#xmlel{name = <<"x">>, attrs = Attrs} | Tail], Result) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_MUC_USER -> nothing; _ -> read_caps(Tail, Result) end; @@ -149,7 +149,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 +167,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 +227,7 @@ disco_info(Acc, Host, Module, Node, Lang) -> c2s_presence_in(C2SState, {From, To, {_, _, Attrs, Els}}) -> - Type = xml:get_attr_s(<<"type">>, Attrs), + Type = fxml:get_attr_s(<<"type">>, Attrs), Subscription = ejabberd_c2s:get_subscription(From, C2SState), Insert = ((Type == <<"">>) or (Type == <<"available">>)) @@ -434,7 +434,7 @@ feature_response(#iq{type = result, Features = lists:flatmap(fun (#xmlel{name = <<"feature">>, attrs = FAttrs}) -> - [xml:get_attr_s(<<"var">>, FAttrs)]; + [fxml:get_attr_s(<<"var">>, FAttrs)]; (_) -> [] end, Els), @@ -567,7 +567,7 @@ concat_features(Els) -> lists:usort(lists:flatmap(fun (#xmlel{name = <<"feature">>, attrs = Attrs}) -> - [[xml:get_attr_s(<<"var">>, Attrs), $<]]; + [[fxml:get_attr_s(<<"var">>, Attrs), $<]]; (_) -> [] end, Els)). @@ -576,11 +576,11 @@ concat_identities(Els) -> lists:sort(lists:flatmap(fun (#xmlel{name = <<"identity">>, attrs = Attrs}) -> - [[xml:get_attr_s(<<"category">>, Attrs), - $/, xml:get_attr_s(<<"type">>, Attrs), + [[fxml:get_attr_s(<<"category">>, Attrs), + $/, fxml:get_attr_s(<<"type">>, Attrs), $/, - xml:get_attr_s(<<"xml:lang">>, Attrs), - $/, xml:get_attr_s(<<"name">>, Attrs), + fxml:get_attr_s(<<"xml:lang">>, Attrs), + $/, fxml:get_attr_s(<<"name">>, Attrs), $<]]; (_) -> [] end, @@ -589,8 +589,8 @@ concat_identities(Els) -> concat_info(Els) -> lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>, attrs = Attrs, children = Fields}) -> - case {xml:get_attr_s(<<"xmlns">>, Attrs), - xml:get_attr_s(<<"type">>, Attrs)} + case {fxml:get_attr_s(<<"xmlns">>, Attrs), + fxml:get_attr_s(<<"type">>, Attrs)} of {?NS_XDATA, <<"result">>} -> [concat_xdata_fields(Fields)]; @@ -606,10 +606,10 @@ concat_xdata_fields(Fields) -> attrs = Attrs, children = Els} = El, [FormType, VarFields] = Acc) -> - case xml:get_attr_s(<<"var">>, Attrs) of + case fxml:get_attr_s(<<"var">>, Attrs) of <<"">> -> Acc; <<"FORM_TYPE">> -> - [xml:get_subtag_cdata(El, + [fxml:get_subtag_cdata(El, <<"value">>), VarFields]; Var -> @@ -622,7 +622,7 @@ concat_xdata_fields(Fields) -> children = VEls}) -> - [[xml:get_cdata(VEls), + [[fxml:get_cdata(VEls), $<]]; (_) -> [] diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl index a4ca45dd..81cf78a9 100644 --- a/src/mod_carboncopy.erl +++ b/src/mod_carboncopy.erl @@ -55,9 +55,9 @@ is_carbon_copy(Packet) -> is_carbon_copy(Packet, <<"received">>). is_carbon_copy(Packet, Direction) -> - case xml:get_subtag(Packet, Direction) of + case fxml:get_subtag(Packet, Direction) of #xmlel{name = Direction, attrs = Attrs} -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_CARBONS_2 -> true; ?NS_CARBONS_1 -> true; _ -> false @@ -137,8 +137,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 -> @@ -268,7 +268,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,7 +282,7 @@ 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) -> diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl index 3c046d77..dfbfc028 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 0c7e7526..5e011704 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -350,7 +350,7 @@ adhoc_local_items(Acc, From, Nodes = recursively_get_local_items(PermLev, LServer, <<"">>, Server, Lang), Nodes1 = lists:filter(fun (N) -> - Nd = xml:get_tag_attr_s(<<"node">>, N), + Nd = fxml:get_tag_attr_s(<<"node">>, N), F = get_local_features([], From, To, Nd, Lang), case F of @@ -379,9 +379,9 @@ recursively_get_local_items(PermLev, LServer, Node, {error, _Error} -> [] end, Nodes = lists:flatten(lists:map(fun (N) -> - S = xml:get_tag_attr_s(<<"jid">>, + S = fxml:get_tag_attr_s(<<"jid">>, N), - Nd = xml:get_tag_attr_s(<<"node">>, + Nd = fxml:get_tag_attr_s(<<"node">>, N), if (S /= Server) or (Nd == <<"">>) -> diff --git a/src/mod_configure2.erl b/src/mod_configure2.erl index 3e00a9cf..0acd4a78 100644 --- a/src/mod_configure2.erl +++ b/src/mod_configure2.erl @@ -65,7 +65,7 @@ process_local_iq(From, To, set -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}; - %%case xml:get_tag_attr_s("type", SubEl) of + %%case fxml:get_tag_attr_s("type", SubEl) of %% "cancel" -> %% IQ#iq{type = result, %% sub_el = [{xmlelement, "query", @@ -79,7 +79,7 @@ process_local_iq(From, To, %% _ -> %% Node = %% string:tokens( - %% xml:get_tag_attr_s("node", SubEl), + %% fxml:get_tag_attr_s("node", SubEl), %% "/"), %% case set_form(Node, Lang, XData) of %% {result, Res} -> diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 6e0b9c92..734e90d3 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -152,7 +152,7 @@ process_local_iq_items(From, To, set -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; get -> - Node = xml:get_tag_attr_s(<<"node">>, SubEl), + Node = fxml:get_tag_attr_s(<<"node">>, SubEl), Host = To#jid.lserver, case ejabberd_hooks:run_fold(disco_local_items, Host, empty, [From, To, Node, Lang]) @@ -180,7 +180,7 @@ process_local_iq_info(From, To, IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; get -> Host = To#jid.lserver, - Node = xml:get_tag_attr_s(<<"node">>, SubEl), + Node = fxml:get_tag_attr_s(<<"node">>, SubEl), Identity = ejabberd_hooks:run_fold(disco_local_identity, Host, [], [From, To, Node, Lang]), Info = ejabberd_hooks:run_fold(disco_info, Host, [], @@ -305,7 +305,7 @@ process_sm_iq_items(From, To, case is_presence_subscribed(From, To) of true -> Host = To#jid.lserver, - Node = xml:get_tag_attr_s(<<"node">>, SubEl), + Node = fxml:get_tag_attr_s(<<"node">>, SubEl), case ejabberd_hooks:run_fold(disco_sm_items, Host, empty, [From, To, Node, Lang]) of @@ -378,10 +378,12 @@ process_sm_iq_info(From, To, case is_presence_subscribed(From, To) of true -> Host = To#jid.lserver, - Node = xml:get_tag_attr_s(<<"node">>, SubEl), + Node = fxml:get_tag_attr_s(<<"node">>, SubEl), Identity = ejabberd_hooks:run_fold(disco_sm_identity, Host, [], [From, To, Node, Lang]), + Info = ejabberd_hooks:run_fold(disco_info, Host, [], + [From, To, Node, Lang]), case ejabberd_hooks:run_fold(disco_sm_features, Host, empty, [From, To, Node, Lang]) of @@ -397,7 +399,7 @@ process_sm_iq_info(From, To, [{<<"xmlns">>, ?NS_DISCO_INFO} | ANode], children = - Identity ++ + Identity ++ Info ++ features_to_xml(Features)}]}; {error, Error} -> IQ#iq{type = error, sub_el = [SubEl, Error]} diff --git a/src/mod_echo.erl b/src/mod_echo.erl index 3f91f1d0..7184ee4e 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}}. %%-------------------------------------------------------------------- @@ -182,7 +182,7 @@ do_client_version(enabled, From, To) -> Els = receive {route, To, From2, IQ} -> #xmlel{name = <<"query">>, children = List} = - xml:get_subtag(IQ, <<"query">>), + fxml:get_subtag(IQ, <<"query">>), List after 5000 -> % Timeout in miliseconds: 5 seconds [] diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl index 36d3626b..6c029c43 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}}. @@ -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) @@ -572,12 +582,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 -> @@ -737,20 +747,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 +789,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 +827,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 +1005,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 db0b4aa4..a5ae0c3c 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()}. diff --git a/src/mod_irc.erl b/src/mod_irc.erl index 18308895..d5cd0135 100644 --- a/src/mod_irc.erl +++ b/src/mod_irc.erl @@ -116,7 +116,7 @@ stop(Host) -> %% Description: Initiates the server %%-------------------------------------------------------------------- init([Host, Opts]) -> - ejabberd:start_app(p1_iconv), + ejabberd:start_app(iconv), MyHost = gen_mod:get_opt_host(Host, Opts, <<"irc.@HOST@">>), case gen_mod:db_type(Host, Opts) of @@ -133,7 +133,7 @@ init([Host, Opts]) -> catch ets:new(irc_connection, [named_table, public, {keypos, #irc_connection.jid_server_host}]), - ejabberd_router:register_route(MyHost), + ejabberd_router:register_route(MyHost, Host), {ok, #state{host = MyHost, server_host = Host, access = Access}}. @@ -216,7 +216,7 @@ do_route(Host, ServerHost, Access, From, To, Packet) -> allow -> do_route1(Host, ServerHost, From, To, Packet); _ -> #xmlel{attrs = Attrs} = Packet, - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), ErrText = <<"Access denied by service policy">>, Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), @@ -234,7 +234,7 @@ do_route1(Host, ServerHost, From, To, Packet) -> #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS, sub_el = SubEl, lang = Lang} = IQ -> - Node = xml:get_tag_attr_s(<<"node">>, SubEl), + Node = fxml:get_tag_attr_s(<<"node">>, SubEl), Info = ejabberd_hooks:run_fold(disco_info, ServerHost, [], [ServerHost, ?MODULE, @@ -262,7 +262,7 @@ do_route1(Host, ServerHost, From, To, Packet) -> #iq{type = get, xmlns = (?NS_DISCO_ITEMS) = XMLNS, sub_el = SubEl, lang = Lang} = IQ -> - Node = xml:get_tag_attr_s(<<"node">>, SubEl), + Node = fxml:get_tag_attr_s(<<"node">>, SubEl), case Node of <<>> -> ResIQ = IQ#iq{type = result, @@ -516,7 +516,7 @@ find_xdata_el1([]) -> false; find_xdata_el1([#xmlel{name = Name, attrs = Attrs, children = SubEls} | Els]) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_XDATA -> #xmlel{name = Name, attrs = Attrs, children = SubEls}; _ -> find_xdata_el1(Els) @@ -535,7 +535,7 @@ process_irc_register(ServerHost, Host, From, _To, IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; #xmlel{attrs = Attrs} -> - case xml:get_attr_s(<<"type">>, Attrs) of + case fxml:get_attr_s(<<"type">>, Attrs) of <<"cancel">> -> IQ#iq{type = result, sub_el = @@ -549,7 +549,7 @@ process_irc_register(ServerHost, Host, From, _To, IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}; _ -> - Node = str:tokens(xml:get_tag_attr_s(<<"node">>, + Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl), <<"/">>), case set_form(ServerHost, Host, From, Node, Lang, @@ -571,7 +571,7 @@ process_irc_register(ServerHost, Host, From, _To, end end; get -> - Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl), + Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl), <<"/">>), case get_form(ServerHost, Host, From, Node, Lang) of {result, Res} -> @@ -1368,7 +1368,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 +1376,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 +1384,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 ae709dc2..098c8c28 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_last.erl b/src/mod_last.erl index d76b6049..33f88e02 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -185,18 +185,12 @@ get_last(LUser, LServer, riak) -> Err end; get_last(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case catch odbc_queries:get_last(LServer, Username) of - {selected, [<<"seconds">>, <<"state">>], []} -> - not_found; - {selected, [<<"seconds">>, <<"state">>], - [[STimeStamp, Status]]} -> - case catch jlib:binary_to_integer(STimeStamp) of - TimeStamp when is_integer(TimeStamp) -> - {ok, TimeStamp, Status}; - Reason -> {error, {invalid_timestamp, Reason}} - end; - Reason -> {error, {invalid_result, Reason}} + case catch odbc_queries:get_last(LServer, LUser) of + {selected, []} -> + not_found; + {selected, [{TimeStamp, Status}]} -> + {ok, TimeStamp, Status}; + Reason -> {error, {invalid_result, Reason}} end. get_last_iq(IQ, SubEl, LUser, LServer) -> @@ -260,12 +254,7 @@ store_last_info(LUser, LServer, TimeStamp, Status, last_activity_schema())}; store_last_info(LUser, LServer, TimeStamp, Status, odbc) -> - Username = ejabberd_odbc:escape(LUser), - Seconds = - ejabberd_odbc:escape(iolist_to_binary(integer_to_list(TimeStamp))), - State = ejabberd_odbc:escape(Status), - odbc_queries:set_last_t(LServer, Username, Seconds, - State). + odbc_queries:set_last_t(LServer, LUser, TimeStamp, Status). %% @spec (LUser::string(), LServer::string()) -> %% {ok, TimeStamp::integer(), Status::string()} | not_found @@ -286,8 +275,7 @@ remove_user(LUser, LServer, mnesia) -> F = fun () -> mnesia:delete({last_activity, US}) end, mnesia:transaction(F); remove_user(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - odbc_queries:del_last(LServer, Username); + odbc_queries:del_last(LServer, LUser); remove_user(LUser, LServer, riak) -> {atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}. diff --git a/src/mod_mam.erl b/src/mod_mam.erl index d6e4e936..38642c0c 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -287,7 +287,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 +297,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 +310,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{}} -> @@ -407,7 +407,7 @@ delete_old_messages(_TimeStamp, _Type, _Host, _DBType) -> %%%=================================================================== 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; _ -> @@ -448,7 +448,7 @@ process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) -> 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 + try {case fxml:get_tag_attr_s(<<"default">>, SubEl) of <<"always">> -> always; <<"never">> -> never; <<"roster">> -> roster @@ -461,11 +461,13 @@ 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 -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} @@ -477,21 +479,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 +516,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 +537,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 +551,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 +567,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 +585,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 +601,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 +639,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 +647,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 +677,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,6 +703,12 @@ 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 @@ -766,8 +761,8 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, odbc) -> jid:remove_resource(Peer))), LPeer = jid:to_string( jid:tolower(Peer)), - XML = xml:element_to_binary(Pkt), - Body = xml:get_subtag_cdata(Pkt, <<"body">>), + XML = fxml:element_to_binary(Pkt), + Body = fxml:get_subtag_cdata(Pkt, <<"body">>), case ejabberd_odbc:sql_query( LServer, [<<"insert into archive (username, timestamp, " @@ -871,6 +866,22 @@ get_prefs(LUser, LServer, odbc) -> error end. +prefs_el(Default, Always, Never, NS) -> + Default1 = jlib:atom_to_binary(Default), + JFun = fun(L) -> + [#xmlel{name = <<"jid">>, + children = [{xmlcdata, jid:to_string(J)}]} + || J <- L] + end, + Always1 = #xmlel{name = <<"always">>, + children = JFun(Always)}, + Never1 = #xmlel{name = <<"never">>, + children = JFun(Never)}, + #xmlel{name = <<"prefs">>, + attrs = [{<<"xmlns">>, NS}, + {<<"default">>, Default1}], + children = [Always1, Never1]}. + maybe_activate_mam(LUser, LServer) -> ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE, request_activates_archiving, @@ -916,12 +927,12 @@ select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType, DBType) - select_and_start(LServer, From, To, Start, End, With, RSM, MsgType, DBType) -> case MsgType of chat -> - select(LServer, From, Start, End, With, RSM, MsgType, DBType); + select(LServer, From, From, Start, End, With, RSM, MsgType, DBType); {groupchat, _Role, _MUCState} -> - select(LServer, To, Start, End, With, RSM, MsgType, DBType) + select(LServer, From, To, Start, End, With, RSM, MsgType, DBType) 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) -> @@ -941,8 +952,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,7 +969,8 @@ select(_LServer, JidRequestor, Start, End, _With, RSM, _ -> {Msgs, true, L} end; -select(_LServer, #jid{luser = LUser, lserver = LServer} = JidRequestor, +select(_LServer, JidRequestor, + #jid{luser = LUser, lserver = LServer} = JidArchive, Start, End, With, RSM, MsgType, mnesia) -> MS = make_matchspec(LUser, LServer, Start, End, With), Msgs = mnesia:dirty_select(archive_msg, MS), @@ -969,13 +981,13 @@ select(_LServer, #jid{luser = LUser, lserver = LServer} = JidRequestor, fun(Msg) -> {Msg#archive_msg.id, jlib:binary_to_integer(Msg#archive_msg.id), - msg_to_el(Msg, MsgType, JidRequestor)} + msg_to_el(Msg, MsgType, JidRequestor, JidArchive)} end, FilteredMsgs), IsComplete, Count}; -select(LServer, #jid{luser = LUser} = JidRequestor, +select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, Start, End, With, RSM, MsgType, {odbc, Host}) -> User = case MsgType of chat -> LUser; - {groupchat, _Role, _MUCState} -> jid:to_string(JidRequestor) + {groupchat, _Role, _MUCState} -> jid:to_string(JidArchive) end, {Query, CountQuery} = make_sql_query(User, LServer, Start, End, With, RSM), @@ -1005,7 +1017,7 @@ select(LServer, #jid{luser = LUser} = JidRequestor, {lists:flatmap( fun([TS, XML, PeerBin, Kind, Nick]) -> try - #xmlel{} = El = xml_stream:parse_element(XML), + #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 @@ -1019,8 +1031,7 @@ select(LServer, #jid{luser = LUser} = JidRequestor, type = T, nick = Nick, peer = PeerJid}, - MsgType, - JidRequestor)}] + MsgType, JidRequestor, JidArchive)}] catch _:Err -> ?ERROR_MSG("failed to parse data from SQL: ~p. " "The data was: " @@ -1035,31 +1046,44 @@ select(LServer, #jid{luser = LUser} = JidRequestor, end. 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 +1119,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 -> @@ -1336,7 +1360,7 @@ 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)]; (_) -> diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl index 3e64da95..c175fcb8 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 00000000..d8cf94ac --- /dev/null +++ b/src/mod_mix.erl @@ -0,0 +1,347 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net> +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 2 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net> +%%%------------------------------------------------------------------- +-module(mod_mix). + +-behaviour(gen_server). +-behaviour(gen_mod). + +%% API +-export([start_link/2, start/2, stop/1, process_iq/3, + disco_items/5, disco_identity/5, disco_info/5, + disco_features/5, mod_opt_type/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-include("logger.hrl"). +-include("jlib.hrl"). +-include("pubsub.hrl"). + +-define(PROCNAME, ejabberd_mod_mix). +-define(NODES, [?NS_MIX_NODES_MESSAGES, + ?NS_MIX_NODES_PRESENCE, + ?NS_MIX_NODES_PARTICIPANTS, + ?NS_MIX_NODES_SUBJECT, + ?NS_MIX_NODES_CONFIG]). + +-record(state, {server_host :: binary(), + host :: binary()}). + +%%%=================================================================== +%%% API +%%%=================================================================== +start_link(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). + +start(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, + temporary, 5000, worker, [?MODULE]}, + supervisor:start_child(ejabberd_sup, ChildSpec). + +stop(Host) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + supervisor:terminate_child(ejabberd_sup, Proc), + supervisor:delete_child(ejabberd_sup, Proc), + ok. + +disco_features(_Acc, _From, _To, _Node, _Lang) -> + {result, [?NS_MIX_0]}. + +disco_items(_Acc, _From, To, _Node, _Lang) when To#jid.luser /= <<"">> -> + To_s = jid:to_string(jid:remove_resource(To)), + {result, [#xmlel{name = <<"item">>, + attrs = [{<<"jid">>, To_s}, + {<<"node">>, Node}]} || Node <- ?NODES]}; +disco_items(_Acc, _From, _To, _Node, _Lang) -> + {result, []}. + +disco_identity(Acc, _From, To, _Node, _Lang) when To#jid.luser == <<"">> -> + Acc ++ [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"conference">>}, + {<<"name">>, <<"MIX service">>}, + {<<"type">>, <<"text">>}]}]; +disco_identity(Acc, _From, _To, _Node, _Lang) -> + Acc ++ [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"conference">>}, + {<<"type">>, <<"mix">>}]}]. + +disco_info(_Acc, _From, To, _Node, _Lang) when is_atom(To) -> + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}, + {<<"type">>, <<"result">>}], + children = [#xmlel{name = <<"field">>, + attrs = [{<<"var">>, <<"FORM_TYPE">>}, + {<<"type">>, <<"hidden">>}], + children = [#xmlel{name = <<"value">>, + children = [{xmlcdata, + ?NS_MIX_SERVICEINFO_0}]}]}]}]; +disco_info(Acc, _From, _To, _Node, _Lang) -> + Acc. + +process_iq(From, To, + #iq{type = set, sub_el = #xmlel{name = <<"join">>} = SubEl} = IQ) -> + Nodes = lists:flatmap( + fun(#xmlel{name = <<"subscribe">>, attrs = Attrs}) -> + Node = fxml:get_attr_s(<<"node">>, Attrs), + case lists:member(Node, ?NODES) of + true -> [Node]; + false -> [] + end; + (_) -> + [] + end, SubEl#xmlel.children), + case subscribe_nodes(From, To, Nodes) of + {result, _} -> + case publish_participant(From, To) of + {result, _} -> + LFrom_s = jid:to_string(jid:tolower(jid:remove_resource(From))), + Subscribe = [#xmlel{name = <<"subscribe">>, + attrs = [{<<"node">>, Node}]} || Node <- Nodes], + IQ#iq{type = result, + sub_el = [#xmlel{name = <<"join">>, + attrs = [{<<"jid">>, LFrom_s}, + {<<"xmlns">>, ?NS_MIX_0}], + children = Subscribe}]}; + {error, Err} -> + IQ#iq{type = error, sub_el = [SubEl, Err]} + end; + {error, Err} -> + IQ#iq{type = error, sub_el = [SubEl, Err]} + end; +process_iq(From, To, + #iq{type = set, sub_el = #xmlel{name = <<"leave">>} = SubEl} = IQ) -> + case delete_participant(From, To) of + {result, _} -> + case unsubscribe_nodes(From, To, ?NODES) of + {result, _} -> + IQ#iq{type = result, sub_el = []}; + {error, Err} -> + IQ#iq{type = error, sub_el = [SubEl, Err]} + end; + {error, Err} -> + IQ#iq{type = error, sub_el = [SubEl, Err]} + end; +process_iq(_From, _To, #iq{sub_el = SubEl} = IQ) -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== +init([ServerHost, Opts]) -> + Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>), + IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, + one_queue), + ConfigTab = gen_mod:get_module_proc(Host, config), + ets:new(ConfigTab, [named_table]), + ets:insert(ConfigTab, {plugins, [<<"mix">>]}), + ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100), + ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100), + ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100), + ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100), + ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100), + ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100), + ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, + ?NS_DISCO_ITEMS, mod_disco, + process_local_iq_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, + ?NS_DISCO_INFO, mod_disco, + process_local_iq_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_DISCO_ITEMS, mod_disco, + process_local_iq_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_DISCO_INFO, mod_disco, + process_local_iq_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_MIX_0, ?MODULE, process_iq, IQDisc), + ejabberd_router:register_route(Host, ServerHost), + {ok, #state{server_host = ServerHost, host = Host}}. + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({route, From, To, Packet}, State) -> + case catch do_route(State, From, To, Packet) of + {'EXIT', _} = Err -> + try + ?ERROR_MSG("failed to route packet ~p from '~s' to '~s': ~p", + [Packet, jid:to_string(From), jid:to_string(To), Err]), + ErrPkt = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR), + ejabberd_router:route_error(To, From, ErrPkt, Packet) + catch _:_ -> + ok + end; + _ -> + ok + end, + {noreply, State}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, #state{host = Host}) -> + ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100), + ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100), + ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100), + ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100), + ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100), + ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100), + ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0), + ejabberd_router:unregister_route(Host), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +do_route(_State, From, To, #xmlel{name = <<"iq">>} = Packet) -> + if To#jid.luser == <<"">> -> + ejabberd_local:process_iq(From, To, Packet); + true -> + ejabberd_sm:process_iq(From, To, Packet) + end; +do_route(_State, From, To, #xmlel{name = <<"presence">>} = Packet) + when To#jid.luser /= <<"">> -> + case fxml:get_tag_attr_s(<<"type">>, Packet) of + <<"unavailable">> -> + delete_presence(From, To); + _ -> + ok + end; +do_route(_State, _From, _To, _Packet) -> + ok. + +subscribe_nodes(From, To, Nodes) -> + LTo = jid:tolower(jid:remove_resource(To)), + LFrom = jid:tolower(jid:remove_resource(From)), + From_s = jid:to_string(LFrom), + lists:foldl( + fun(_Node, {error, _} = Err) -> + Err; + (Node, {result, _}) -> + case mod_pubsub:subscribe_node(LTo, Node, From, From_s, []) of + {error, _} = Err -> + case is_item_not_found(Err) of + true -> + case mod_pubsub:create_node( + LTo, To#jid.lserver, Node, LFrom, <<"mix">>) of + {result, _} -> + mod_pubsub:subscribe_node(LTo, Node, From, From_s, []); + Error -> + Error + end; + false -> + Err + end; + {result, _} = Result -> + Result + end + end, {result, []}, Nodes). + +unsubscribe_nodes(From, To, Nodes) -> + LTo = jid:tolower(jid:remove_resource(To)), + LFrom = jid:tolower(jid:remove_resource(From)), + From_s = jid:to_string(LFrom), + lists:foldl( + fun(_Node, {error, _} = Err) -> + Err; + (Node, {result, _} = Result) -> + case mod_pubsub:unsubscribe_node(LTo, Node, From, From_s, <<"">>) of + {error, _} = Err -> + case is_not_subscribed(Err) of + true -> Result; + _ -> Err + end; + {result, _} = Res -> + Res + end + end, {result, []}, Nodes). + +publish_participant(From, To) -> + LFrom = jid:tolower(jid:remove_resource(From)), + LTo = jid:tolower(jid:remove_resource(To)), + Participant = #xmlel{name = <<"participant">>, + attrs = [{<<"xmlns">>, ?NS_MIX_0}, + {<<"jid">>, jid:to_string(LFrom)}]}, + ItemID = p1_sha:sha(jid:to_string(LFrom)), + mod_pubsub:publish_item( + LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS, + From, ItemID, [Participant]). + +delete_presence(From, To) -> + LFrom = jid:tolower(From), + LTo = jid:tolower(jid:remove_resource(To)), + case mod_pubsub:get_items(LTo, ?NS_MIX_NODES_PRESENCE) of + Items when is_list(Items) -> + lists:foreach( + fun(#pubsub_item{modification = {_, LJID}, + itemid = {ItemID, _}}) when LJID == LFrom -> + delete_item(From, To, ?NS_MIX_NODES_PRESENCE, ItemID); + (_) -> + ok + end, Items); + _ -> + ok + end. + +delete_participant(From, To) -> + LFrom = jid:tolower(jid:remove_resource(From)), + ItemID = p1_sha:sha(jid:to_string(LFrom)), + delete_presence(From, To), + delete_item(From, To, ?NS_MIX_NODES_PARTICIPANTS, ItemID). + +delete_item(From, To, Node, ItemID) -> + LTo = jid:tolower(jid:remove_resource(To)), + case mod_pubsub:delete_item( + LTo, Node, From, ItemID, true) of + {result, _} = Res -> + Res; + {error, _} = Err -> + case is_item_not_found(Err) of + true -> {result, []}; + false -> Err + end + end. + +is_item_not_found({error, ErrEl}) -> + case fxml:get_subtag_with_xmlns( + ErrEl, <<"item-not-found">>, ?NS_STANZAS) of + #xmlel{} -> true; + _ -> false + end. + +is_not_subscribed({error, ErrEl}) -> + case fxml:get_subtag_with_xmlns( + ErrEl, <<"not-subscribed">>, ?NS_PUBSUB_ERRORS) of + #xmlel{} -> true; + _ -> false + end. + +mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; +mod_opt_type(host) -> fun iolist_to_binary/1; +mod_opt_type(_) -> [host, iqdisc]. diff --git a/src/mod_muc.erl b/src/mod_muc.erl index a64a0032..0d37a236 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -222,7 +222,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">>, @@ -373,7 +373,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 +451,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 +557,18 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, _ -> ok end; <<"message">> -> - case xml:get_attr_s(<<"type">>, Attrs) of + case fxml:get_attr_s(<<"type">>, Attrs) of <<"error">> -> ok; _ -> case acl:match_rule(ServerHost, AccessAdmin, From) of allow -> - Msg = xml:get_path_s(Packet, + Msg = fxml:get_path_s(Packet, [{elem, <<"body">>}, cdata]), broadcast_service_message(Host, Msg); _ -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), ErrText = <<"Only service administrators are allowed " "to send service messages">>, @@ -581,7 +581,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 +593,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 +607,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,17 +629,22 @@ 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, @@ -1056,12 +1062,12 @@ iq_set_register_info(ServerHost, Host, From, Nick, process_iq_register_set(ServerHost, Host, From, SubEl, Lang) -> #xmlel{children = Els} = SubEl, - case xml:get_subtag(SubEl, <<"remove">>) of + case fxml:get_subtag(SubEl, <<"remove">>) of false -> - case xml:remove_cdata(Els) of + case fxml:remove_cdata(Els) of [#xmlel{name = <<"x">>} = XEl] -> - case {xml:get_tag_attr_s(<<"xmlns">>, XEl), - xml:get_tag_attr_s(<<"type">>, XEl)} + case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), + fxml:get_tag_attr_s(<<"type">>, XEl)} of {?NS_XDATA, <<"cancel">>} -> {result, []}; {?NS_XDATA, <<"submit">>} -> @@ -1317,6 +1323,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 +1350,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 c0875737..7c6e84c4 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 diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl index 4d8e3965..1f32f6f9 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_room.erl b/src/mod_muc_room.erl index d19bc5f3..06fdf325 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), @@ -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,7 +432,7 @@ 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 + case fxml:get_attr(<<"node">>, Attrs) of false -> process_iq_disco_info(From, Type, Lang, StateData); {value, _} -> {error, ?ERR_SERVICE_UNAVAILABLE} end; @@ -509,8 +509,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 +568,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 +607,8 @@ normal_state({route, From, ToNick, normal_state({route, From, ToNick, #xmlel{name = <<"iq">>, attrs = Attrs} = Packet}, StateData) -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), - StanzaId = xml:get_attr_s(<<"id">>, Attrs), + Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), + StanzaId = fxml:get_attr_s(<<"id">>, Attrs), case {(StateData#state.config)#config.allow_query_users, is_user_online_iq(StanzaId, From, StateData)} of @@ -884,7 +884,7 @@ route(Pid, From, ToNick, Packet) -> process_groupchat_message(From, #xmlel{name = <<"message">>, attrs = Attrs} = Packet, StateData) -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), case is_user_online(From, StateData) orelse is_user_allowed_message_nonparticipant(From, StateData) of @@ -936,7 +936,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 +1016,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 +1029,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 +1047,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 +1087,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 +1304,7 @@ get_error_condition(Packet) -> end. get_error_condition2(Packet) -> - #xmlel{children = EEls} = xml:get_subtag(Packet, + #xmlel{children = EEls} = fxml:get_subtag(Packet, <<"error">>), [Condition] = [Name || #xmlel{name = Name, @@ -1655,7 +1655,7 @@ filter_presence(#xmlel{name = <<"presence">>, case El of {xmlcdata, _} -> false; #xmlel{attrs = Attrs1} -> - XMLNS = xml:get_attr_s(<<"xmlns">>, + XMLNS = fxml:get_attr_s(<<"xmlns">>, Attrs1), NS_MUC = ?NS_MUC, Size = byte_size(NS_MUC), @@ -1743,11 +1743,11 @@ higher_presence(Pres1, Pres2) -> Pri1 > Pri2. get_priority_from_presence(PresencePacket) -> - case xml:get_subtag(PresencePacket, <<"priority">>) of + case fxml:get_subtag(PresencePacket, <<"priority">>) of false -> 0; SubEl -> case catch - jlib:binary_to_integer(xml:get_tag_cdata(SubEl)) + jlib:binary_to_integer(fxml:get_tag_cdata(SubEl)) of P when is_integer(P) -> P; _ -> 0 @@ -1781,7 +1781,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), @@ -1856,7 +1856,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 +1879,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 +1979,11 @@ check_captcha(Affiliation, From, StateData) -> extract_password([]) -> false; extract_password([#xmlel{attrs = Attrs} = El | Els]) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_MUC -> - case xml:get_subtag(El, <<"password">>) of + case fxml:get_subtag(El, <<"password">>) of false -> false; - SubEl -> xml:get_tag_cdata(SubEl) + SubEl -> fxml:get_tag_cdata(SubEl) end; _ -> extract_password(Els) end; @@ -2057,9 +2057,9 @@ calc_shift(MaxSize, Size, Shift, [S | TSizes]) -> extract_history([], _Type) -> false; extract_history([#xmlel{attrs = Attrs} = El | Els], Type) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_MUC -> - AttrVal = xml:get_path_s(El, + AttrVal = fxml:get_path_s(El, [{elem, <<"history">>}, {attr, Type}]), case Type of <<"since">> -> @@ -2090,6 +2090,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 +2120,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 +2195,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 +2211,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 +2260,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 +2391,7 @@ send_nick_changing(JID, OldNick, StateData, <<"303">>}], children = []}|Status110]}]}, - Packet2 = xml:append_subtags(Presence, + Packet2 = fxml:append_subtags(Presence, [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, @@ -2450,6 +2421,40 @@ send_nick_changing(JID, OldNick, StateData, end, (?DICT):to_list(StateData#state.users)). +status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) -> + Status = case IsInitialPresence of + true -> + S1 = case StateData#state.just_created of + true -> + [#xmlel{name = <<"status">>, + attrs = [{<<"code">>, <<"201">>}], + children = []}]; + false -> [] + end, + S2 = case (StateData#state.config)#config.anonymous of + true -> S1; + false -> + [#xmlel{name = <<"status">>, + attrs = [{<<"code">>, <<"100">>}], + children = []} | S1] + end, + S3 = case (StateData#state.config)#config.logging of + true -> + [#xmlel{name = <<"status">>, + attrs = [{<<"code">>, <<"170">>}], + children = []} | S2]; + false -> S2 + end, + S3; + false -> [] + end, + [#xmlel{name = <<"status">>, + attrs = + [{<<"code">>, + <<"110">>}], + children = []} | Status]; +status_els(_IsInitialPresence, _JID, _Info, _StateData) -> []. + lqueue_new(Max) -> #lqueue{queue = queue:new(), len = 0, max = Max}. @@ -2474,7 +2479,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 +2496,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 +2535,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,14 +2554,14 @@ process_iq_admin(From, set, Lang, SubEl, StateData) -> #xmlel{children = Items} = SubEl, process_admin_items_set(From, Items, Lang, StateData); process_iq_admin(From, get, Lang, SubEl, StateData) -> - case xml:get_subtag(SubEl, <<"item">>) of + case fxml:get_subtag(SubEl, <<"item">>) of false -> {error, ?ERR_BAD_REQUEST}; Item -> FAffiliation = get_affiliation(From, StateData), FRole = get_role(From, StateData), - case xml:get_tag_attr(<<"role">>, Item) of + case fxml:get_tag_attr(<<"role">>, Item) of false -> - case xml:get_tag_attr(<<"affiliation">>, Item) of + case fxml:get_tag_attr(<<"affiliation">>, Item) of false -> {error, ?ERR_BAD_REQUEST}; {value, StrAffiliation} -> case catch list_to_affiliation(StrAffiliation) of @@ -2564,7 +2569,8 @@ process_iq_admin(From, get, Lang, SubEl, StateData) -> SAffiliation -> if (FAffiliation == owner) or (FAffiliation == admin) or - ((FAffiliation == member) and (SAffiliation == member)) -> + ((FAffiliation == member) and not + (StateData#state.config)#config.anonymous) -> Items = items_with_affiliation(SAffiliation, StateData), {result, Items, StateData}; @@ -2748,7 +2754,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 +2767,7 @@ find_changed_items(UJID, UAffiliation, URole, J -> {value, [J]} end; _ -> - case xml:get_attr(<<"nick">>, Attrs) of + case fxml:get_attr(<<"nick">>, Attrs) of {value, N} -> case find_jids_by_nick(N, StateData) of false -> @@ -2781,9 +2787,9 @@ find_changed_items(UJID, UAffiliation, URole, {value, [JID | _] = JIDs} -> TAffiliation = get_affiliation(JID, StateData), TRole = get_role(JID, StateData), - case xml:get_attr(<<"role">>, Attrs) of + case fxml:get_attr(<<"role">>, Attrs) of false -> - case xml:get_attr(<<"affiliation">>, Attrs) of + case fxml:get_attr(<<"affiliation">>, Attrs) of false -> {error, ?ERR_BAD_REQUEST}; {value, StrAffiliation} -> case catch list_to_affiliation(StrAffiliation) of @@ -2824,7 +2830,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), @@ -2871,7 +2877,7 @@ find_changed_items(UJID, UAffiliation, URole, find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, Res); true -> - Reason = xml:get_path_s(Item, + Reason = fxml:get_path_s(Item, [{elem, <<"reason">>}, cdata]), MoreRes = [{Jidx, role, SRole, Reason} @@ -3123,10 +3129,10 @@ process_iq_owner(From, set, Lang, SubEl, StateData) -> case FAffiliation of owner -> #xmlel{children = Els} = SubEl, - case xml:remove_cdata(Els) of + case fxml:remove_cdata(Els) of [#xmlel{name = <<"x">>} = XEl] -> - case {xml:get_tag_attr_s(<<"xmlns">>, XEl), - xml:get_tag_attr_s(<<"type">>, XEl)} + case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), + fxml:get_tag_attr_s(<<"type">>, XEl)} of {?NS_XDATA, <<"cancel">>} -> {result, [], StateData}; {?NS_XDATA, <<"submit">>} -> @@ -3160,10 +3166,10 @@ process_iq_owner(From, get, Lang, SubEl, StateData) -> case FAffiliation of owner -> #xmlel{children = Els} = SubEl, - case xml:remove_cdata(Els) of + case fxml:remove_cdata(Els) of [] -> get_config(Lang, StateData, From); [Item] -> - case xml:get_tag_attr(<<"affiliation">>, Item) of + case fxml:get_tag_attr(<<"affiliation">>, Item) of false -> {error, ?ERR_BAD_REQUEST}; {value, StrAffiliation} -> case catch list_to_affiliation(StrAffiliation) of @@ -4219,7 +4225,7 @@ process_iq_captcha(_From, set, _Lang, SubEl, process_iq_vcard(_From, get, _Lang, _SubEl, StateData) -> #state{config = #config{vcard = VCardRaw}} = StateData, - case xml_stream:parse_element(VCardRaw) of + case fxml_stream:parse_element(VCardRaw) of #xmlel{children = VCardEls} -> {result, VCardEls, StateData}; {error, _} -> @@ -4228,7 +4234,7 @@ process_iq_vcard(_From, get, _Lang, _SubEl, StateData) -> process_iq_vcard(From, set, Lang, SubEl, StateData) -> case get_affiliation(From, StateData) of owner -> - VCardRaw = xml:element_to_binary(SubEl), + VCardRaw = fxml:element_to_binary(SubEl), Config = StateData#state.config, NewConfig = Config#config{vcard = VCardRaw}, change_config(NewConfig, StateData); @@ -4287,7 +4293,7 @@ is_voice_request(Els) -> lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = El, false) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_XDATA -> case jlib:parse_xdata_submit(El) of [_ | _] = Fields -> @@ -4370,7 +4376,7 @@ is_voice_approvement(Els) -> lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = El, false) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_XDATA -> case jlib:parse_xdata_submit(El) of [_ | _] = Fs -> @@ -4424,9 +4430,9 @@ is_invitation(Els) -> lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = El, false) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_MUC_USER -> - case xml:get_subtag(El, <<"invite">>) of + case fxml:get_subtag(El, <<"invite">>) of false -> false; _ -> true end; @@ -4442,20 +4448,20 @@ check_invitation(From, Els, Lang, StateData) -> (StateData#state.config)#config.allow_user_invites orelse FAffiliation == admin orelse FAffiliation == owner, - InviteEl = case xml:remove_cdata(Els) of + InviteEl = case fxml:remove_cdata(Els) of [#xmlel{name = <<"x">>, children = Els1} = XEl] -> - case xml:get_tag_attr_s(<<"xmlns">>, XEl) of + case fxml:get_tag_attr_s(<<"xmlns">>, XEl) of ?NS_MUC_USER -> ok; _ -> throw({error, ?ERR_BAD_REQUEST}) end, - case xml:remove_cdata(Els1) of + case fxml:remove_cdata(Els1) of [#xmlel{name = <<"invite">>} = InviteEl1] -> InviteEl1; _ -> throw({error, ?ERR_BAD_REQUEST}) end; _ -> throw({error, ?ERR_BAD_REQUEST}) end, JID = case - 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}); @@ -4464,9 +4470,9 @@ check_invitation(From, Els, Lang, StateData) -> case CanInvite of false -> throw({error, ?ERR_NOT_ALLOWED}); true -> - Reason = xml:get_path_s(InviteEl, + Reason = fxml:get_path_s(InviteEl, [{elem, <<"reason">>}, cdata]), - ContinueEl = case xml:get_path_s(InviteEl, + ContinueEl = case fxml:get_path_s(InviteEl, [{elem, <<"continue">>}]) of <<>> -> []; @@ -4556,10 +4562,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 +4652,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_multicast.erl b/src/mod_multicast.erl index 96ebcb6f..83520c0b 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 1b1627e8..fa6a961f 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -25,8 +25,11 @@ -module(mod_offline). +-compile([{parse_transform, ejabberd_sql_pt}]). + -author('alexey@process-one.net'). +-protocol({xep, 13, '1.2'}). -protocol({xep, 22, '1.4'}). -protocol({xep, 23, '1.3'}). -protocol({xep, 160, '1.0'}). @@ -37,15 +40,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,6 +59,7 @@ import/3, export/1, get_queue_length/2, + count_offline_messages/2, get_offline_els/2, webadmin_page/3, webadmin_user/4, @@ -62,6 +69,8 @@ handle_info/2, terminate/2, code_change/3, mod_opt_type/1]). +-deprecated({get_queue_length,2}). + -include("ejabberd.hrl"). -include("logger.hrl"). @@ -73,6 +82,8 @@ -include("mod_offline.hrl"). +-include("ejabberd_sql_pt.hrl"). + -define(PROCNAME, ejabberd_offline). -define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000). @@ -112,6 +123,8 @@ init([Host, Opts]) -> update_table(); _ -> ok end, + IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, + no_queue), ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, store_packet, 50), ejabberd_hooks:add(resend_offline_messages_hook, Host, @@ -124,12 +137,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, @@ -174,17 +194,24 @@ terminate(_Reason, State) -> ?MODULE, remove_user, 50), ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50), ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50), + ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50), + ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 50), + ejabberd_hooks:delete(disco_info, Host, ?MODULE, get_info, 50), ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, webadmin_page, 50), ejabberd_hooks:delete(webadmin_user, Host, ?MODULE, webadmin_user, 50), ejabberd_hooks:delete(webadmin_user_parse_query, Host, ?MODULE, webadmin_user_parse_query, 50), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. +store_offline_msg(Host, US, Msgs, Len, MaxOfflineMsgs) -> + DBType = gen_mod:db_type(Host, ?MODULE), + store_offline_msg(Host, US, Msgs, Len, MaxOfflineMsgs, DBType). store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs, mnesia) -> @@ -223,7 +250,7 @@ store_offline_msg(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs, odbc) -> M#offline_msg.timestamp, <<"Offline Storage">>), XML = - ejabberd_odbc:escape(xml:element_to_binary(NewPacket)), + ejabberd_odbc:escape(fxml:element_to_binary(NewPacket)), odbc_queries:add_spool_sql(Username, XML) end, Msgs), @@ -272,38 +299,229 @@ get_sm_features(Acc, _From, _To, <<"">>, _Lang) -> {result, I} -> I; _ -> [] end, - {result, Feats ++ [?NS_FEATURE_MSGOFFLINE]}; + {result, Feats ++ [?NS_FEATURE_MSGOFFLINE, ?NS_FLEX_OFFLINE]}; get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) -> %% override all lesser features... {result, []}; +get_sm_features(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, + ?NS_FLEX_OFFLINE, _Lang) -> + {result, [?NS_FLEX_OFFLINE]}; + get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. +get_sm_identity(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, + ?NS_FLEX_OFFLINE, _Lang) -> + Identity = #xmlel{name = <<"identity">>, + attrs = [{<<"category">>, <<"automation">>}, + {<<"type">>, <<"message-list">>}]}, + [Identity]; +get_sm_identity(Acc, _From, _To, _Node, _Lang) -> + Acc. + +get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID, + #jid{luser = U, lserver = S}, + ?NS_FLEX_OFFLINE, _Lang) -> + case ejabberd_sm:get_session_pid(U, S, R) of + Pid when is_pid(Pid) -> + Hdrs = read_message_headers(U, S), + BareJID = jid:to_string(jid:remove_resource(JID)), + Pid ! dont_ask_offline, + {result, lists:map( + fun({Node, From, _OfflineMsg}) -> + #xmlel{name = <<"item">>, + attrs = [{<<"jid">>, BareJID}, + {<<"node">>, Node}, + {<<"name">>, From}]} + end, Hdrs)}; + none -> + {result, []} + end; +get_sm_items(Acc, _From, _To, _Node, _Lang) -> + Acc. + +get_info(_Acc, #jid{luser = U, lserver = S, lresource = R}, + #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, _Lang) -> + N = jlib:integer_to_binary(count_offline_messages(U, S)), + case ejabberd_sm:get_session_pid(U, S, R) of + Pid when is_pid(Pid) -> + Pid ! dont_ask_offline; + none -> + ok + end, + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}, + {<<"type">>, <<"result">>}], + children = [#xmlel{name = <<"field">>, + attrs = [{<<"var">>, <<"FORM_TYPE">>}, + {<<"type">>, <<"hidden">>}], + children = [#xmlel{name = <<"value">>, + children = [{xmlcdata, + ?NS_FLEX_OFFLINE}]}]}, + #xmlel{name = <<"field">>, + attrs = [{<<"var">>, <<"number_of_messages">>}], + children = [#xmlel{name = <<"value">>, + children = [{xmlcdata, N}]}]}]}]; +get_info(Acc, _From, _To, _Node, _Lang) -> + Acc. + +handle_offline_query(#jid{luser = U, lserver = S} = From, + #jid{luser = U, lserver = S} = _To, + #iq{type = Type, sub_el = SubEl} = IQ) -> + case Type of + get -> + case fxml:get_subtag(SubEl, <<"fetch">>) of + #xmlel{} -> + handle_offline_fetch(From); + false -> + handle_offline_items_view(From, SubEl) + end; + set -> + case fxml:get_subtag(SubEl, <<"purge">>) of + #xmlel{} -> + delete_all_msgs(U, S); + false -> + handle_offline_items_remove(From, SubEl) + end + end, + IQ#iq{type = result, sub_el = []}; +handle_offline_query(_From, _To, #iq{sub_el = SubEl} = IQ) -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}. + +handle_offline_items_view(JID, #xmlel{children = Items}) -> + {U, S, R} = jid:tolower(JID), + lists:foreach( + fun(Node) -> + case fetch_msg_by_node(JID, Node) of + {ok, OfflineMsg} -> + case offline_msg_to_route(S, OfflineMsg) of + {route, From, To, El} -> + NewEl = set_offline_tag(El, Node), + case ejabberd_sm:get_session_pid(U, S, R) of + Pid when is_pid(Pid) -> + Pid ! {route, From, To, NewEl}; + none -> + ok + end; + error -> + ok + end; + error -> + ok + end + end, get_nodes_from_items(Items, <<"view">>)). + +handle_offline_items_remove(JID, #xmlel{children = Items}) -> + lists:foreach( + fun(Node) -> + remove_msg_by_node(JID, Node) + end, get_nodes_from_items(Items, <<"remove">>)). + +get_nodes_from_items(Items, Action) -> + lists:flatmap( + fun(#xmlel{name = <<"item">>, attrs = Attrs}) -> + case fxml:get_attr_s(<<"action">>, Attrs) of + Action -> + case fxml:get_attr_s(<<"node">>, Attrs) of + <<"">> -> + []; + TS -> + [TS] + end; + _ -> + [] + end; + (_) -> + [] + end, Items). + +set_offline_tag(#xmlel{children = Els} = El, Node) -> + OfflineEl = #xmlel{name = <<"offline">>, + attrs = [{<<"xmlns">>, ?NS_FLEX_OFFLINE}], + children = [#xmlel{name = <<"item">>, + attrs = [{<<"node">>, Node}]}]}, + El#xmlel{children = [OfflineEl|Els]}. + +handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) -> + case ejabberd_sm:get_session_pid(U, S, R) of + none -> + ok; + Pid when is_pid(Pid) -> + Pid ! dont_ask_offline, + lists:foreach( + fun({Node, _, Msg}) -> + case offline_msg_to_route(S, Msg) of + {route, From, To, El} -> + NewEl = set_offline_tag(El, Node), + Pid ! {route, From, To, NewEl}; + error -> + ok + end + end, read_message_headers(U, S)) + end. + +fetch_msg_by_node(To, <<Seq:20/binary, "+", From_s/binary>>) -> + case jid:from_string(From_s) of + From = #jid{} -> + case gen_mod:db_type(To#jid.lserver, ?MODULE) of + odbc -> + read_message(From, To, Seq, odbc); + DBType -> + case binary_to_timestamp(Seq) of + undefined -> ok; + TS -> read_message(From, To, TS, DBType) + end + end; + error -> + ok + end. + +remove_msg_by_node(To, <<Seq:20/binary, "+", From_s/binary>>) -> + case jid:from_string(From_s) of + From = #jid{} -> + case gen_mod:db_type(To#jid.lserver, ?MODULE) of + odbc -> + remove_message(From, To, Seq, odbc); + DBType -> + case binary_to_timestamp(Seq) of + undefined -> ok; + TS -> remove_message(From, To, TS, DBType) + end + end; + error -> + ok + end. + need_to_store(LServer, Packet) -> - Type = xml:get_tag_attr_s(<<"type">>, Packet), + Type = fxml:get_tag_attr_s(<<"type">>, Packet), if (Type /= <<"error">>) and (Type /= <<"groupchat">>) and (Type /= <<"headline">>) -> - case 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 +560,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 +577,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 +619,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 +628,9 @@ find_x_expire(_, []) -> never; find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) -> find_x_expire(TimeStamp, Els); find_x_expire(TimeStamp, [El | Els]) -> - case xml:get_tag_attr_s(<<"xmlns">>, El) of + case fxml:get_tag_attr_s(<<"xmlns">>, El) of ?NS_EXPIRE -> - Val = xml:get_tag_attr_s(<<"seconds">>, El), + Val = fxml:get_tag_attr_s(<<"seconds">>, El), case catch jlib:binary_to_integer(Val) of {'EXIT', _} -> never; Int when Int > 0 -> @@ -476,14 +697,11 @@ pop_offline_messages(Ls, LUser, LServer, mnesia) -> _ -> Ls end; pop_offline_messages(Ls, LUser, LServer, odbc) -> - EUser = ejabberd_odbc:escape(LUser), - case odbc_queries:get_and_del_spool_msg_t(LServer, - EUser) - of - {atomic, {selected, [<<"username">>, <<"xml">>], Rs}} -> + case odbc_queries:get_and_del_spool_msg_t(LServer, LUser) of + {atomic, {selected, Rs}} -> Ls ++ - lists:flatmap(fun ([_, XML]) -> - case xml_stream:parse_element(XML) of + lists:flatmap(fun ({_, XML}) -> + case fxml_stream:parse_element(XML) of {error, _Reason} -> []; El -> @@ -601,8 +819,7 @@ remove_user(LUser, LServer, mnesia) -> F = fun () -> mnesia:delete({offline_msg, US}) end, mnesia:transaction(F); remove_user(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - odbc_queries:del_spool_msg(LServer, Username); + odbc_queries:del_spool_msg(LServer, LUser); remove_user(LUser, LServer, riak) -> {atomic, ejabberd_riak:delete_by_index(offline_msg, <<"us">>, {LUser, LServer})}. @@ -631,7 +848,7 @@ update_table() -> iolist_to_binary(S)}, from = jid_to_binary(From), to = jid_to_binary(To), - packet = xml:to_xmlel(El)} + packet = fxml:to_xmlel(El)} end); _ -> ?INFO_MSG("Recreating offline_msg table", []), @@ -646,7 +863,7 @@ discard_warn_sender(Msgs) -> packet = Packet}) -> ErrText = <<"Your contact offline message queue is " "full. The message has been discarded.">>, - Lang = xml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), Err = jlib:make_error_reply(Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)), @@ -673,14 +890,14 @@ get_offline_els(LUser, LServer, DBType) jlib:replace_from_to(From, To, Packet) end, Msgs); get_offline_els(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select xml from spool where username='">>, - Username, <<"' order by seq;">>]) of - {selected, [<<"xml">>], Rs} -> + case catch ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(xml)s from spool where " + "username=%(LUser)s order by seq")) of + {selected, Rs} -> lists:flatmap( - fun([XML]) -> - case xml_stream:parse_element(XML) of + fun({XML}) -> + case fxml_stream:parse_element(XML) of #xmlel{} = El -> case offline_msg_to_route(LServer, El) of {route, _, _, NewEl} -> @@ -701,14 +918,131 @@ offline_msg_to_route(LServer, #offline_msg{} = R) -> jlib:add_delay_info(R#offline_msg.packet, LServer, R#offline_msg.timestamp, <<"Offline Storage">>)}; offline_msg_to_route(_LServer, #xmlel{} = El) -> - To = jid:from_string(xml:get_tag_attr_s(<<"to">>, El)), - From = jid:from_string(xml:get_tag_attr_s(<<"from">>, El)), + To = jid:from_string(fxml:get_tag_attr_s(<<"to">>, El)), + From = jid:from_string(fxml:get_tag_attr_s(<<"from">>, El)), if (To /= error) and (From /= error) -> {route, From, To, El}; true -> error end. +binary_to_timestamp(TS) -> + case catch jlib:binary_to_integer(TS) of + Int when is_integer(Int) -> + Secs = Int div 1000000, + USec = Int rem 1000000, + MSec = Secs div 1000000, + Sec = Secs rem 1000000, + {MSec, Sec, USec}; + _ -> + undefined + end. + +timestamp_to_binary({MS, S, US}) -> + format_timestamp(integer_to_list((MS * 1000000 + S) * 1000000 + US)). + +format_timestamp(TS) -> + iolist_to_binary(io_lib:format("~20..0s", [TS])). + +offline_msg_to_header(#offline_msg{from = From, timestamp = Int} = Msg) -> + TS = timestamp_to_binary(Int), + From_s = jid:to_string(From), + {<<TS/binary, "+", From_s/binary>>, From_s, Msg}. + +read_message_headers(LUser, LServer) -> + DBType = gen_mod:db_type(LServer, ?MODULE), + read_message_headers(LUser, LServer, DBType). + +read_message_headers(LUser, LServer, mnesia) -> + Msgs = mnesia:dirty_read({offline_msg, {LUser, LServer}}), + Hdrs = lists:map(fun offline_msg_to_header/1, Msgs), + lists:keysort(1, Hdrs); +read_message_headers(LUser, LServer, riak) -> + case ejabberd_riak:get_by_index( + offline_msg, offline_msg_schema(), + <<"us">>, {LUser, LServer}) of + {ok, Rs} -> + Hdrs = lists:map(fun offline_msg_to_header/1, Rs), + lists:keysort(1, Hdrs); + _Err -> + [] + end; +read_message_headers(LUser, LServer, odbc) -> + Username = ejabberd_odbc:escape(LUser), + case catch ejabberd_odbc:sql_query( + LServer, [<<"select xml, seq from spool where username ='">>, + Username, <<"' order by seq;">>]) of + {selected, [<<"xml">>, <<"seq">>], Rows} -> + Hdrs = lists:flatmap( + fun([XML, Seq]) -> + try + #xmlel{} = El = fxml_stream:parse_element(XML), + From = fxml:get_tag_attr_s(<<"from">>, El), + #jid{} = jid:from_string(From), + TS = format_timestamp(Seq), + [{<<TS/binary, "+", From/binary>>, From, El}] + catch _:_ -> [] + end + end, Rows), + lists:keysort(1, Hdrs); + _Err -> + [] + end. + +read_message(_From, To, TS, mnesia) -> + {U, S, _} = jid:tolower(To), + case mnesia:dirty_match_object( + offline_msg, #offline_msg{us = {U, S}, timestamp = TS, _ = '_'}) of + [Msg|_] -> + {ok, Msg}; + _ -> + error + end; +read_message(_From, _To, TS, riak) -> + case ejabberd_riak:get(offline_msg, offline_msg_schema(), TS) of + {ok, Msg} -> + {ok, Msg}; + _ -> + error + end; +read_message(_From, To, Seq, odbc) -> + {LUser, LServer, _} = jid:tolower(To), + Username = ejabberd_odbc:escape(LUser), + SSeq = ejabberd_odbc:escape(Seq), + case ejabberd_odbc:sql_query( + LServer, + [<<"select xml from spool where username='">>, Username, + <<"' and seq='">>, SSeq, <<"';">>]) of + {selected, [<<"xml">>], [[RawXML]|_]} -> + case fxml_stream:parse_element(RawXML) of + #xmlel{} = El -> {ok, El}; + {error, _} -> error + end; + _ -> + error + end. + +remove_message(_From, To, TS, mnesia) -> + {U, S, _} = jid:tolower(To), + Msgs = mnesia:dirty_match_object( + offline_msg, #offline_msg{us = {U, S}, timestamp = TS, _ = '_'}), + lists:foreach( + fun(Msg) -> + mnesia:dirty_delete_object(Msg) + end, Msgs); +remove_message(_From, _To, TS, riak) -> + ejabberd_riak:delete(offline_msg, TS), + ok; +remove_message(_From, To, Seq, odbc) -> + {LUser, LServer, _} = jid:tolower(To), + Username = ejabberd_odbc:escape(LUser), + SSeq = ejabberd_odbc:escape(Seq), + ejabberd_odbc:sql_query( + LServer, + [<<"delete from spool where username='">>, Username, + <<"' and seq='">>, SSeq, <<"';">>]), + ok. + read_all_msgs(LUser, LServer, mnesia) -> US = {LUser, LServer}, lists:keysort(#offline_msg.timestamp, @@ -723,20 +1057,20 @@ read_all_msgs(LUser, LServer, riak) -> [] end; read_all_msgs(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select xml from spool where username='">>, - Username, <<"' order by seq;">>]) - of - {selected, [<<"xml">>], Rs} -> - lists:flatmap(fun ([XML]) -> - case xml_stream:parse_element(XML) of - {error, _Reason} -> []; - El -> [El] - end - end, - Rs); - _ -> [] + case catch ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(xml)s from spool where " + "username=%(LUser)s order by seq")) of + {selected, Rs} -> + lists:flatmap( + fun({XML}) -> + case fxml_stream:parse_element(XML) of + {error, _Reason} -> []; + El -> [El] + end + end, + Rs); + _ -> [] end. format_user_queue(Msgs, DBType) when DBType == mnesia; DBType == riak -> @@ -878,7 +1212,7 @@ user_queue_parse_query(LUser, LServer, Query, odbc) -> of {selected, [<<"xml">>, <<"seq">>], Rs} -> lists:flatmap(fun ([XML, Seq]) -> - case xml_stream:parse_element(XML) + case fxml_stream:parse_element(XML) of {error, _Reason} -> []; El -> [{El, Seq}] @@ -919,30 +1253,7 @@ 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) -> Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages, @@ -984,7 +1295,7 @@ get_messages_subset2(Max, Length, MsgsAll, odbc) -> MsgsFirstN ++ [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))))], @@ -1015,8 +1326,7 @@ delete_all_msgs(LUser, LServer, riak) -> <<"us">>, {LUser, LServer}), {atomic, Res}; delete_all_msgs(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - odbc_queries:del_spool_msg(LServer, Username), + odbc_queries:del_spool_msg(LServer, LUser), {atomic, ok}. webadmin_user_parse_query(_, <<"removealloffline">>, @@ -1052,15 +1362,13 @@ count_offline_messages(LUser, LServer, mnesia) -> _ -> 0 end; count_offline_messages(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case catch odbc_queries:count_records_where(LServer, - <<"spool">>, - <<"where username='", - Username/binary, "'">>) - of - {selected, [_], [[Res]]} -> - jlib:binary_to_integer(Res); - _ -> 0 + case catch ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(count(*))d from spool " + "where username=%(LUser)s")) of + {selected, [{Res}]} -> + Res; + _ -> 0 end; count_offline_messages(LUser, LServer, riak) -> case ejabberd_riak:count_by_index( @@ -1110,7 +1418,7 @@ export(_Server) -> Packet1 = jlib:replace_from_to(From, To, Packet), Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp, <<"Offline Storage">>), - XML = ejabberd_odbc:escape(xml:element_to_binary(Packet2)), + XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet2)), [[<<"delete from spool where username='">>, Username, <<"';">>], [<<"insert into spool(username, xml) values ('">>, Username, <<"', '">>, XML, <<"');">>]]; @@ -1121,12 +1429,12 @@ export(_Server) -> import(LServer) -> [{<<"select username, xml from spool;">>, fun([LUser, XML]) -> - El = #xmlel{} = xml_stream:parse_element(XML), + El = #xmlel{} = fxml_stream:parse_element(XML), From = #jid{} = jid:from_string( - xml:get_attr_s(<<"from">>, El#xmlel.attrs)), + fxml: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">>}, + 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 -> diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl index 34fdcdb7..1118b7bb 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 9c2af037..193befe8 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -35,7 +35,8 @@ process_iq_set/4, process_iq_get/5, get_user_list/3, check_packet/6, remove_user/2, item_to_raw/1, raw_to_item/1, is_list_needdb/1, updated_list/3, - item_to_xml/1, get_user_lists/2, import/3]). + item_to_xml/1, get_user_lists/2, import/3, + set_privacy_list/1]). -export([sql_add_privacy_list/2, sql_get_default_privacy_list/2, @@ -108,12 +109,12 @@ process_iq_get(_, From, _To, #iq{sub_el = SubEl}, #userlist{name = Active}) -> #jid{luser = LUser, lserver = LServer} = From, #xmlel{children = Els} = SubEl, - case xml:remove_cdata(Els) of + case fxml:remove_cdata(Els) of [] -> process_lists_get(LUser, LServer, Active); [#xmlel{name = Name, attrs = Attrs}] -> case Name of <<"list">> -> - ListName = xml:get_attr(<<"name">>, Attrs), + ListName = fxml:get_attr(<<"name">>, Attrs), process_list_get(LUser, LServer, ListName); _ -> {error, ?ERR_BAD_REQUEST} end; @@ -180,16 +181,14 @@ process_lists_get(LUser, LServer, _Active, riak) -> error end; process_lists_get(LUser, LServer, _Active, odbc) -> - Default = case catch sql_get_default_privacy_list(LUser, - LServer) - of - {selected, [<<"name">>], []} -> none; - {selected, [<<"name">>], [[DefName]]} -> DefName; + Default = case catch sql_get_default_privacy_list(LUser, LServer) of + {selected, []} -> none; + {selected, [{DefName}]} -> DefName; _ -> none end, case catch sql_get_privacy_list_names(LUser, LServer) of - {selected, [<<"name">>], Names} -> - LItems = lists:map(fun ([N]) -> + {selected, Names} -> + LItems = lists:map(fun ({N}) -> #xmlel{name = <<"list">>, attrs = [{<<"name">>, N}], children = []} @@ -241,17 +240,11 @@ process_list_get(LUser, LServer, Name, riak) -> error end; process_list_get(LUser, LServer, Name, odbc) -> - case catch sql_get_privacy_list_id(LUser, LServer, Name) - of - {selected, [<<"id">>], []} -> not_found; - {selected, [<<"id">>], [[ID]]} -> - case catch sql_get_privacy_list_data_by_id(ID, LServer) - of - {selected, - [<<"t">>, <<"value">>, <<"action">>, <<"ord">>, - <<"match_all">>, <<"match_iq">>, <<"match_message">>, - <<"match_presence_in">>, <<"match_presence_out">>], - RItems} -> + case catch sql_get_privacy_list_id(LUser, LServer, Name) of + {selected, []} -> not_found; + {selected, [{ID}]} -> + case catch sql_get_privacy_list_data_by_id(ID, LServer) of + {selected, RItems} -> lists:flatmap(fun raw_to_item/1, RItems); _ -> error end; @@ -342,14 +335,14 @@ list_to_action(S) -> process_iq_set(_, From, _To, #iq{sub_el = SubEl}) -> #jid{luser = LUser, lserver = LServer} = From, #xmlel{children = Els} = SubEl, - case xml:remove_cdata(Els) of + case fxml:remove_cdata(Els) of [#xmlel{name = Name, attrs = Attrs, children = SubEls}] -> - ListName = xml:get_attr(<<"name">>, Attrs), + ListName = fxml:get_attr(<<"name">>, Attrs), case Name of <<"list">> -> process_list_set(LUser, LServer, ListName, - xml:remove_cdata(SubEls)); + fxml:remove_cdata(SubEls)); <<"active">> -> process_active_set(LUser, LServer, ListName); <<"default">> -> @@ -404,9 +397,9 @@ process_default_set(LUser, LServer, {value, Name}, odbc) -> F = fun () -> case sql_get_privacy_list_names_t(LUser) of - {selected, [<<"name">>], []} -> not_found; - {selected, [<<"name">>], Names} -> - case lists:member([Name], Names) of + {selected, []} -> not_found; + {selected, Names} -> + case lists:member({Name}, Names) of true -> sql_set_default_privacy_list(LUser, Name), ok; false -> not_found end @@ -472,17 +465,11 @@ process_active_set(LUser, LServer, Name, riak) -> error end; process_active_set(LUser, LServer, Name, odbc) -> - case catch sql_get_privacy_list_id(LUser, LServer, Name) - of - {selected, [<<"id">>], []} -> error; - {selected, [<<"id">>], [[ID]]} -> - case catch sql_get_privacy_list_data_by_id(ID, LServer) - of - {selected, - [<<"t">>, <<"value">>, <<"action">>, <<"ord">>, - <<"match_all">>, <<"match_iq">>, <<"match_message">>, - <<"match_presence_in">>, <<"match_presence_out">>], - RItems} -> + case catch sql_get_privacy_list_id(LUser, LServer, Name) of + {selected, []} -> error; + {selected, [{ID}]} -> + case catch sql_get_privacy_list_data_by_id(ID, LServer) of + {selected, RItems} -> lists:flatmap(fun raw_to_item/1, RItems); _ -> error end; @@ -519,9 +506,9 @@ remove_privacy_list(LUser, LServer, Name, riak) -> remove_privacy_list(LUser, LServer, Name, odbc) -> F = fun () -> case sql_get_default_privacy_list_t(LUser) of - {selected, [<<"name">>], []} -> + {selected, []} -> sql_remove_privacy_list(LUser, Name), ok; - {selected, [<<"name">>], [[Default]]} -> + {selected, [{Default}]} -> if Name == Default -> conflict; true -> sql_remove_privacy_list(LUser, Name), ok end @@ -529,6 +516,35 @@ remove_privacy_list(LUser, LServer, Name, odbc) -> end, odbc_queries:sql_transaction(LServer, F). +set_privacy_list(#privacy{us = {_, LServer}} = Privacy) -> + DBType = gen_mod:db_type(LServer, ?MODULE), + set_privacy_list(Privacy, DBType). + +set_privacy_list(Privacy, mnesia) -> + mnesia:dirty_write(Privacy); +set_privacy_list(Privacy, riak) -> + ejabberd_riak:put(Privacy, privacy_schema()); +set_privacy_list(#privacy{us = {LUser, LServer}, + default = Default, + lists = Lists}, odbc) -> + F = fun() -> + lists:foreach( + fun({Name, List}) -> + sql_add_privacy_list(LUser, Name), + {selected, [<<"id">>], [[I]]} = + sql_get_privacy_list_id_t(LUser, Name), + RItems = lists:map(fun item_to_raw/1, List), + sql_set_privacy_list(I, RItems), + if is_binary(Default) -> + sql_set_default_privacy_list(LUser, Default), + ok; + true -> + ok + end + end, Lists) + end, + odbc_queries:sql_transaction(LServer, F). + set_privacy_list(LUser, LServer, Name, List, mnesia) -> F = fun () -> case mnesia:wread({privacy, {LUser, LServer}}) of @@ -560,12 +576,12 @@ set_privacy_list(LUser, LServer, Name, List, odbc) -> RItems = lists:map(fun item_to_raw/1, List), F = fun () -> ID = case sql_get_privacy_list_id_t(LUser, Name) of - {selected, [<<"id">>], []} -> + {selected, []} -> sql_add_privacy_list(LUser, Name), - {selected, [<<"id">>], [[I]]} = + {selected, [{I}]} = sql_get_privacy_list_id_t(LUser, Name), I; - {selected, [<<"id">>], [[I]]} -> I + {selected, [{I}]} -> I end, sql_set_privacy_list(ID, RItems), ok @@ -621,10 +637,10 @@ parse_items([#xmlel{name = <<"item">>, attrs = Attrs, children = SubEls} | Els], Res) -> - Type = xml:get_attr(<<"type">>, Attrs), - Value = xml:get_attr(<<"value">>, Attrs), - SAction = xml:get_attr(<<"action">>, Attrs), - SOrder = xml:get_attr(<<"order">>, Attrs), + Type = fxml:get_attr(<<"type">>, Attrs), + Value = fxml:get_attr(<<"value">>, Attrs), + SAction = fxml:get_attr(<<"action">>, Attrs), + SOrder = fxml:get_attr(<<"order">>, Attrs), Action = case catch list_to_action(element(2, SAction)) of {'EXIT', _} -> false; @@ -674,7 +690,7 @@ parse_items([#xmlel{name = <<"item">>, attrs = Attrs, case I2 of false -> false; _ -> - case parse_matches(I2, xml:remove_cdata(SubEls)) of + case parse_matches(I2, fxml:remove_cdata(SubEls)) of false -> false; I3 -> parse_items(Els, [I3 | Res]) end @@ -755,16 +771,11 @@ get_user_list(_, LUser, LServer, riak) -> get_user_list(_, LUser, LServer, odbc) -> case catch sql_get_default_privacy_list(LUser, LServer) of - {selected, [<<"name">>], []} -> {none, []}; - {selected, [<<"name">>], [[Default]]} -> + {selected, []} -> {none, []}; + {selected, [{Default}]} -> case catch sql_get_privacy_list_data(LUser, LServer, - Default) - of - {selected, - [<<"t">>, <<"value">>, <<"action">>, <<"ord">>, - <<"match_all">>, <<"match_iq">>, <<"match_message">>, - <<"match_presence_in">>, <<"match_presence_out">>], - RItems} -> + Default) of + {selected, RItems} -> {Default, lists:flatmap(fun raw_to_item/1, RItems)}; _ -> {none, []} end; @@ -792,26 +803,21 @@ get_user_lists(LUser, LServer, riak) -> end; get_user_lists(LUser, LServer, odbc) -> Default = case catch sql_get_default_privacy_list(LUser, LServer) of - {selected, [<<"name">>], []} -> + {selected, []} -> none; - {selected, [<<"name">>], [[DefName]]} -> + {selected, [{DefName}]} -> DefName; _ -> none end, case catch sql_get_privacy_list_names(LUser, LServer) of - {selected, [<<"name">>], Names} -> + {selected, Names} -> Lists = lists:flatmap( - fun([Name]) -> + fun({Name}) -> case catch sql_get_privacy_list_data( LUser, LServer, Name) of - {selected, - [<<"t">>, <<"value">>, <<"action">>, - <<"ord">>, <<"match_all">>, <<"match_iq">>, - <<"match_message">>, <<"match_presence_in">>, - <<"match_presence_out">>], - RItems} -> + {selected, RItems} -> [{Name, lists:flatmap(fun raw_to_item/1, RItems)}]; _ -> [] @@ -852,7 +858,7 @@ check_packet(_, User, Server, <<"message">> -> message; <<"iq">> -> iq; <<"presence">> -> - case xml:get_attr_s(<<"type">>, Attrs) of + case fxml:get_attr_s(<<"type">>, Attrs) of %% notification <<"">> -> presence; <<"unavailable">> -> presence; @@ -964,9 +970,9 @@ updated_list(_, #userlist{name = OldName} = Old, true -> Old end. -raw_to_item([SType, SValue, SAction, SOrder, SMatchAll, - SMatchIQ, SMatchMessage, SMatchPresenceIn, - SMatchPresenceOut] = Row) -> +raw_to_item({SType, SValue, SAction, Order, MatchAll, + MatchIQ, MatchMessage, MatchPresenceIn, + MatchPresenceOut} = Row) -> try {Type, Value} = case SType of <<"n">> -> {none, none}; @@ -988,12 +994,6 @@ raw_to_item([SType, SValue, SAction, SOrder, SMatchAll, <<"a">> -> allow; <<"d">> -> deny end, - Order = jlib:binary_to_integer(SOrder), - MatchAll = ejabberd_odbc:to_bool(SMatchAll), - MatchIQ = ejabberd_odbc:to_bool(SMatchIQ), - MatchMessage = ejabberd_odbc:to_bool(SMatchMessage), - MatchPresenceIn = ejabberd_odbc:to_bool(SMatchPresenceIn), - MatchPresenceOut = ejabberd_odbc:to_bool(SMatchPresenceOut), [#listitem{type = Type, value = Value, action = Action, order = Order, match_all = MatchAll, match_iq = MatchIQ, match_message = MatchMessage, @@ -1027,58 +1027,29 @@ item_to_raw(#listitem{type = Type, value = Value, allow -> <<"a">>; deny -> <<"d">> end, - SOrder = iolist_to_binary(integer_to_list(Order)), - SMatchAll = if MatchAll -> <<"1">>; - true -> <<"0">> - end, - SMatchIQ = if MatchIQ -> <<"1">>; - true -> <<"0">> - end, - SMatchMessage = if MatchMessage -> <<"1">>; - true -> <<"0">> - end, - SMatchPresenceIn = if MatchPresenceIn -> <<"1">>; - true -> <<"0">> - end, - SMatchPresenceOut = if MatchPresenceOut -> <<"1">>; - true -> <<"0">> - end, - [SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ, - SMatchMessage, SMatchPresenceIn, SMatchPresenceOut]. + {SType, SValue, SAction, Order, MatchAll, MatchIQ, + MatchMessage, MatchPresenceIn, MatchPresenceOut}. sql_get_default_privacy_list(LUser, LServer) -> - Username = ejabberd_odbc:escape(LUser), - odbc_queries:get_default_privacy_list(LServer, - Username). + odbc_queries:get_default_privacy_list(LServer, LUser). sql_get_default_privacy_list_t(LUser) -> - Username = ejabberd_odbc:escape(LUser), - odbc_queries:get_default_privacy_list_t(Username). + odbc_queries:get_default_privacy_list_t(LUser). sql_get_privacy_list_names(LUser, LServer) -> - Username = ejabberd_odbc:escape(LUser), - odbc_queries:get_privacy_list_names(LServer, Username). + odbc_queries:get_privacy_list_names(LServer, LUser). sql_get_privacy_list_names_t(LUser) -> - Username = ejabberd_odbc:escape(LUser), - odbc_queries:get_privacy_list_names_t(Username). + odbc_queries:get_privacy_list_names_t(LUser). sql_get_privacy_list_id(LUser, LServer, Name) -> - Username = ejabberd_odbc:escape(LUser), - SName = ejabberd_odbc:escape(Name), - odbc_queries:get_privacy_list_id(LServer, Username, - SName). + odbc_queries:get_privacy_list_id(LServer, LUser, Name). sql_get_privacy_list_id_t(LUser, Name) -> - Username = ejabberd_odbc:escape(LUser), - SName = ejabberd_odbc:escape(Name), - odbc_queries:get_privacy_list_id_t(Username, SName). + odbc_queries:get_privacy_list_id_t(LUser, Name). sql_get_privacy_list_data(LUser, LServer, Name) -> - Username = ejabberd_odbc:escape(LUser), - SName = ejabberd_odbc:escape(Name), - odbc_queries:get_privacy_list_data(LServer, Username, - SName). + odbc_queries:get_privacy_list_data(LServer, LUser, Name). sql_get_privacy_list_data_t(LUser, Name) -> Username = ejabberd_odbc:escape(LUser), @@ -1092,33 +1063,22 @@ sql_get_privacy_list_data_by_id_t(ID) -> odbc_queries:get_privacy_list_data_by_id_t(ID). sql_set_default_privacy_list(LUser, Name) -> - Username = ejabberd_odbc:escape(LUser), - SName = ejabberd_odbc:escape(Name), - odbc_queries:set_default_privacy_list(Username, SName). + odbc_queries:set_default_privacy_list(LUser, Name). sql_unset_default_privacy_list(LUser, LServer) -> - Username = ejabberd_odbc:escape(LUser), - odbc_queries:unset_default_privacy_list(LServer, - Username). + odbc_queries:unset_default_privacy_list(LServer, LUser). sql_remove_privacy_list(LUser, Name) -> - Username = ejabberd_odbc:escape(LUser), - SName = ejabberd_odbc:escape(Name), - odbc_queries:remove_privacy_list(Username, SName). + odbc_queries:remove_privacy_list(LUser, Name). sql_add_privacy_list(LUser, Name) -> - Username = ejabberd_odbc:escape(LUser), - SName = ejabberd_odbc:escape(Name), - odbc_queries:add_privacy_list(Username, SName). + odbc_queries:add_privacy_list(LUser, Name). sql_set_privacy_list(ID, RItems) -> odbc_queries:set_privacy_list(ID, RItems). sql_del_privacy_lists(LUser, LServer) -> - Username = ejabberd_odbc:escape(LUser), - Server = ejabberd_odbc:escape(LServer), - odbc_queries:del_privacy_lists(LServer, Server, - Username). + odbc_queries:del_privacy_lists(LServer, LUser). update_table() -> Fields = record_info(fields, privacy), diff --git a/src/mod_private.erl b/src/mod_private.erl index a03d83e5..f3dceeaa 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -33,7 +33,7 @@ -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"). @@ -82,19 +82,7 @@ process_sm_iq(#jid{luser = LUser, lserver = LServer}, IQ#iq{type = error, sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]}; Data -> - DBType = gen_mod:db_type(LServer, ?MODULE), - F = fun () -> - lists:foreach(fun (Datum) -> - set_data(LUser, LServer, - Datum, DBType) - end, - Data) - end, - case DBType of - odbc -> ejabberd_odbc:sql_transaction(LServer, F); - mnesia -> mnesia:transaction(F); - riak -> F() - end, + set_data(LUser, LServer, Data), IQ#iq{type = result, sub_el = []} end; _ -> @@ -137,23 +125,35 @@ filter_xmlels(Xmlels) -> filter_xmlels(Xmlels, []). filter_xmlels([], Data) -> lists:reverse(Data); filter_xmlels([#xmlel{attrs = Attrs} = Xmlel | Xmlels], Data) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of <<"">> -> []; XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data]) end; filter_xmlels([_ | Xmlels], Data) -> filter_xmlels(Xmlels, Data). +set_data(LUser, LServer, Data) -> + DBType = gen_mod:db_type(LServer, ?MODULE), + F = fun () -> + lists:foreach(fun (Datum) -> + set_data(LUser, LServer, + Datum, DBType) + end, + Data) + end, + case DBType of + odbc -> ejabberd_odbc:sql_transaction(LServer, F); + mnesia -> mnesia:transaction(F); + riak -> F() + end. + set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) -> mnesia:write(#private_storage{usns = {LUser, LServer, XmlNS}, xml = Xmlel}); set_data(LUser, LServer, {XMLNS, El}, odbc) -> - Username = ejabberd_odbc:escape(LUser), - LXMLNS = ejabberd_odbc:escape(XMLNS), - SData = ejabberd_odbc:escape(xml:element_to_binary(El)), - odbc_queries:set_private_data(LServer, Username, LXMLNS, - SData); + SData = fxml:element_to_binary(El), + odbc_queries:set_private_data(LServer, LUser, XMLNS, SData); set_data(LUser, LServer, {XMLNS, El}, riak) -> ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS}, xml = El}, @@ -181,13 +181,11 @@ get_data(LUser, LServer, mnesia, end; get_data(LUser, LServer, odbc, [{XMLNS, El} | Els], Res) -> - Username = ejabberd_odbc:escape(LUser), - LXMLNS = ejabberd_odbc:escape(XMLNS), case catch odbc_queries:get_private_data(LServer, - Username, LXMLNS) + LUser, XMLNS) of - {selected, [<<"data">>], [[SData]]} -> - case xml_stream:parse_element(SData) of + {selected, [{SData}]} -> + case fxml_stream:parse_element(SData) of Data when is_record(Data, xmlel) -> get_data(LUser, LServer, odbc, Els, [Data | Res]) end; @@ -214,12 +212,11 @@ get_all_data(LUser, LServer, mnesia) -> xml = '$1'}, [], ['$1']}])); get_all_data(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case catch odbc_queries:get_private_data(LServer, Username) of - {selected, [<<"namespace">>, <<"data">>], Res} -> + case catch odbc_queries:get_private_data(LServer, LUser) of + {selected, Res} -> lists:flatmap( - fun([_, SData]) -> - case xml_stream:parse_element(SData) of + fun({_, SData}) -> + case fxml_stream:parse_element(SData) of #xmlel{} = El -> [El]; _ -> @@ -266,9 +263,7 @@ remove_user(LUser, LServer, mnesia) -> end, mnesia:transaction(F); remove_user(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - odbc_queries:del_user_private_storage(LServer, - Username); + odbc_queries:del_user_private_storage(LServer, LUser); remove_user(LUser, LServer, riak) -> {atomic, ejabberd_riak:delete_by_index(private_storage, <<"us">>, {LUser, LServer})}. @@ -284,7 +279,7 @@ update_table() -> R#private_storage{usns = {iolist_to_binary(U), iolist_to_binary(S), iolist_to_binary(NS)}, - xml = xml:to_xmlel(El)} + xml = fxml:to_xmlel(El)} end); _ -> ?INFO_MSG("Recreating private_storage table", []), @@ -299,7 +294,7 @@ export(_Server) -> Username = ejabberd_odbc:escape(LUser), LXMLNS = ejabberd_odbc:escape(XMLNS), SData = - ejabberd_odbc:escape(xml:element_to_binary(Data)), + ejabberd_odbc:escape(fxml:element_to_binary(Data)), odbc_queries:set_private_data_sql(Username, LXMLNS, SData); (_Host, _R) -> @@ -309,7 +304,7 @@ export(_Server) -> import(LServer) -> [{<<"select username, namespace, data from private_storage;">>, fun([LUser, XMLNS, XML]) -> - El = #xmlel{} = xml_stream:parse_element(XML), + El = #xmlel{} = fxml_stream:parse_element(XML), #private_storage{usns = {LUser, LServer, XMLNS}, xml = El} end}]. diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl index 97d5b00a..8b4644ba 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}) -> @@ -175,11 +175,11 @@ process_iq(InitiatorJID, #state{acl = ACL, serverhost = ServerHost}) -> case acl:match_rule(ServerHost, ACL, InitiatorJID) of allow -> - ActivateEl = xml:get_path_s(SubEl, + ActivateEl = fxml:get_path_s(SubEl, [{elem, <<"activate">>}]), - SID = xml:get_tag_attr_s(<<"sid">>, SubEl), + SID = fxml:get_tag_attr_s(<<"sid">>, SubEl), case catch - jid:from_string(xml:get_tag_cdata(ActivateEl)) + jid:from_string(fxml:get_tag_cdata(ActivateEl)) of TargetJID when is_record(TargetJID, jid), SID /= <<"">>, diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 616c6492..6531ed87 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; _ -> @@ -1040,7 +1044,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) -> ok end; _ -> - case xml:get_attr_s(<<"type">>, Attrs) of + case fxml:get_attr_s(<<"type">>, Attrs) of <<"error">> -> ok; <<"result">> -> @@ -1236,7 +1240,7 @@ iq_get_vcard(Lang) -> ). iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) -> - iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)). + iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(Host)). -spec(iq_pubsub/8 :: ( @@ -1255,16 +1259,16 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) -> iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> #xmlel{children = SubEls} = SubEl, - case xml:remove_cdata(SubEls) of + case fxml:remove_cdata(SubEls) of [#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] -> - Node = xml:get_attr_s(<<"node">>, Attrs), + Node = fxml:get_attr_s(<<"node">>, Attrs), case {IQType, Name} of {set, <<"create">>} -> Config = case Rest of [#xmlel{name = <<"configure">>, children = C}] -> C; _ -> [] end, - Type = case xml:get_attr_s(<<"type">>, Attrs) of + Type = case fxml:get_attr_s(<<"type">>, Attrs) of <<>> -> hd(Plugins); T -> T end, @@ -1276,10 +1280,10 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> create_node(Host, ServerHost, Node, From, Type, Access, Config) end; {set, <<"publish">>} -> - case xml:remove_cdata(Els) of + case fxml:remove_cdata(Els) of [#xmlel{name = <<"item">>, attrs = ItemAttrs, children = Payload}] -> - ItemId = xml:get_attr_s(<<"id">>, ItemAttrs), + ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs), publish_item(Host, ServerHost, Node, From, ItemId, Payload, Access); [] -> {error, @@ -1289,14 +1293,14 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)} end; {set, <<"retract">>} -> - ForceNotify = case xml:get_attr_s(<<"notify">>, Attrs) of + ForceNotify = case fxml:get_attr_s(<<"notify">>, Attrs) of <<"1">> -> true; <<"true">> -> true; _ -> false end, - case xml:remove_cdata(Els) of + case fxml:remove_cdata(Els) of [#xmlel{name = <<"item">>, attrs = ItemAttrs}] -> - ItemId = xml:get_attr_s(<<"id">>, ItemAttrs), + ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs), delete_item(Host, Node, From, ItemId, ForceNotify); _ -> {error, @@ -1307,37 +1311,37 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> [#xmlel{name = <<"options">>, children = C}] -> C; _ -> [] end, - JID = xml:get_attr_s(<<"jid">>, Attrs), + JID = fxml:get_attr_s(<<"jid">>, Attrs), subscribe_node(Host, Node, From, JID, Config); {set, <<"unsubscribe">>} -> - JID = xml:get_attr_s(<<"jid">>, Attrs), - SubId = xml:get_attr_s(<<"subid">>, Attrs), + JID = fxml:get_attr_s(<<"jid">>, Attrs), + SubId = fxml:get_attr_s(<<"subid">>, Attrs), unsubscribe_node(Host, Node, From, JID, SubId); {get, <<"items">>} -> - MaxItems = xml:get_attr_s(<<"max_items">>, Attrs), - SubId = xml:get_attr_s(<<"subid">>, Attrs), + MaxItems = fxml:get_attr_s(<<"max_items">>, Attrs), + SubId = fxml:get_attr_s(<<"subid">>, Attrs), ItemIds = lists:foldl(fun (#xmlel{name = <<"item">>, attrs = ItemAttrs}, Acc) -> - case xml:get_attr_s(<<"id">>, ItemAttrs) of + case fxml:get_attr_s(<<"id">>, ItemAttrs) of <<>> -> Acc; ItemId -> [ItemId | Acc] end; (_, Acc) -> Acc end, - [], xml:remove_cdata(Els)), + [], fxml:remove_cdata(Els)), get_items(Host, Node, From, SubId, MaxItems, ItemIds, jlib:rsm_decode(SubEl)); {get, <<"subscriptions">>} -> get_subscriptions(Host, Node, From, Plugins); {get, <<"affiliations">>} -> get_affiliations(Host, Node, From, Plugins); {get, <<"options">>} -> - SubId = xml:get_attr_s(<<"subid">>, Attrs), - JID = xml:get_attr_s(<<"jid">>, Attrs), + SubId = fxml:get_attr_s(<<"subid">>, Attrs), + JID = fxml:get_attr_s(<<"jid">>, Attrs), get_options(Host, Node, JID, SubId, Lang); {set, <<"options">>} -> - SubId = xml:get_attr_s(<<"subid">>, Attrs), - JID = xml:get_attr_s(<<"jid">>, Attrs), + SubId = fxml:get_attr_s(<<"subid">>, Attrs), + JID = fxml:get_attr_s(<<"jid">>, Attrs), set_options(Host, Node, JID, SubId, Els); _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} @@ -1362,10 +1366,10 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> ). iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) -> #xmlel{children = SubEls} = SubEl, - Action = xml:remove_cdata(SubEls), + Action = fxml:remove_cdata(SubEls), case Action of [#xmlel{name = Name, attrs = Attrs, children = Els}] -> - Node = xml:get_attr_s(<<"node">>, Attrs), + Node = fxml:get_attr_s(<<"node">>, Attrs), case {IQType, Name} of {get, <<"configure">>} -> get_configure(Host, ServerHost, Node, From, Lang); @@ -1380,11 +1384,11 @@ iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) -> {get, <<"subscriptions">>} -> get_subscriptions(Host, Node, From); {set, <<"subscriptions">>} -> - set_subscriptions(Host, Node, From, xml:remove_cdata(Els)); + set_subscriptions(Host, Node, From, fxml:remove_cdata(Els)); {get, <<"affiliations">>} -> get_affiliations(Host, Node, From); {set, <<"affiliations">>} -> - set_affiliations(Host, Node, From, xml:remove_cdata(Els)); + set_affiliations(Host, Node, From, fxml:remove_cdata(Els)); _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} end; @@ -1597,9 +1601,9 @@ find_authorization_response(Packet) -> #xmlel{children = Els} = Packet, XData1 = lists:map(fun (#xmlel{name = <<"x">>, attrs = XAttrs} = XEl) -> - case xml:get_attr_s(<<"xmlns">>, XAttrs) of + case fxml:get_attr_s(<<"xmlns">>, XAttrs) of ?NS_XDATA -> - case xml:get_attr_s(<<"type">>, XAttrs) of + case fxml:get_attr_s(<<"type">>, XAttrs) of <<"cancel">> -> none; _ -> jlib:parse_xdata_submit(XEl) end; @@ -1609,7 +1613,7 @@ find_authorization_response(Packet) -> (_) -> none end, - xml:remove_cdata(Els)), + fxml:remove_cdata(Els)), XData = lists:filter(fun (E) -> E /= none end, XData1), case XData of [invalid] -> @@ -1775,6 +1779,20 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) -> %%<li>nodetree create_node checks if nodeid already exists</li> %%<li>node plugin create_node just sets default affiliation/subscription</li> %%</ul> +-spec(create_node/5 :: + ( + Host :: mod_pubsub:host(), + ServerHost :: binary(), + Node :: <<>> | mod_pubsub:nodeId(), + Owner :: jid(), + Type :: binary()) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} + ). +create_node(Host, ServerHost, Node, Owner, Type) -> + create_node(Host, ServerHost, Node, Owner, Type, all, []). + -spec(create_node/7 :: ( Host :: mod_pubsub:host(), @@ -1788,8 +1806,6 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) -> %%% | {error, xmlel()} ). -create_node(Host, ServerHost, Node, Owner, Type) -> - create_node(Host, ServerHost, Node, Owner, Type, all, []). create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) -> case lists:member(<<"instant-nodes">>, plugin_features(Host, Type)) of true -> @@ -1808,7 +1824,7 @@ create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) -> end; create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> Type = select_type(ServerHost, Host, Node, GivenType), - ParseOptions = case xml:remove_cdata(Configuration) of + ParseOptions = case fxml:remove_cdata(Configuration) of [] -> {result, node_options(Host, Type)}; [#xmlel{name = <<"x">>} = XEl] -> @@ -2668,8 +2684,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 @@ -2998,9 +3014,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 @@ -3170,17 +3186,15 @@ sub_option_can_deliver(_, _, _) -> true. presence_can_deliver(_, false) -> true; presence_can_deliver({User, Server, Resource}, true) -> - case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of + case ejabberd_sm:get_user_present_resources(User, Server) of [] -> false; Ss -> lists:foldl(fun (_, true) -> true; - ({session, _, _, _, undefined, _}, _Acc) -> - false; - ({session, {_, _, R}, _, _, _Priority, _}, _Acc) -> - case Resource of + ({_, R}, _Acc) -> + case Resource of <<>> -> true; R -> true; _ -> false @@ -3294,9 +3308,14 @@ broadcast_publish_item(Host, Node, Nidx, Type, NodeOptions, ItemId, From, Payloa true -> Payload; false -> [] end, + Attrs = case get_option(NodeOptions, itemreply, none) of + owner -> itemAttr(ItemId); %% owner not supported + publisher -> itemAttr(ItemId, {<<"publisher">>, jid:to_string(From)}); + none -> itemAttr(ItemId) + end, Stanza = event_stanza( [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId), + children = [#xmlel{name = <<"item">>, attrs = Attrs, children = Content}]}]), broadcast_stanza(Host, From, Node, Nidx, Type, NodeOptions, SubsByDepth, items, Stanza, true), @@ -3626,7 +3645,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 @@ -3777,7 +3796,9 @@ get_configure_xfields(_Type, Options, Lang, Groups) -> ?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available users">>, presence_based_delivery), ?NLIST_CONFIG_FIELD(<<"The collections with which a node is affiliated">>, - collection)]. + collection), + ?ALIST_CONFIG_FIELD(<<"Whether owners or publisher should receive replies to items">>, + itemreply, [none, owner, publisher])]. %%<p>There are several reasons why the node configuration request might fail:</p> %%<ul> @@ -3788,9 +3809,9 @@ get_configure_xfields(_Type, Options, Lang, Groups) -> %%<li>The specified node does not exist.</li> %%</ul> set_configure(Host, Node, From, Els, Lang) -> - case xml:remove_cdata(Els) of + case fxml:remove_cdata(Els) of [#xmlel{name = <<"x">>} = XEl] -> - case {xml:get_tag_attr_s(<<"xmlns">>, XEl), xml:get_tag_attr_s(<<"type">>, XEl)} of + case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), fxml:get_tag_attr_s(<<"type">>, XEl)} of {?NS_XDATA, <<"cancel">>} -> {result, []}; {?NS_XDATA, <<"submit">>} -> @@ -3931,25 +3952,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 +4016,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. @@ -4033,7 +4044,7 @@ plugin(Host, Name) -> end. plugins(Host) -> - case config(serverhost(Host), plugins) of + case config(Host, plugins) of undefined -> [?STDNODE]; [] -> [?STDNODE]; Plugins -> Plugins @@ -4048,6 +4059,9 @@ subscription_plugin(Host) -> config(ServerHost, Key) -> config(ServerHost, Key, undefined). + +config({_User, Host, _Resource}, Key, Default) -> + config(Host, Key, Default); config(ServerHost, Key, Default) -> case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), Key) of [{Key, Value}] -> Value; @@ -4064,14 +4078,14 @@ select_type(ServerHost, Host, Node, Type) -> _ -> Type end, - ConfiguredTypes = plugins(ServerHost), + ConfiguredTypes = plugins(Host), case lists:member(SelectedType, ConfiguredTypes) of true -> SelectedType; false -> hd(ConfiguredTypes) end. select_type(ServerHost, Host, Node) -> - select_type(ServerHost, Host, Node, hd(plugins(ServerHost))). + select_type(ServerHost, Host, Node, hd(plugins(Host))). feature(<<"rsm">>) -> ?NS_RSM; feature(Feature) -> <<(?NS_PUBSUB)/binary, "#", Feature/binary>>. @@ -4273,6 +4287,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 c7bfd963..56c5f720 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 @@ -185,8 +185,8 @@ process_iq(From, To, IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]} end; (UTag /= false) and (PTag /= false) -> - User = xml:get_tag_cdata(UTag), - Password = xml:get_tag_cdata(PTag), + User = fxml:get_tag_cdata(UTag), + Password = fxml:get_tag_cdata(PTag), try_register_or_set_password(User, Server, Password, From, IQ, SubEl, Source, Lang, not IsCaptchaEnabled); @@ -599,7 +599,7 @@ write_time({{Y, Mo, D}, {H, Mi, S}}) -> [Y, Mo, D, H, Mi, S]). process_xdata_submit(El) -> - case xml:get_subtag(El, <<"x">>) of + case fxml:get_subtag(El, <<"x">>) of false -> error; Xdata -> Fields = jlib:parse_xdata_submit(Xdata), diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl index 35f52f3b..5b7f950a 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, @@ -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 adc2210d..997544b1 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -50,7 +50,7 @@ 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"). @@ -203,11 +203,9 @@ read_roster_version(LUser, LServer, mnesia) -> [] -> error end; read_roster_version(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case odbc_queries:get_roster_version(LServer, Username) - of - {selected, [<<"version">>], [[Version]]} -> Version; - {selected, [<<"version">>], []} -> error + case odbc_queries:get_roster_version(LServer, LUser) of + {selected, [{Version}]} -> Version; + {selected, []} -> error end; read_roster_version(LServer, LUser, riak) -> case ejabberd_riak:get(roster_version, roster_version_schema(), @@ -267,7 +265,7 @@ process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) -> LServer = From#jid.lserver, US = {LUser, LServer}, try {ItemsToSend, VersionToSend} = case - {xml:get_tag_attr(<<"ver">>, SubEl), + {fxml:get_tag_attr(<<"ver">>, SubEl), roster_versioning_enabled(LServer), roster_version_on_db(LServer)} of @@ -369,48 +367,46 @@ get_roster(LUser, LServer, riak) -> _Err -> [] end; get_roster(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case catch odbc_queries:get_roster(LServer, Username) of - {selected, - [<<"username">>, <<"jid">>, <<"nick">>, - <<"subscription">>, <<"ask">>, <<"askmessage">>, - <<"server">>, <<"subscribe">>, <<"type">>], - Items} - when is_list(Items) -> - JIDGroups = case catch - odbc_queries:get_roster_jid_groups(LServer, - Username) - of - {selected, [<<"jid">>, <<"grp">>], JGrps} - when is_list(JGrps) -> - JGrps; - _ -> [] - end, - GroupsDict = lists:foldl(fun ([J, G], Acc) -> - dict:append(J, G, Acc) - end, - dict:new(), JIDGroups), - RItems = lists:flatmap(fun (I) -> - case raw_to_record(LServer, I) of - %% Bad JID in database: - error -> []; - R -> - SJID = - jid:to_string(R#roster.jid), - Groups = case dict:find(SJID, - GroupsDict) - of - {ok, Gs} -> Gs; - error -> [] - end, - [R#roster{groups = Groups}] - end - end, - Items), - RItems; - _ -> [] + case catch odbc_queries:get_roster(LServer, LUser) of + {selected, Items} when is_list(Items) -> + JIDGroups = case catch odbc_queries:get_roster_jid_groups( + LServer, LUser) of + {selected, JGrps} + when is_list(JGrps) -> + JGrps; + _ -> [] + end, + GroupsDict = lists:foldl(fun({J, G}, Acc) -> + dict:append(J, G, Acc) + end, + dict:new(), JIDGroups), + RItems = + lists:flatmap( + fun(I) -> + case raw_to_record(LServer, I) of + %% Bad JID in database: + error -> []; + R -> + SJID = jid:to_string(R#roster.jid), + Groups = case dict:find(SJID, GroupsDict) of + {ok, Gs} -> Gs; + error -> [] + end, + [R#roster{groups = Groups}] + end + end, + Items), + RItems; + _ -> [] end. +set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) -> + transaction( + LServer, + fun() -> + roster_subscribe_t(LUser, LServer, LJID, Item) + end). + item_to_xml(Item) -> Attrs1 = [{<<"jid">>, jid:to_string(Item#roster.jid)}], @@ -453,14 +449,8 @@ get_roster_by_jid_t(LUser, LServer, LJID, mnesia) -> xs = []} end; get_roster_by_jid_t(LUser, LServer, LJID, odbc) -> - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jid:to_string(LJID)), - {selected, - [<<"username">>, <<"jid">>, <<"nick">>, - <<"subscription">>, <<"ask">>, <<"askmessage">>, - <<"server">>, <<"subscribe">>, <<"type">>], - Res} = - odbc_queries:get_roster_by_jid(LServer, Username, SJID), + {selected, Res} = + odbc_queries:get_roster_by_jid(LServer, LUser, jid:to_string(LJID)), case Res of [] -> #roster{usj = {LUser, LServer, LJID}, @@ -509,7 +499,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 +568,10 @@ process_item_els(Item, | Els]) -> case Name of <<"group">> -> - Groups = [xml:get_cdata(SEls) | Item#roster.groups], + Groups = [fxml:get_cdata(SEls) | Item#roster.groups], process_item_els(Item#roster{groups = Groups}, Els); _ -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of <<"">> -> process_item_els(Item, Els); _ -> XEls = [#xmlel{name = Name, attrs = Attrs, @@ -655,14 +645,8 @@ get_subscription_lists(_, LUser, LServer, mnesia) -> _ -> [] end; get_subscription_lists(_, LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case catch odbc_queries:get_roster(LServer, Username) of - {selected, - [<<"username">>, <<"jid">>, <<"nick">>, - <<"subscription">>, <<"ask">>, <<"askmessage">>, - <<"server">>, <<"subscribe">>, <<"type">>], - Items} - when is_list(Items) -> + case catch odbc_queries:get_roster(LServer, LUser) of + {selected, Items} when is_list(Items) -> lists:map(fun(I) -> raw_to_record(LServer, I) end, Items); _ -> [] end; @@ -704,12 +688,9 @@ roster_subscribe_t(LUser, LServer, LJID, Item) -> roster_subscribe_t(_LUser, _LServer, _LJID, Item, mnesia) -> mnesia:write(Item); -roster_subscribe_t(LUser, LServer, LJID, Item, odbc) -> - ItemVals = record_to_string(Item), - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jid:to_string(LJID)), - odbc_queries:roster_subscribe(LServer, Username, SJID, - ItemVals); +roster_subscribe_t(_LUser, _LServer, _LJID, Item, odbc) -> + ItemVals = record_to_row(Item), + odbc_queries:roster_subscribe(ItemVals); roster_subscribe_t(LUser, LServer, _LJID, Item, riak) -> ejabberd_riak:put(Item, roster_schema(), [{'2i', [{<<"us">>, {LUser, LServer}}]}]). @@ -743,30 +724,18 @@ get_roster_by_jid_with_groups_t(LUser, LServer, LJID, end; get_roster_by_jid_with_groups_t(LUser, LServer, LJID, odbc) -> - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jid:to_string(LJID)), - case odbc_queries:get_roster_by_jid(LServer, Username, - SJID) - of - {selected, - [<<"username">>, <<"jid">>, <<"nick">>, - <<"subscription">>, <<"ask">>, <<"askmessage">>, - <<"server">>, <<"subscribe">>, <<"type">>], - [I]} -> - R = raw_to_record(LServer, I), - Groups = case odbc_queries:get_roster_groups(LServer, - Username, SJID) - of - {selected, [<<"grp">>], JGrps} when is_list(JGrps) -> - [JGrp || [JGrp] <- JGrps]; - _ -> [] - end, - R#roster{groups = Groups}; - {selected, - [<<"username">>, <<"jid">>, <<"nick">>, - <<"subscription">>, <<"ask">>, <<"askmessage">>, - <<"server">>, <<"subscribe">>, <<"type">>], - []} -> + SJID = jid:to_string(LJID), + case odbc_queries:get_roster_by_jid(LServer, LUser, SJID) of + {selected, [I]} -> + R = raw_to_record(LServer, I), + Groups = + case odbc_queries:get_roster_groups(LServer, LUser, SJID) of + {selected, JGrps} when is_list(JGrps) -> + [JGrp || {JGrp} <- JGrps]; + _ -> [] + end, + R#roster{groups = Groups}; + {selected, []} -> #roster{usj = {LUser, LServer, LJID}, us = {LUser, LServer}, jid = LJID} end; @@ -988,8 +957,7 @@ remove_user(LUser, LServer, mnesia) -> end, mnesia:transaction(F); remove_user(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - odbc_queries:del_user_roster_t(LServer, Username), + odbc_queries:del_user_roster_t(LServer, LUser), ok; remove_user(LUser, LServer, riak) -> {atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}. @@ -1057,11 +1025,10 @@ update_roster_t(_LUser, _LServer, _LJID, Item, mnesia) -> mnesia:write(Item); update_roster_t(LUser, LServer, LJID, Item, odbc) -> - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jid:to_string(LJID)), - ItemVals = record_to_string(Item), - ItemGroups = groups_to_string(Item), - odbc_queries:update_roster(LServer, Username, SJID, ItemVals, + SJID = jid:to_string(LJID), + ItemVals = record_to_row(Item), + ItemGroups = Item#roster.groups, + odbc_queries:update_roster(LServer, LUser, SJID, ItemVals, ItemGroups); update_roster_t(LUser, LServer, _LJID, Item, riak) -> ejabberd_riak:put(Item, roster_schema(), @@ -1074,15 +1041,14 @@ del_roster_t(LUser, LServer, LJID) -> del_roster_t(LUser, LServer, LJID, mnesia) -> mnesia:delete({roster, {LUser, LServer, LJID}}); del_roster_t(LUser, LServer, LJID, odbc) -> - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jid:to_string(LJID)), - odbc_queries:del_roster(LServer, Username, SJID); + SJID = jid:to_string(LJID), + odbc_queries:del_roster(LServer, LUser, SJID); del_roster_t(LUser, LServer, LJID, riak) -> ejabberd_riak:delete(roster, {LUser, LServer, LJID}). process_item_set_t(LUser, LServer, #xmlel{attrs = Attrs, children = Els}) -> - JID1 = jid:from_string(xml:get_attr_s(<<"jid">>, + JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>, Attrs)), case JID1 of error -> ok; @@ -1177,14 +1143,8 @@ 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) -> + case catch odbc_queries:get_roster(LServer, LUser) of + {selected, Items} when is_list(Items) -> Ls ++ lists:map(fun (R) -> Message = R#roster.askmessage, @@ -1236,12 +1196,9 @@ read_subscription_and_groups(LUser, LServer, LJID, end; read_subscription_and_groups(LUser, LServer, LJID, odbc) -> - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jid:to_string(LJID)), - case catch odbc_queries:get_subscription(LServer, - Username, SJID) - of - {selected, [<<"subscription">>], [[SSubscription]]} -> + SJID = jid:to_string(LJID), + case catch odbc_queries:get_subscription(LServer, LUser, SJID) of + {selected, [{SSubscription}]} -> Subscription = case SSubscription of <<"B">> -> both; <<"T">> -> to; @@ -1249,11 +1206,11 @@ read_subscription_and_groups(LUser, LServer, LJID, _ -> none end, Groups = case catch - odbc_queries:get_rostergroup_by_jid(LServer, Username, + odbc_queries:get_rostergroup_by_jid(LServer, LUser, SJID) of - {selected, [<<"grp">>], JGrps} when is_list(JGrps) -> - [JGrp || [JGrp] <- JGrps]; + {selected, JGrps} when is_list(JGrps) -> + [JGrp || {JGrp} <- JGrps]; _ -> [] end, {Subscription, Groups}; @@ -1290,6 +1247,12 @@ get_jid_info(_, User, Server, JID) -> 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 -> @@ -1339,6 +1302,27 @@ record_to_string(#roster{us = {User, _Server}, [Username, SJID, Nick, SSubscription, SAsk, SAskMessage, <<"N">>, <<"">>, <<"item">>]. +record_to_row( + #roster{us = {LUser, _LServer}, + jid = JID, name = Name, subscription = Subscription, + ask = Ask, askmessage = AskMessage}) -> + SJID = jid:to_string(jid:tolower(JID)), + SSubscription = case Subscription of + both -> <<"B">>; + to -> <<"T">>; + from -> <<"F">>; + none -> <<"N">> + end, + SAsk = case Ask of + subscribe -> <<"S">>; + unsubscribe -> <<"U">>; + both -> <<"B">>; + out -> <<"O">>; + in -> <<"I">>; + none -> <<"N">> + end, + {LUser, SJID, Name, SSubscription, SAsk, AskMessage}. + groups_to_string(#roster{us = {User, _Server}, jid = JID, groups = Groups}) -> Username = ejabberd_odbc:escape(User), @@ -1383,7 +1367,7 @@ update_roster_table() -> groups = [iolist_to_binary(G) || G <- Gs], askmessage = try iolist_to_binary(Ask) catch _:_ -> <<"">> end, - xs = [xml:to_xmlel(X) || X <- Xs]} + xs = [fxml:to_xmlel(X) || X <- Xs]} end); _ -> ?INFO_MSG("Recreating roster table", []), diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index cde0f66b..212a7d47 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -206,11 +206,11 @@ get_rosteritem_name([ModVcard], U, S) -> get_rosteritem_name_vcard([]) -> <<"">>; get_rosteritem_name_vcard([Vcard]) -> - case xml:get_path_s(Vcard, + case fxml:get_path_s(Vcard, [{elem, <<"NICKNAME">>}, cdata]) of <<"">> -> - xml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]); + fxml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]); Nickname -> Nickname end. @@ -1160,7 +1160,7 @@ list_shared_roster_groups(Host, Query, Lang) -> [?INPUTT(<<"submit">>, <<"addnew">>, <<"Add New">>)])])]))])), (?H1GL((?T(<<"Shared Roster Groups">>)), - <<"modsharedroster">>, <<"mod_shared_roster">>)) + <<"mod_shared_roster">>, <<"mod_shared_roster">>)) ++ case Res of ok -> [?XREST(<<"Submitted">>)]; @@ -1254,7 +1254,7 @@ shared_roster_group(Host, Group, Query, Lang) -> <<"20">>, list_to_binary(FDisplayedGroups))])])])])), (?H1GL((?T(<<"Shared Roster Groups">>)), - <<"modsharedroster">>, <<"mod_shared_roster">>)) + <<"mod_shared_roster">>, <<"mod_shared_roster">>)) ++ [?XC(<<"h2">>, <<(?T(<<"Group ">>))/binary, Group/binary>>)] ++ case Res of diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl index a4ac65c1..dd6a7f6d 100644 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@ -3,6 +3,7 @@ %%% Author : Realloc <realloc@realloc.spb.ru> %%% Marcin Owsiany <marcin@owsiany.pl> %%% Evgeniy Khramtsov <ekhramtsov@process-one.net> +%%% Contributor : Mike Kaganski <mikekaganski@hotmail.com> %%% Description : LDAP shared roster management %%% Created : 5 Mar 2005 by Alexey Shchepin <alexey@process-one.net> %%% @@ -26,8 +27,6 @@ %%%------------------------------------------------------------------- -module(mod_shared_roster_ldap). --behaviour(ejabberd_config). - -behaviour(gen_server). -behaviour(gen_mod). @@ -44,19 +43,15 @@ out_subscription/4, mod_opt_type/1, opt_type/1]). -include("ejabberd.hrl"). --include("logger.hrl"). -include("jlib.hrl"). -include("mod_roster.hrl"). - -include("eldap.hrl"). +-define(ERROR_MSG(Fmt, Args), error_logger:error_msg(Fmt, Args)). --define(CACHE_SIZE, 1000). - --define(USER_CACHE_VALIDITY, 300). - --define(GROUP_CACHE_VALIDITY, 300). - --define(LDAP_SEARCH_TIMEOUT, 5). +-define(CACHE_SIZE, 1). +-define(CACHE_VALIDITY, 300). %% in seconds +-define(LDAP_SEARCH_TIMEOUT, 5). %% Timeout for LDAP search queries in seconds +-define(INVALID_SETTING_MSG, "~s is not properly set! ~s will not function."). -record(state, {host = <<"">> :: binary(), @@ -74,7 +69,6 @@ group_attr = <<"">> :: binary(), group_desc = <<"">> :: binary(), user_desc = <<"">> :: binary(), - user_uid = <<"">> :: binary(), uid_format = <<"">> :: binary(), uid_format_re = <<"">> :: binary(), filter = <<"">> :: binary(), @@ -82,25 +76,51 @@ rfilter = <<"">> :: binary(), gfilter = <<"">> :: binary(), auth_check = true :: boolean(), - user_cache_size = ?CACHE_SIZE :: non_neg_integer(), - group_cache_size = ?CACHE_SIZE :: non_neg_integer(), - user_cache_validity = ?USER_CACHE_VALIDITY :: non_neg_integer(), - group_cache_validity = ?GROUP_CACHE_VALIDITY :: non_neg_integer()}). - + %% Group data parameters + group_base = <<"">> :: binary(), + %% - Subgroup of roster filter + %% This filter defines which groups are displayed in the shared roster + %% Valid values are 'all' or "LDAP filter string" or "LDAP filter string containing %g" + shgfilter = <<"">> :: binary(), + shg_attr = <<"">> :: binary(), + %% - Subgroup of group filter + group_is_dn = true :: boolean(), + member_attr = <<"">> :: binary(), + %% User data parameters + member_selection_mode = memberattr_dn :: memberattr_normal | memberattr_dn | + group_children, + %% Algorithm control parameters + subscribe_all = false :: binary(), + roster_cache_size = ?CACHE_SIZE :: non_neg_integer(), + roster_cache_validity = ?CACHE_VALIDITY :: non_neg_integer()}). + +%% If #state.member_selection_mode is memberattr_normal or memberattr_dn, +%% then members is list of member_attr values; +%% if #state.member_selection_mode is group_children, +%% then members is dn of the group (to make it possible to search for its subtree) -record(group_info, {desc, members}). +-record(user_info, {us, name}). + +-record(shared_roster_item, {us, name, groups}). + +% Groups visible to this group +% grp may be atom 'all' or a group name string. +% shgrps is a list containing one or more grp +-record(shg_data, {grp, shgrps}). + %%==================================================================== %% API %%==================================================================== start_link(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?MODULE), gen_server:start_link({local, Proc}, ?MODULE, - [Host, Opts], []). + [Host, Opts], []). start(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?MODULE), ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, - permanent, 1000, worker, [?MODULE]}, + permanent, 1000, worker, [?MODULE]}, supervisor:start_child(ejabberd_sup, ChildSpec). stop(Host) -> @@ -112,109 +132,92 @@ stop(Host) -> %% Hooks %%-------------------------------------------------------------------- get_user_roster(Items, {U, S} = US) -> - SRUsers = get_user_to_groups_map(US, true), - {NewItems1, SRUsersRest} = lists:mapfoldl(fun (Item, - SRUsers1) -> - {_, _, {U1, S1, _}} = - Item#roster.usj, - US1 = {U1, S1}, - case dict:find(US1, - SRUsers1) - of - {ok, _GroupNames} -> - {Item#roster{subscription - = - both, - ask = - none}, - dict:erase(US1, - SRUsers1)}; - error -> - {Item, SRUsers1} - end - end, - SRUsers, Items), + {ok, State} = eldap_utils:get_state(S, ?MODULE), + SRUsers = get_shared_roster(State, US), + %%?ERROR_MSG("XXXXXX get_user_roster: SRUsers=~p", [SRUsers]), + %% If partially subscribed users are also in shared roster, + %% show them as totally subscribed: + {NewItems1, SRUsersRest} = lists:mapfoldl( + fun(Item, SRUsers1) -> + {_, _, {U1, S1, _}} = Item#roster.usj, + US1 = {U1, S1}, + case lists:keytake(US1, #shared_roster_item.us, SRUsers1) of + %%case dict:find(US1, SRUsers1) of + {value, _, SRUsers2} -> {Item#roster{subscription = both, ask = none}, SRUsers2}; + %%{ok, _GroupNames} -> {Item#roster{subscription = both, ask = none}, dict:erase(US1, SRUsers1)}; + false -> {Item, SRUsers1} + end + end, + SRUsers, Items), + %% Export items in roster format: SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}}, - us = US, jid = {U1, S1, <<"">>}, - name = get_user_name(U1, S1), subscription = both, - ask = none, groups = GroupNames} - || {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)], + us = US, + jid = {U1, S1, <<"">>}, + name = Name, + subscription = both, + ask = none, + groups = Groups} || + #shared_roster_item{us = {U1, S1}, name = Name, groups = Groups} <- SRUsersRest], + %% SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}}, + %% us = US, jid = {U1, S1, <<"">>}, + %% name = get_user_name(U1, S1), subscription = both, + %% ask = none, groups = GroupNames} + %% || {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)], SRItems ++ NewItems1. %% This function in use to rewrite the roster entries when moving or renaming %% them in the user contact list. process_item(RosterItem, _Host) -> - USFrom = RosterItem#roster.us, - {User, Server, _Resource} = RosterItem#roster.jid, - USTo = {User, Server}, - Map = get_user_to_groups_map(USFrom, false), - case dict:find(USTo, Map) of - error -> RosterItem; - {ok, []} -> RosterItem; - {ok, GroupNames} - when RosterItem#roster.subscription == remove -> - RosterItem#roster{subscription = both, ask = none, - groups = GroupNames}; - _ -> RosterItem#roster{subscription = both, ask = none} + {ok, State} = eldap_utils:get_state(_Host, ?MODULE), + {User,Server,_Resource} = RosterItem#roster.jid, + USTo = {User,Server}, + SR = get_shared_roster(State, RosterItem#roster.us), + case lists:keysearch(USTo, #shared_roster_item.us, SR) of + false -> + RosterItem; + {value, #shared_roster_item{groups = Groups}} when RosterItem#roster.subscription == remove -> + %% Roster item cannot be removed: + %% We simply reset the original groups: + RosterItem#roster{subscription = both, ask = none, + groups=Groups}; + _ -> + RosterItem#roster{subscription = both, ask = none} end. get_subscription_lists({F, T}, User, Server) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, - DisplayedGroups = get_user_displayed_groups(US), - SRUsers = lists:usort(lists:flatmap(fun (Group) -> - get_group_users(LServer, Group) - end, - DisplayedGroups)), - SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers], + U = jid:nodeprep(User), + S = jid:nameprep(Server), + {ok, State} = eldap_utils:get_state(S, ?MODULE), + SRJIDs = get_presense_subscribers(State, {U, S}), +%?INFO_MSG("SRJIDs: ~p", [SRJIDs]), {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}. -get_jid_info({Subscription, Groups}, User, Server, - JID) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, +get_jid_info({Subscription, Groups}, User, Server, JID) -> + {ok, State} = eldap_utils:get_state(Server, ?MODULE), {U1, S1, _} = jid:tolower(JID), US1 = {U1, S1}, - SRUsers = get_user_to_groups_map(US, false), - case dict:find(US1, SRUsers) of - {ok, GroupNames} -> - NewGroups = if Groups == [] -> GroupNames; - true -> Groups - end, - {both, NewGroups}; - error -> {Subscription, Groups} + SR = get_shared_roster(State, {User, Server}), + case lists:keysearch(US1, #shared_roster_item.us, SR) of + false -> {Subscription, Groups}; + {value, #shared_roster_item{groups = GroupNames}} when Groups == [] -> {both, GroupNames}; + _ -> {both, Groups} end. -in_subscription(Acc, User, Server, JID, Type, - _Reason) -> +in_subscription(Acc, User, Server, JID, Type, _Reason) -> process_subscription(in, User, Server, JID, Type, Acc). out_subscription(User, Server, JID, Type) -> - process_subscription(out, User, Server, JID, Type, - false). - -process_subscription(Direction, User, Server, JID, - _Type, Acc) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, - {U1, S1, _} = - jid:tolower(jid:remove_resource(JID)), + process_subscription(out, User, Server, JID, Type, false). + +process_subscription(Direction, User, Server, JID, _Type, Acc) -> + {ok, State} = eldap_utils:get_state(Server, ?MODULE), + {U1, S1, _} = jid:tolower(JID), US1 = {U1, S1}, - DisplayedGroups = get_user_displayed_groups(US), - SRUsers = lists:usort(lists:flatmap(fun (Group) -> - get_group_users(LServer, Group) - end, - DisplayedGroups)), - case lists:member(US1, SRUsers) of - true -> - case Direction of - in -> {stop, false}; - out -> stop - end; - false -> Acc + SR = get_shared_roster(State, {User, Server}), + case lists:keysearch(US1, #shared_roster_item.us, SR) of + false -> Acc; + _ when Direction == in -> {stop, false}; + _ -> stop end. %%==================================================================== @@ -222,267 +225,509 @@ process_subscription(Direction, User, Server, JID, %%==================================================================== init([Host, Opts]) -> State = parse_options(Host, Opts), - cache_tab:new(shared_roster_ldap_user, - [{max_size, State#state.user_cache_size}, {lru, false}, - {life_time, State#state.user_cache_validity}]), - cache_tab:new(shared_roster_ldap_group, - [{max_size, State#state.group_cache_size}, {lru, false}, - {life_time, State#state.group_cache_validity}]), - ejabberd_hooks:add(roster_get, Host, ?MODULE, - get_user_roster, 70), + if + State#state.roster_cache_size > 0 -> + cache_tab:new(shared_roster_ldap_sr, + [{max_size, State#state.roster_cache_size}, + {lru, false}, % We don't need LRU algorithm + {life_time, State#state.roster_cache_validity}]); + true -> + false + end, + ejabberd_hooks:add(roster_get, Host, + ?MODULE, get_user_roster, 70), ejabberd_hooks:add(roster_in_subscription, Host, - ?MODULE, in_subscription, 30), + ?MODULE, in_subscription, 30), ejabberd_hooks:add(roster_out_subscription, Host, - ?MODULE, out_subscription, 30), + ?MODULE, out_subscription, 30), ejabberd_hooks:add(roster_get_subscription_lists, Host, - ?MODULE, get_subscription_lists, 70), - ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE, - get_jid_info, 70), - ejabberd_hooks:add(roster_process_item, Host, ?MODULE, - process_item, 50), + ?MODULE, get_subscription_lists, 70), + ejabberd_hooks:add(roster_get_jid_info, Host, + ?MODULE, get_jid_info, 70), + ejabberd_hooks:add(roster_process_item, Host, + ?MODULE, process_item, 50), eldap_pool:start_link(State#state.eldap_id, - State#state.servers, State#state.backups, - State#state.port, State#state.dn, - State#state.password, State#state.tls_options), + State#state.servers, + State#state.backups, + State#state.port, + State#state.dn, + State#state.password, + State#state.tls_options), {ok, State}. handle_call(get_state, _From, State) -> {reply, {ok, State}, State}; + handle_call(_Request, _From, State) -> {reply, {error, badarg}, State}. -handle_cast(_Msg, State) -> {noreply, State}. +handle_cast(_Msg, State) -> + {noreply, State}. -handle_info(_Info, State) -> {noreply, State}. +handle_info(_Info, State) -> + {noreply, State}. terminate(_Reason, State) -> Host = State#state.host, - ejabberd_hooks:delete(roster_get, Host, ?MODULE, - get_user_roster, 70), + ejabberd_hooks:delete(roster_get, Host, + ?MODULE, get_user_roster, 70), ejabberd_hooks:delete(roster_in_subscription, Host, - ?MODULE, in_subscription, 30), + ?MODULE, in_subscription, 30), ejabberd_hooks:delete(roster_out_subscription, Host, - ?MODULE, out_subscription, 30), - ejabberd_hooks:delete(roster_get_subscription_lists, - Host, ?MODULE, get_subscription_lists, 70), + ?MODULE, out_subscription, 30), + ejabberd_hooks:delete(roster_get_subscription_lists, Host, + ?MODULE, get_subscription_lists, 70), ejabberd_hooks:delete(roster_get_jid_info, Host, - ?MODULE, get_jid_info, 70), + ?MODULE, get_jid_info, 70), ejabberd_hooks:delete(roster_process_item, Host, - ?MODULE, process_item, 50). + ?MODULE, process_item, 50). -code_change(_OldVsn, State, _Extra) -> {ok, State}. +code_change(_OldVsn, State, _Extra) -> + {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -get_user_to_groups_map({_, Server} = US, SkipUS) -> - DisplayedGroups = get_user_displayed_groups(US), - lists:foldl(fun (Group, Dict1) -> - GroupName = get_group_name(Server, Group), - lists:foldl(fun (Contact, Dict) -> - if SkipUS, Contact == US -> Dict; - true -> - dict:append(Contact, - GroupName, Dict) - end - end, - Dict1, get_group_users(Server, Group)) - end, - dict:new(), DisplayedGroups). - -eldap_search(State, FilterParseArgs, AttributesList) -> - case apply(eldap_filter, parse, FilterParseArgs) of - {ok, EldapFilter} -> - case eldap_pool:search(State#state.eldap_id, - [{base, State#state.base}, - {filter, EldapFilter}, - {timeout, ?LDAP_SEARCH_TIMEOUT}, - {deref_aliases, State#state.deref_aliases}, - {attributes, AttributesList}]) - of - #eldap_search_result{entries = Es} -> - %% A result with entries. Return their list. - Es; - _ -> - %% Something else. Pretend we got no results. - [] - end; - _ -> - %% Filter parsing failed. Pretend we got no results. - [] +do_eldap_search(PoolName, Opts) -> + case eldap_pool:search(PoolName, Opts) of + #eldap_search_result{entries = Es} -> + %% A result with entries. Return their list. + Es; + Err -> + %% Something else. Pretend we got no results. + ?ERROR_MSG("Error searching: ~p ~p", [Err, Opts]), + [] + end. + +%% Pass given Filter or FilterTemplate and SubstList to eldap_filter:parse, +%% and if successful, run LDAP search on the whole subtree of Base, using +%% resulting filter, retrieving given AttributesList. Return the result entries. +%% On any error, print an error message and return an empty list of results. +eldap_search(State, Base, EldapFilter, AttributesList) when is_tuple(EldapFilter) -> + do_eldap_search(State#state.eldap_id, + [{base, Base}, +%% {scope, wholeSubtree} %% This is the default + {filter, EldapFilter}, + {timeout, ?LDAP_SEARCH_TIMEOUT}, + {deref_aliases, State#state.deref_aliases}, + {attributes, AttributesList}]); +%% Filter is string +eldap_search(State, Base, Filter, AttributesList) -> + eldap_search(State, Base, Filter, [], AttributesList). + +eldap_search(State, Base, FilterTemplate, SubstList, AttributesList) -> + case apply(eldap_filter, parse, [eldap_filter:do_sub(FilterTemplate, SubstList)]) of + {ok, EldapFilter} -> + %% Filter parsing succeeded + eldap_search(State, Base, EldapFilter, AttributesList); + Err -> + %% Filter parsing failed. Pretend we got no results. + ?ERROR_MSG("Error parsing filter: ~p", [Err]), + [] end. -get_user_displayed_groups({User, Host}) -> - {ok, State} = eldap_utils:get_state(Host, ?MODULE), - GroupAttr = State#state.group_attr, - Entries = eldap_search(State, - [eldap_filter:do_sub(State#state.rfilter, - [{<<"%u">>, User}])], - [GroupAttr]), - Reply = lists:flatmap(fun (#eldap_entry{attributes = - Attrs}) -> - case Attrs of - [{GroupAttr, ValuesList}] -> ValuesList; - _ -> [] - end - end, - Entries), - lists:usort(Reply). - -get_group_users(Host, Group) -> - {ok, State} = eldap_utils:get_state(Host, ?MODULE), - case cache_tab:dirty_lookup(shared_roster_ldap_group, - {Group, Host}, - fun () -> search_group_info(State, Group) end) - of - {ok, #group_info{members = Members}} - when Members /= undefined -> - Members; - _ -> [] +%% The same as above, but gets the Attributes for the specified DN. +%% Note that this function doesn't honor the State's base DN; +%% TODO: fix this (create a custom check?) +eldap_search_dn(State, DN, EldapFilter, AttributesList) when is_tuple(EldapFilter) -> + do_eldap_search(State#state.eldap_id, + [{scope, baseObject}, + {base, DN}, + {filter, EldapFilter}, + {timeout, ?LDAP_SEARCH_TIMEOUT}, + {deref_aliases, State#state.deref_aliases}, + {attributes, AttributesList}]); +%% Filter is string. +eldap_search_dn(State, DN, Filter, AttributesList) -> + case eldap_filter:parse(Filter) of + {ok, EldapFilter} -> + %% Filter parsing succeeded + eldap_search_dn(State, DN, EldapFilter, AttributesList); + Err -> + %% Filter parsing failed. Pretend we got no results. + ?ERROR_MSG("Error parsing filter: ~p", [Err]), + [] end. -get_group_name(Host, Group) -> - {ok, State} = eldap_utils:get_state(Host, ?MODULE), - case cache_tab:dirty_lookup(shared_roster_ldap_group, - {Group, Host}, - fun () -> search_group_info(State, Group) end) - of - {ok, #group_info{desc = GroupName}} - when GroupName /= undefined -> - GroupName; - _ -> Group +intersection(L1,L2) -> lists:filter(fun(X) -> lists:member(X,L1) end, L2). + +filter_roster(Roster, all) -> Roster; +filter_roster(_, []) -> []; +filter_roster(Roster, IncludeGroups) when is_list(IncludeGroups) -> + lists:foldl( + fun(RosterItem, Acc) -> + case intersection(IncludeGroups, RosterItem#shared_roster_item.groups) of + [] -> Acc; + CommonGroups -> [RosterItem#shared_roster_item{groups=CommonGroups} | Acc] + end + end, + [], Roster). + +get_user_visible_groups(UserGroups, VisibilityMap) -> + lists:foldl( + fun(Group, Acc) -> + case (lists:keysearch(Group, #shg_data.grp, VisibilityMap)) of + {value, #shg_data{shgrps=Gs}} when is_list(Gs) -> Gs ++ Acc; + _ -> Acc + end + end, + UserGroups, UserGroups). + +%% Returns [#shared_roster_item]; +%% Removes the US from returned data +%% If State#state.user_groups_only is 'true', then it removes all users that are not in US's groups, +%% and also removes the groups from the users that the US is not member of. +get_shared_roster(State, {_, Server} = US) -> + case (catch get_full_roster(State, Server)) of + {ok, {VisibilityMap, FullRoster}} -> + %%?ERROR_MSG("XXXXXX get_shared_roster: VMap=~p FullRoster=~p", [VisibilityMap, FullRoster]), + CommonRosterGroups = lists:foldl( + fun(_, all) -> all; + (#shg_data{grp=all, shgrps=all}, _) -> all; + (#shg_data{grp=all, shgrps=Gs}, Acc) when is_list(Gs) -> Gs ++ Acc; + (_, Acc) -> Acc + end, + [], VisibilityMap), + case lists:keytake(US, #shared_roster_item.us, FullRoster) of + false -> filter_roster(FullRoster, CommonRosterGroups); + {value, #shared_roster_item{groups=UserGroups}, Roster2} -> + VisibleGroups = case (CommonRosterGroups) of + all -> all; + CRG -> get_user_visible_groups(UserGroups, VisibilityMap) ++ CRG + end, + filter_roster(Roster2, VisibleGroups) + end; + {'EXIT', CatchData} -> ?ERROR_MSG("Error getting shared roster for user ~p: ~p", [US, CatchData]), []; + _Unexpected -> [] end. -get_user_name(User, Host) -> - {ok, State} = eldap_utils:get_state(Host, ?MODULE), - case cache_tab:dirty_lookup(shared_roster_ldap_user, - {User, Host}, - fun () -> search_user_name(State, User) end) - of - {ok, UserName} -> UserName; - error -> User +%% 1. If user is not a member of shared roster -> no additional subscriptions +%% 2. Else if ldap_subscribe_all is set AND this user is member of a group published to all -> +%% add all registered users of this vhost +%% 3. Else add only those groups this user' groups are published to +get_presense_subscribers(State, {_, Server} = US) -> + case (catch get_full_roster(State, Server)) of + {ok, {VisibilityMap, FullRoster}} -> + case lists:keytake(US, #shared_roster_item.us, FullRoster) of + false -> []; % Case #1 + {value, #shared_roster_item{groups=UserGroups}, Roster2} -> + AllGroups = lists:usort(lists:foldl( + fun(#shared_roster_item{groups=Gs}, Acc) -> Gs ++ Acc end, + [], FullRoster)), + Fun = case (State#state.subscribe_all) of + true -> % Possible case 2 + fun(_, all) -> all; + (#shg_data{grp=all, shgrps=all}, _) -> all; + (#shg_data{grp=all, shgrps=Gs}, Acc) when is_list(Gs) -> + case intersection(Gs, UserGroups) of + [] -> Acc; + _SomeCommon -> all + end; + (#shg_data{grp=G, shgrps=Gs}, Acc) when is_list(Gs) -> + case intersection(Gs, UserGroups) of + [] -> Acc; + _SomeCommon -> [G | Acc] + end; + (_, Acc) -> Acc + end; + _False -> % Case 3 + fun(#shg_data{grp=all}, Acc) -> AllGroups ++ Acc; + (#shg_data{grp=G, shgrps=Gs}, Acc) when is_list(Gs) -> + case intersection(Gs, UserGroups) of + [] -> Acc; + _SomeCommon -> [G | Acc] + end; + (_, Acc) -> Acc + end + end, + PublishTo = lists:foldl(Fun, [], VisibilityMap), + case (PublishTo) of + all -> + [{U1, S1, <<"">>} || {U1, S1} <- ejabberd_auth:get_vh_registered_users(Server)]; + Groups -> + [{U1, S1, <<"">>} || #shared_roster_item{us = {U1, S1}} <- filter_roster(Roster2, UserGroups ++ Groups)] + end + end; + {'EXIT', CatchData} -> ?ERROR_MSG("Error getting shared roster for user ~p: ~p", [US, CatchData]), []; + _Unexpected -> [] end. +get_full_roster(State, Server) when State#state.roster_cache_size > 0 -> + cache_tab:dirty_lookup(shared_roster_ldap_sr, + {Server}, + fun() -> search_roster_info(State, Server) end); +get_full_roster(State, Server) -> + search_roster_info(State, Server). + +search_visible_groups(State, _) when State#state.shgfilter == all -> + [{all, all}]; +search_visible_groups(State, _) when State#state.shgfilter == none -> + [{all, none}]; +search_visible_groups(State, Groups) -> + case (string:str(State#state.shgfilter, "%g")) of + 0 -> [{all, search_group_visible_groups(State, "")}]; + _ -> lists:map( + fun(Group) -> {Group, search_group_visible_groups(State, Group)} end, + Groups) + end. + +search_group_visible_groups(State, Group) -> + Entries = eldap_search(State, State#state.group_base, State#state.shgfilter, [{<<"%g">>, Group}], [State#state.shg_attr]), + lists:usort(lists:flatmap( + fun(#eldap_entry{attributes = Attrs}) -> + case Attrs of + [{_GroupAttr, ValuesList}] -> + ValuesList; + _ -> + [] + end + end, Entries)). + +group2name(all, _) -> all; +group2name(none, _) -> none; +group2name(Group, GroupNames) -> + case (lists:keysearch(Group, 1, GroupNames)) of + {value, {_, Name}} -> Name; + _ -> false + end. + +groups2names(all, _) -> all; +groups2names(none, _) -> none; +groups2names(GroupList, GroupNames) -> + lists:foldl( + fun(G, Acc) -> + case (group2name(G, GroupNames)) of + false -> Acc; + Name -> [Name | Acc] + end + end, + [], GroupList). + +prep_vis_map(VisGroups, GroupNames) -> + lists:foldl( + fun({G, Gs}, Acc) -> + case (group2name(G, GroupNames)) of + false -> Acc; + Name -> [#shg_data{grp=Name, shgrps=groups2names(Gs, GroupNames)} | Acc] + end + end, + [], VisGroups). + +search_roster_info(State, _Host) -> + Entries = eldap_search(State, State#state.group_base, State#state.rfilter, [State#state.group_attr]), + AllGroupIds = lists:usort(lists:flatmap( + fun(#eldap_entry{attributes = Attrs}) -> + case Attrs of + [{_GroupAttr, ValuesList}] -> + ValuesList; + _ -> + [] + end + end, Entries)), + VisGroups = search_visible_groups(State, AllGroupIds), + %%?ERROR_MSG("XXXXXX search_roster_info: VisGroups=~p", [VisGroups]), + + {GroupNames, RosterItems} = case State#state.member_selection_mode of + group_children -> + {GroupNames0, UsersDict0} = lists:foldl( + fun(Group, {GrNAcc, Dict1} = Acc) -> + case search_group_info(State, Group) of + {ok, #group_info{desc = GroupName, members = GroupDN}} -> + {[{Group, GroupName} | GrNAcc], search_users_info(State, GroupDN, GroupName, Dict1)}; + _ -> Acc %% Error getting group data -> No users! + end + end, + {[], dict:new()}, AllGroupIds), + + {GroupNames0, dict:fold( + fun(#user_info{us=US, name=UserName}, Groups, AccIn) -> + [#shared_roster_item{us = US, name = UserName, groups = Groups} | AccIn] + end, + [], UsersDict0)}; + _ -> + {GroupNames1, UsersDict1} = lists:foldl( + fun(Group, {GrNAcc, Dict1} = Acc) -> + case search_group_info(State, Group) of + {ok, #group_info{desc = GroupName, members = Members}} -> + {[{Group, GroupName} | GrNAcc], lists:foldl( + fun(Member, Dict) -> dict:append(Member, GroupName, Dict) end, + Dict1, Members)}; + _ -> Acc %% Error getting group data -> No users! + end + end, + {[], dict:new()}, AllGroupIds), + + %%?ERROR_MSG("UsersDict1: ~p", [UsersDict1]), + %%?ERROR_MSG("GroupNames1: ~p", [GroupNames1]), + + {GroupNames1, dict:fold( + fun(Member, Groups, AccIn) -> + case search_user_info(State, Member) of + {ok, #user_info{us=US, name=UserName}} -> + %%?ERROR_MSG("XXXX found user: ~p ~p ~p", [UserName, Groups, US]), + [#shared_roster_item{us = US, name = UserName, groups = Groups} | AccIn]; + _ -> AccIn + end + end, + [], UsersDict1)} + end, + + VisibilityMap = prep_vis_map(VisGroups, GroupNames), + {ok, {VisibilityMap, RosterItems}}. + search_group_info(State, Group) -> + AttList = case State#state.member_selection_mode of + group_children -> [State#state.group_desc]; + _ -> [State#state.group_desc, State#state.member_attr] + end, + SearchResult = case State#state.group_is_dn of + true -> eldap_search_dn(State, + Group, + State#state.gfilter, + AttList); + _ -> eldap_search(State, + State#state.group_base, + State#state.gfilter, + [{<<"%g">>, Group}], + AttList) + end, + case SearchResult of + [] -> + error; + LDAPEntries -> + case State#state.member_selection_mode of + group_children -> + [#eldap_entry{object_name=Name, attributes=Attrs} | _] = LDAPEntries, + {ok, #group_info{desc = eldap_utils:get_ldap_attr(State#state.group_desc, Attrs), + members = Name}}; + _ -> + {GroupDesc, MembersLists} = lists:foldl( + fun(#eldap_entry{attributes=Attrs}, {DescAcc, MembersAcc}) -> + case {eldap_utils:get_ldap_attr(State#state.group_desc, Attrs), + lists:keysearch(State#state.member_attr, 1, Attrs)} of + {Desc, {value, {GroupMemberAttr, Members}}} + when GroupMemberAttr == State#state.member_attr -> + {Desc, lists:usort(Members ++ MembersAcc)}; + _ -> + {DescAcc, MembersAcc} + end + end, + {Group, []}, LDAPEntries), + {ok, #group_info{desc = GroupDesc, + members = lists:usort(MembersLists)}} + end + end. + +%% Takes the attributes from LDAP user search; +%% returns error or {ok, #user_info} +construct_user(State, Attrs) -> Extractor = case State#state.uid_format_re of - <<"">> -> - fun (UID) -> - catch eldap_utils:get_user_part(UID, - State#state.uid_format) - end; - _ -> - fun (UID) -> - catch get_user_part_re(UID, - State#state.uid_format_re) - end - end, + <<"">> -> fun(UID) -> + catch eldap_utils:get_user_part(UID, State#state.uid_format) + end; + _ -> fun(UID) -> + catch get_user_part_re(UID, State#state.uid_format_re) + end + end, AuthChecker = case State#state.auth_check of - true -> fun ejabberd_auth:is_user_exists/2; - _ -> fun (_U, _S) -> true end - end, + true -> fun ejabberd_auth:is_user_exists/2; + _ -> fun(_U, _S) -> true end + end, Host = State#state.host, - case eldap_search(State, - [eldap_filter:do_sub(State#state.gfilter, - [{<<"%g">>, Group}])], - [State#state.group_attr, State#state.group_desc, - State#state.uid]) - of - [] -> error; - LDAPEntries -> - {GroupDesc, MembersLists} = lists:foldl(fun - (#eldap_entry{attributes = - Attrs}, - {DescAcc, JIDsAcc}) -> - case - {eldap_utils:get_ldap_attr(State#state.group_attr, - Attrs), - eldap_utils:get_ldap_attr(State#state.group_desc, - Attrs), - lists:keysearch(State#state.uid, - 1, - Attrs)} - of - {ID, Desc, - {value, - {GroupMemberAttr, - Members}}} - when ID /= <<"">>, - GroupMemberAttr - == - State#state.uid -> - JIDs = - lists:foldl(fun - ({ok, - UID}, - L) -> - PUID = - 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))}} + + case {eldap_utils:get_ldap_attr(State#state.uid, Attrs), + eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)} of + {UID, Desc} when UID /= "" -> + %% By returning "" get_ldap_attr means "not found" + case Extractor(UID) of + {ok, UID1} -> + UID2 = jid:nodeprep(UID1), + case UID2 of + error -> error; + _ -> + case AuthChecker(UID2, Host) of + true -> {ok, #user_info{us={UID2, Host}, name=Desc}}; + _ -> error + end + end; + _ -> error + end; + _ -> + error end. -search_user_name(State, User) -> - case eldap_search(State, - [eldap_filter:do_sub(State#state.ufilter, - [{<<"%u">>, User}])], - [State#state.user_desc, State#state.user_uid]) - of - [#eldap_entry{attributes = Attrs} | _] -> - case {eldap_utils:get_ldap_attr(State#state.user_uid, - Attrs), - eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)} - of - {UID, Desc} when UID /= <<"">> -> {ok, Desc}; - _ -> error - end; - [] -> error +%% This function is used when State#state.member_selection_mode is group_children +%% Returns UsersDict to which the users (#user_info) of this group are added +%%search_users_info(State, GroupInfo) -> +search_users_info(State, GroupDN, GroupName, UsersDict) -> + SearchResult = eldap_search(State, + GroupDN, + State#state.ufilter, + [State#state.user_desc, State#state.uid]), + lists:foldl( + fun(#eldap_entry{attributes=Attrs}, Dict1) -> + case construct_user(State, Attrs) of + {ok, UserInfo} -> + dict:append(UserInfo, GroupName, Dict1); + _ -> Dict1 + end + end, UsersDict, SearchResult). + +%% This function is used when State#state.member_selection_mode is either memberattr_normal or memberattr_dn +search_user_info(State, User) -> + %%?ERROR_MSG("XXX search_user_info: searching for ~p", [User]), + SearchResult = case State#state.member_selection_mode of + memberattr_dn -> eldap_search_dn(State, + User, + State#state.ufilter, + [State#state.user_desc, State#state.uid]); + memberattr_normal -> eldap_search(State, + State#state.base, + State#state.ufilter, + [{<<"%u">>, User}], + [State#state.user_desc, State#state.uid]) + end, + case SearchResult of + [#eldap_entry{attributes=Attrs}|_] -> + construct_user(State, Attrs); + [] -> + %%?ERROR_MSG("XX not found", []), + error end. %% Getting User ID part by regex pattern get_user_part_re(String, Pattern) -> case catch re:run(String, Pattern) of - {match, Captured} -> - {First, Len} = lists:nth(2, Captured), - Result = str:sub_string(String, First + 1, First + Len), - {ok, Result}; - _ -> {error, badmatch} + {match, Captured} -> + {First, Len} = lists:nth(2,Captured), + Result = string:sub_string(String, First+1, First+Len), + {ok,Result}; + _ -> {error, badmatch} end. +% select(SelectFirst, First, Second) -> +% case SelectFirst of +% true -> First; +% _ -> Second +% end. + +% prepare_filter(Opts, Name, Default, ReturnParsed) -> +% F = gen_mod:get_opt(Name, Opts, Default), +% prepare_filter(F, Name, ReturnParsed). + +% prepare_filter(F, Name, ReturnParsed) -> +% case eldap_filter:parse(F) of +% {ok, EldapFilter} -> +% case ReturnParsed of +% true -> EldapFilter; +% _ -> F +% end; +% _ -> +% ?ERROR_MSG(?INVALID_SETTING_MSG, [atom_to_list(Name), ?MODULE]), +% [] +% end. + parse_options(Host, Opts) -> Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)), Cfg = eldap_utils:get_config(Host, Opts), @@ -516,84 +761,114 @@ parse_options(Host, Opts) -> (false) -> false; (true) -> true end, true), - UserCacheValidity = gen_mod:get_opt( - {ldap_user_cache_validity, Host}, Opts, - fun(I) when is_integer(I), I>0 -> I end, - ?USER_CACHE_VALIDITY), - GroupCacheValidity = gen_mod:get_opt( + RosterCacheValidity = gen_mod:get_opt( {ldap_group_cache_validity, Host}, Opts, fun(I) when is_integer(I), I>0 -> I end, - ?GROUP_CACHE_VALIDITY), - UserCacheSize = gen_mod:get_opt( - {ldap_user_cache_size, Host}, Opts, - fun(I) when is_integer(I), I>0 -> I end, - ?CACHE_SIZE), - GroupCacheSize = gen_mod:get_opt( - {ldap_group_cache_size, Host}, Opts, + ?CACHE_VALIDITY), + RosterCacheSize = gen_mod:get_opt( + {ldap_roster_cache_size, Host}, Opts, fun(I) when is_integer(I), I>0 -> I end, ?CACHE_SIZE), ConfigFilter = gen_mod:get_opt({ldap_filter, Host}, Opts, - fun check_filter/1, <<"">>), + fun check_filter/1, <<"">>), ConfigUserFilter = gen_mod:get_opt({ldap_ufilter, Host}, Opts, - fun check_filter/1, <<"">>), + fun check_filter/1, <<"">>), ConfigGroupFilter = gen_mod:get_opt({ldap_gfilter, Host}, Opts, - fun check_filter/1, <<"">>), + fun check_filter/1, <<"">>), RosterFilter = gen_mod:get_opt({ldap_rfilter, Host}, Opts, - fun check_filter/1, <<"">>), + fun check_filter/1, <<"">>), SubFilter = <<"(&(", UIDAttr/binary, "=", - UIDAttrFormat/binary, ")(", GroupAttr/binary, "=%g))">>, + UIDAttrFormat/binary, ")(", GroupAttr/binary, "=%g))">>, UserSubFilter = case ConfigUserFilter of - <<"">> -> - eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]); - UString -> UString - end, + <<"">> -> + eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]); + UString -> UString + end, GroupSubFilter = case ConfigGroupFilter of - <<"">> -> - eldap_filter:do_sub(SubFilter, - [{<<"%u">>, <<"*">>}]); - GString -> GString + <<"">> -> + eldap_filter:do_sub(SubFilter, + [{<<"%u">>, <<"*">>}]); + GString -> GString end, Filter = case ConfigFilter of - <<"">> -> SubFilter; - _ -> - <<"(&", SubFilter/binary, ConfigFilter/binary, ")">> - end, + <<"">> -> SubFilter; + _ -> + <<"(&", SubFilter/binary, ConfigFilter/binary, ")">> + end, UserFilter = case ConfigFilter of - <<"">> -> UserSubFilter; - _ -> - <<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">> - end, + <<"">> -> UserSubFilter; + _ -> + <<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">> + end, GroupFilter = case ConfigFilter of - <<"">> -> GroupSubFilter; - _ -> - <<"(&", GroupSubFilter/binary, ConfigFilter/binary, - ")">> - end, + <<"">> -> GroupSubFilter; + _ -> + <<"(&", GroupSubFilter/binary, ConfigFilter/binary, + ")">> + end, +%%%%%%%%%%%%% + GroupBase = gen_mod:get_opt(ldap_group_base, Opts, fun iolist_to_binary/1, + Cfg#eldap_config.base), + GroupIsDN = gen_mod:get_opt(ldap_group_is_dn, Opts, + fun(on) -> true; + (off) -> false; + (false) -> false; + (true) -> true + end, true), + MemberSelMode = gen_mod:get_opt(ldap_member_selection_mode, Opts, + fun(memberattr_normal) -> memberattr_normal; + (memberattr_dn) -> memberattr_dn; + (group_children) -> group_children; + (Invalid) -> + ?ERROR_MSG("Invalid ldap_member_selection_mode '~p'. " + "Value 'memberattr_normal' will be used instead.", + [Invalid]) + end, memberattr_normal), + SubscribeAll = gen_mod:get_opt(ldap_subscribe_all, Opts, + fun(on) -> true; + (off) -> false; + (false) -> false; + (true) -> true + end, false), + % MemberIsDN = (MemberSelMode == member_attr_dn) or (MemberSelMode == group_children), + ShGFilter = gen_mod:get_opt(ldap_shgfilter, Opts, + fun(all) -> all; + (none) -> none; + (S) -> check_filter(S) + end, all), + ShGAttr = gen_mod:get_opt(ldap_shgattr, Opts, + fun iolist_to_binary/1, + << GroupAttr/binary >>), +%%%%%% #state{host = Host, eldap_id = Eldap_ID, - servers = Cfg#eldap_config.servers, - backups = Cfg#eldap_config.backups, + servers = Cfg#eldap_config.servers, + backups = Cfg#eldap_config.backups, port = Cfg#eldap_config.port, - tls_options = Cfg#eldap_config.tls_options, - dn = Cfg#eldap_config.dn, + tls_options = Cfg#eldap_config.tls_options, + dn = Cfg#eldap_config.dn, password = Cfg#eldap_config.password, base = Cfg#eldap_config.base, deref_aliases = Cfg#eldap_config.deref_aliases, - uid = UIDAttr, - group_attr = GroupAttr, group_desc = GroupDesc, - user_desc = UserDesc, user_uid = UserUID, - uid_format = UIDAttrFormat, - uid_format_re = UIDAttrFormatRe, filter = Filter, - ufilter = UserFilter, rfilter = RosterFilter, - gfilter = GroupFilter, auth_check = AuthCheck, - user_cache_size = UserCacheSize, - user_cache_validity = UserCacheValidity, - group_cache_size = GroupCacheSize, - group_cache_validity = GroupCacheValidity}. + group_attr = GroupAttr, group_desc = GroupDesc, + user_desc = UserDesc, uid = UserUID, + uid_format = UIDAttrFormat, + uid_format_re = UIDAttrFormatRe, filter = Filter, + ufilter = UserFilter, rfilter = RosterFilter, + gfilter = GroupFilter, auth_check = AuthCheck, + group_base = GroupBase, + member_attr = UIDAttr, + member_selection_mode = MemberSelMode, + group_is_dn = GroupIsDN, + shgfilter = ShGFilter, + shg_attr = ShGAttr, + subscribe_all = SubscribeAll, + roster_cache_size = RosterCacheSize, + roster_cache_validity = RosterCacheValidity}. check_filter(F) -> - NewF = iolist_to_binary(F), - {ok, _} = eldap_filter:parse(NewF), - NewF. + NewF = iolist_to_binary(F), + {ok, _} = eldap_filter:parse(NewF), + NewF. mod_opt_type(deref_aliases) -> fun (never) -> never; @@ -661,6 +936,20 @@ mod_opt_type(ldap_user_cache_validity) -> fun (I) when is_integer(I), I > 0 -> I end; mod_opt_type(ldap_userdesc) -> fun iolist_to_binary/1; mod_opt_type(ldap_useruid) -> fun iolist_to_binary/1; +mod_opt_type(ldap_group_base) -> fun iolist_to_binary/1; +mod_opt_type(ldap_group_is_dn) -> fun(B) when is_boolean(B) -> B end; +mod_opt_type(ldap_member_selection_mode) -> + fun(memberattr_normal) -> memberattr_normal; + (memberattr_dn) -> memberattr_dn; + (group_children) -> group_children + end; +mod_opt_type(ldap_subscribe_all) -> fun(B) when is_boolean(B) -> B end; +mod_opt_type(ldap_shgfilter) -> + fun(all) -> all; + (none) -> none; + (S) -> check_filter(S) + end; +mod_opt_type(ldap_shgattr) -> fun iolist_to_binary/1; mod_opt_type(_) -> [ldap_auth_check, ldap_filter, ldap_gfilter, ldap_group_cache_size, ldap_group_cache_validity, @@ -672,7 +961,9 @@ mod_opt_type(_) -> ldap_deref_aliases, ldap_encrypt, ldap_password, ldap_port, ldap_rootdn, ldap_servers, ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth, - ldap_tls_verify]. + ldap_tls_verify, ldap_group_base, ldap_group_is_dn, + ldap_member_selection_mode, ldap_subscribe_all, + ldap_shgfilter, ldap_shgattr]. opt_type(ldap_filter) -> fun check_filter/1; opt_type(ldap_gfilter) -> fun check_filter/1; diff --git a/src/mod_stats.erl b/src/mod_stats.erl index c9f659d3..0328aec3 100644 --- a/src/mod_stats.erl +++ b/src/mod_stats.erl @@ -57,7 +57,7 @@ process_local_iq(_From, To, IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; get -> #xmlel{children = Els} = SubEl, - Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl), + Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl), <<"/">>), Names = get_names(Els, []), case get_local_stats(To#jid.server, Node, Names) of @@ -76,7 +76,7 @@ get_names([], Res) -> Res; get_names([#xmlel{name = <<"stat">>, attrs = Attrs} | Els], Res) -> - Name = xml:get_attr_s(<<"name">>, Attrs), + Name = fxml:get_attr_s(<<"name">>, Attrs), case Name of <<"">> -> get_names(Els, Res); _ -> get_names(Els, [Name | Res]) diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index db19b557..256dc5de 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -25,6 +25,8 @@ -module(mod_vcard). +-compile([{parse_transform, ejabberd_sql_pt}]). + -author('alexey@process-one.net'). -protocol({xep, 54, '1.2'}). @@ -35,10 +37,11 @@ -export([start/2, init/3, stop/1, get_sm_features/5, process_local_iq/3, process_sm_iq/3, reindex_vcards/0, remove_user/2, export/1, import/1, import/3, - mod_opt_type/1]). + mod_opt_type/1, set_vcard/3]). -include("ejabberd.hrl"). -include("logger.hrl"). +-include("ejabberd_sql_pt.hrl"). -include("jlib.hrl"). @@ -102,7 +105,7 @@ init(Host, ServerHost, Search) -> case Search of false -> loop(Host, ServerHost); _ -> - ejabberd_router:register_route(Host), + ejabberd_router:register_route(Host, ServerHost), loop(Host, ServerHost) end. @@ -212,14 +215,13 @@ get_vcard(LUser, LServer, mnesia) -> {aborted, _Reason} -> error end; get_vcard(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case catch odbc_queries:get_vcard(LServer, Username) of - {selected, [<<"vcard">>], [[SVCARD]]} -> - case xml_stream:parse_element(SVCARD) of + case catch odbc_queries:get_vcard(LServer, LUser) of + {selected, [{SVCARD}]} -> + case fxml_stream:parse_element(SVCARD) of {error, _Reason} -> error; VCARD -> [VCARD] end; - {selected, [<<"vcard">>], []} -> []; + {selected, []} -> []; _ -> error end; get_vcard(LUser, LServer, riak) -> @@ -233,29 +235,29 @@ get_vcard(LUser, LServer, riak) -> end. set_vcard(User, LServer, VCARD) -> - FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]), - Family = xml:get_path_s(VCARD, + FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]), + Family = fxml:get_path_s(VCARD, [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]), - Given = xml:get_path_s(VCARD, + Given = fxml:get_path_s(VCARD, [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]), - Middle = xml:get_path_s(VCARD, + Middle = fxml:get_path_s(VCARD, [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]), - Nickname = xml:get_path_s(VCARD, + Nickname = fxml:get_path_s(VCARD, [{elem, <<"NICKNAME">>}, cdata]), - BDay = xml:get_path_s(VCARD, + BDay = fxml:get_path_s(VCARD, [{elem, <<"BDAY">>}, cdata]), - CTRY = xml:get_path_s(VCARD, + CTRY = fxml:get_path_s(VCARD, [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]), - Locality = xml:get_path_s(VCARD, + Locality = fxml:get_path_s(VCARD, [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>}, cdata]), - EMail1 = xml:get_path_s(VCARD, + EMail1 = fxml:get_path_s(VCARD, [{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]), - EMail2 = xml:get_path_s(VCARD, + EMail2 = fxml:get_path_s(VCARD, [{elem, <<"EMAIL">>}, cdata]), - OrgName = xml:get_path_s(VCARD, + OrgName = fxml:get_path_s(VCARD, [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]), - OrgUnit = xml:get_path_s(VCARD, + OrgUnit = fxml:get_path_s(VCARD, [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]), EMail = case EMail1 of <<"">> -> EMail2; @@ -336,39 +338,14 @@ set_vcard(User, LServer, VCARD) -> {<<"orgunit">>, OrgUnit}, {<<"lorgunit">>, LOrgUnit}]}]); odbc -> - Username = ejabberd_odbc:escape(User), - LUsername = ejabberd_odbc:escape(LUser), - SVCARD = - ejabberd_odbc:escape(xml:element_to_binary(VCARD)), - SFN = ejabberd_odbc:escape(FN), - SLFN = ejabberd_odbc:escape(LFN), - SFamily = ejabberd_odbc:escape(Family), - SLFamily = ejabberd_odbc:escape(LFamily), - SGiven = ejabberd_odbc:escape(Given), - SLGiven = ejabberd_odbc:escape(LGiven), - SMiddle = ejabberd_odbc:escape(Middle), - SLMiddle = ejabberd_odbc:escape(LMiddle), - SNickname = ejabberd_odbc:escape(Nickname), - SLNickname = ejabberd_odbc:escape(LNickname), - SBDay = ejabberd_odbc:escape(BDay), - SLBDay = ejabberd_odbc:escape(LBDay), - SCTRY = ejabberd_odbc:escape(CTRY), - SLCTRY = ejabberd_odbc:escape(LCTRY), - SLocality = ejabberd_odbc:escape(Locality), - SLLocality = ejabberd_odbc:escape(LLocality), - SEMail = ejabberd_odbc:escape(EMail), - SLEMail = ejabberd_odbc:escape(LEMail), - SOrgName = ejabberd_odbc:escape(OrgName), - SLOrgName = ejabberd_odbc:escape(LOrgName), - SOrgUnit = ejabberd_odbc:escape(OrgUnit), - SLOrgUnit = ejabberd_odbc:escape(LOrgUnit), - odbc_queries:set_vcard(LServer, LUsername, SBDay, SCTRY, - SEMail, SFN, SFamily, SGiven, SLBDay, - SLCTRY, SLEMail, SLFN, SLFamily, - SLGiven, SLLocality, SLMiddle, - SLNickname, SLOrgName, SLOrgUnit, - SLocality, SMiddle, SNickname, SOrgName, - SOrgUnit, SVCARD, Username) + SVCARD = fxml:element_to_binary(VCARD), + odbc_queries:set_vcard(LServer, LUser, BDay, CTRY, + EMail, FN, Family, Given, LBDay, + LCTRY, LEMail, LFN, LFamily, + LGiven, LLocality, LMiddle, + LNickname, LOrgName, LOrgUnit, + Locality, Middle, Nickname, OrgName, + OrgUnit, SVCARD, User) end, ejabberd_hooks:run(vcard_set, LServer, [LUser, LServer, VCARD]) @@ -587,7 +564,7 @@ find_xdata_el1([]) -> false; find_xdata_el1([#xmlel{name = Name, attrs = Attrs, children = SubEls} | Els]) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_XDATA -> #xmlel{name = Name, attrs = Attrs, children = SubEls}; _ -> find_xdata_el1(Els) @@ -862,27 +839,27 @@ set_vcard_t(R, _) -> US = R#vcard.us, User = US, VCARD = R#vcard.vcard, - FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]), - Family = xml:get_path_s(VCARD, + FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]), + Family = fxml:get_path_s(VCARD, [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]), - Given = xml:get_path_s(VCARD, + Given = fxml:get_path_s(VCARD, [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]), - Middle = xml:get_path_s(VCARD, + Middle = fxml:get_path_s(VCARD, [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]), - Nickname = xml:get_path_s(VCARD, + Nickname = fxml:get_path_s(VCARD, [{elem, <<"NICKNAME">>}, cdata]), - BDay = xml:get_path_s(VCARD, + BDay = fxml:get_path_s(VCARD, [{elem, <<"BDAY">>}, cdata]), - CTRY = xml:get_path_s(VCARD, + CTRY = fxml:get_path_s(VCARD, [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]), - Locality = xml:get_path_s(VCARD, + Locality = fxml:get_path_s(VCARD, [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>}, cdata]), - EMail = xml:get_path_s(VCARD, + EMail = fxml:get_path_s(VCARD, [{elem, <<"EMAIL">>}, cdata]), - OrgName = xml:get_path_s(VCARD, + OrgName = fxml:get_path_s(VCARD, [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]), - OrgUnit = xml:get_path_s(VCARD, + OrgUnit = fxml:get_path_s(VCARD, [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]), {LUser, _LServer} = US, LFN = string2lower(FN), @@ -929,12 +906,14 @@ remove_user(LUser, LServer, mnesia) -> end, mnesia:transaction(F); remove_user(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - ejabberd_odbc:sql_transaction(LServer, - [[<<"delete from vcard where username='">>, - Username, <<"';">>], - [<<"delete from vcard_search where lusername='">>, - Username, <<"';">>]]); + ejabberd_odbc:sql_transaction( + LServer, + fun() -> + ejabberd_odbc:sql_query_t( + ?SQL("delete from vcard where username=%(LUser)s")), + ejabberd_odbc:sql_query_t( + ?SQL("delete from vcard_search where lusername=%(LUser)s")) + end); remove_user(LUser, LServer, riak) -> {atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}. @@ -952,7 +931,7 @@ update_vcard_table() -> fun(#vcard{us = {U, S}, vcard = El} = R) -> R#vcard{us = {iolist_to_binary(U), iolist_to_binary(S)}, - vcard = xml:to_xmlel(El)} + vcard = fxml:to_xmlel(El)} end); _ -> ?INFO_MSG("Recreating vcard table", []), @@ -991,7 +970,7 @@ export(_Server) -> when LServer == Host -> Username = ejabberd_odbc:escape(LUser), SVCARD = - ejabberd_odbc:escape(xml:element_to_binary(VCARD)), + ejabberd_odbc:escape(fxml:element_to_binary(VCARD)), [[<<"delete from vcard where username='">>, Username, <<"';">>], [<<"insert into vcard(username, vcard) values ('">>, Username, <<"', '">>, SVCARD, <<"');">>]]; @@ -1064,7 +1043,7 @@ export(_Server) -> import(LServer) -> [{<<"select username, vcard from vcard;">>, fun([LUser, SVCard]) -> - #xmlel{} = VCARD = xml_stream:parse_element(SVCard), + #xmlel{} = VCARD = fxml_stream:parse_element(SVCard), #vcard{us = {LUser, LServer}, vcard = VCARD} end}, {<<"select username, lusername, fn, lfn, family, lfamily, " @@ -1095,29 +1074,29 @@ import(_LServer, mnesia, #vcard{} = VCard) -> import(_LServer, mnesia, #vcard_search{} = S) -> mnesia:dirty_write(S); import(_LServer, riak, #vcard{us = {LUser, _}, vcard = El} = VCard) -> - FN = xml:get_path_s(El, [{elem, <<"FN">>}, cdata]), - Family = xml:get_path_s(El, + FN = fxml:get_path_s(El, [{elem, <<"FN">>}, cdata]), + Family = fxml:get_path_s(El, [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]), - Given = xml:get_path_s(El, + Given = fxml:get_path_s(El, [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]), - Middle = xml:get_path_s(El, + Middle = fxml:get_path_s(El, [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]), - Nickname = xml:get_path_s(El, + Nickname = fxml:get_path_s(El, [{elem, <<"NICKNAME">>}, cdata]), - BDay = xml:get_path_s(El, + BDay = fxml:get_path_s(El, [{elem, <<"BDAY">>}, cdata]), - CTRY = xml:get_path_s(El, + CTRY = fxml:get_path_s(El, [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]), - Locality = xml:get_path_s(El, + Locality = fxml:get_path_s(El, [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>}, cdata]), - EMail1 = xml:get_path_s(El, + EMail1 = fxml:get_path_s(El, [{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]), - EMail2 = xml:get_path_s(El, + EMail2 = fxml:get_path_s(El, [{elem, <<"EMAIL">>}, cdata]), - OrgName = xml:get_path_s(El, + OrgName = fxml:get_path_s(El, [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]), - OrgUnit = xml:get_path_s(El, + OrgUnit = fxml:get_path_s(El, [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]), EMail = case EMail1 of <<"">> -> EMail2; diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index b24356d8..98aaf936 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}. @@ -723,7 +723,7 @@ find_xdata_el1([]) -> false; find_xdata_el1([#xmlel{name = Name, attrs = Attrs, children = SubEls} | Els]) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_XDATA -> #xmlel{name = Name, attrs = Attrs, children = SubEls}; _ -> find_xdata_el1(Els) diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl index 96ee09d8..18fb09a5 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -56,7 +56,7 @@ stop(Host) -> update_presence(#xmlel{name = <<"presence">>, attrs = Attrs} = Packet, User, Host) -> - case xml:get_attr_s(<<"type">>, Attrs) of + case fxml:get_attr_s(<<"type">>, Attrs) of <<>> -> presence_with_xupdate(Packet, User, Host); _ -> Packet end; @@ -64,7 +64,7 @@ update_presence(Packet, _User, _Host) -> Packet. vcard_set(LUser, LServer, VCARD) -> US = {LUser, LServer}, - case xml:get_path_s(VCARD, + case fxml:get_path_s(VCARD, [{elem, <<"PHOTO">>}, {elem, <<"BINVAL">>}, cdata]) of <<>> -> remove_xupdate(LUser, LServer); diff --git a/src/node_flat_odbc.erl b/src/node_flat_odbc.erl index 794d3f98..e3c57938 100644 --- a/src/node_flat_odbc.erl +++ b/src/node_flat_odbc.erl @@ -824,7 +824,7 @@ set_item(Item) -> {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_odbc:escape(str:join([fxml:element_to_binary(X) || X<-Payload], <<>>)), S = fun ({T1, T2, T3}) -> str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>) end, @@ -1041,7 +1041,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_mix.erl b/src/node_mix.erl new file mode 100644 index 00000000..b0410a8c --- /dev/null +++ b/src/node_mix.erl @@ -0,0 +1,167 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net> +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net> +%%%------------------------------------------------------------------- +-module(node_mix). + +-behaviour(gen_pubsub_node). + +%% API +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/7, get_items/3, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1]). + +-include("pubsub.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(Host, ServerHost, Opts) -> + node_flat:init(Host, ServerHost, Opts). + +terminate(Host, ServerHost) -> + node_flat:terminate(Host, ServerHost). + +options() -> + [{deliver_payloads, true}, + {notify_config, false}, + {notify_delete, false}, + {notify_retract, true}, + {purge_offline, false}, + {persist_items, true}, + {max_items, ?MAXITEMS}, + {subscribe, true}, + {access_model, open}, + {roster_groups_allowed, []}, + {publish_model, open}, + {notification_type, headline}, + {max_payload_size, ?MAX_PAYLOAD_SIZE}, + {send_last_published_item, never}, + {deliver_notifications, true}, + {broadcast_all_resources, true}, + {presence_based_delivery, false}]. + +features() -> + [<<"create-nodes">>, + <<"delete-nodes">>, + <<"delete-items">>, + <<"instant-nodes">>, + <<"item-ids">>, + <<"outcast-affiliation">>, + <<"persistent-items">>, + <<"publish">>, + <<"purge-nodes">>, + <<"retract-items">>, + <<"retrieve-affiliations">>, + <<"retrieve-items">>, + <<"retrieve-subscriptions">>, + <<"subscribe">>, + <<"subscription-notifications">>]. + +create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> + node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + +create_node(Nidx, Owner) -> + node_flat:create_node(Nidx, Owner). + +delete_node(Removed) -> + node_flat:delete_node(Removed). + +subscribe_node(Nidx, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, + PresenceSubscription, RosterGroup, Options). + +unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> + node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). + +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> + node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). + +remove_extra_items(Nidx, MaxItems, ItemIds) -> + node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). + +delete_item(Nidx, Publisher, PublishModel, ItemId) -> + node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId). + +purge_node(Nidx, Owner) -> + node_flat:purge_node(Nidx, Owner). + +get_entity_affiliations(Host, Owner) -> + node_flat:get_entity_affiliations(Host, Owner). + +get_node_affiliations(Nidx) -> + node_flat:get_node_affiliations(Nidx). + +get_affiliation(Nidx, Owner) -> + node_flat:get_affiliation(Nidx, Owner). + +set_affiliation(Nidx, Owner, Affiliation) -> + node_flat:set_affiliation(Nidx, Owner, Affiliation). + +get_entity_subscriptions(Host, Owner) -> + node_flat:get_entity_subscriptions(Host, Owner). + +get_node_subscriptions(Nidx) -> + node_flat:get_node_subscriptions(Nidx). + +get_subscriptions(Nidx, Owner) -> + node_flat:get_subscriptions(Nidx, Owner). + +set_subscriptions(Nidx, Owner, Subscription, SubId) -> + node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId). + +get_pending_nodes(Host, Owner) -> + node_flat:get_pending_nodes(Host, Owner). + +get_states(Nidx) -> + node_flat:get_states(Nidx). + +get_state(Nidx, JID) -> + node_flat:get_state(Nidx, JID). + +set_state(State) -> + node_flat:set_state(State). + +get_items(Nidx, From, RSM) -> + node_flat:get_items(Nidx, From, RSM). + +get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> + node_flat:get_items(Nidx, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, RSM). + +get_item(Nidx, ItemId) -> + node_flat:get_item(Nidx, ItemId). + +get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_flat:get_item(Nidx, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). + +set_item(Item) -> + node_flat:set_item(Item). + +get_item_name(Host, Node, Id) -> + node_flat:get_item_name(Host, Node, Id). + +node_to_path(Node) -> + node_flat:node_to_path(Node). + +path_to_node(Path) -> + node_flat:path_to_node(Path). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/node_mix_odbc.erl b/src/node_mix_odbc.erl new file mode 100644 index 00000000..e7cc6883 --- /dev/null +++ b/src/node_mix_odbc.erl @@ -0,0 +1,170 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net> +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net> +%%%------------------------------------------------------------------- +-module(node_mix_odbc). + +-behaviour(gen_pubsub_node). + +%% API +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/7, get_items/3, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1, get_entity_subscriptions_for_send_last/2]). + +-include("pubsub.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(Host, ServerHost, Opts) -> + node_flat_odbc:init(Host, ServerHost, Opts). + +terminate(Host, ServerHost) -> + node_flat_odbc:terminate(Host, ServerHost). + +options() -> + [{deliver_payloads, true}, + {notify_config, false}, + {notify_delete, false}, + {notify_retract, true}, + {purge_offline, false}, + {persist_items, true}, + {max_items, ?MAXITEMS}, + {subscribe, true}, + {access_model, open}, + {roster_groups_allowed, []}, + {publish_model, open}, + {notification_type, headline}, + {max_payload_size, ?MAX_PAYLOAD_SIZE}, + {send_last_published_item, never}, + {deliver_notifications, true}, + {broadcast_all_resources, true}, + {presence_based_delivery, false}]. + +features() -> + [<<"create-nodes">>, + <<"delete-nodes">>, + <<"delete-items">>, + <<"instant-nodes">>, + <<"item-ids">>, + <<"outcast-affiliation">>, + <<"persistent-items">>, + <<"publish">>, + <<"purge-nodes">>, + <<"retract-items">>, + <<"retrieve-affiliations">>, + <<"retrieve-items">>, + <<"retrieve-subscriptions">>, + <<"subscribe">>, + <<"subscription-notifications">>]. + +create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> + node_flat_odbc:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + +create_node(Nidx, Owner) -> + node_flat_odbc:create_node(Nidx, Owner). + +delete_node(Removed) -> + node_flat_odbc:delete_node(Removed). + +subscribe_node(Nidx, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, + PresenceSubscription, RosterGroup, Options). + +unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> + node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId). + +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> + node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). + +remove_extra_items(Nidx, MaxItems, ItemIds) -> + node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds). + +delete_item(Nidx, Publisher, PublishModel, ItemId) -> + node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId). + +purge_node(Nidx, Owner) -> + node_flat_odbc:purge_node(Nidx, Owner). + +get_entity_affiliations(Host, Owner) -> + node_flat_odbc:get_entity_affiliations(Host, Owner). + +get_node_affiliations(Nidx) -> + node_flat_odbc:get_node_affiliations(Nidx). + +get_affiliation(Nidx, Owner) -> + node_flat_odbc:get_affiliation(Nidx, Owner). + +set_affiliation(Nidx, Owner, Affiliation) -> + node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation). + +get_entity_subscriptions(Host, Owner) -> + node_flat_odbc:get_entity_subscriptions(Host, Owner). + +get_node_subscriptions(Nidx) -> + node_flat_odbc:get_node_subscriptions(Nidx). + +get_subscriptions(Nidx, Owner) -> + node_flat_odbc:get_subscriptions(Nidx, Owner). + +set_subscriptions(Nidx, Owner, Subscription, SubId) -> + node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId). + +get_pending_nodes(Host, Owner) -> + node_flat_odbc:get_pending_nodes(Host, Owner). + +get_states(Nidx) -> + node_flat_odbc:get_states(Nidx). + +get_state(Nidx, JID) -> + node_flat_odbc:get_state(Nidx, JID). + +set_state(State) -> + node_flat_odbc:set_state(State). + +get_items(Nidx, From, RSM) -> + node_flat_odbc:get_items(Nidx, From, RSM). + +get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> + node_flat_odbc:get_items(Nidx, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, RSM). + +get_item(Nidx, ItemId) -> + node_flat_odbc:get_item(Nidx, ItemId). + +get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_flat_odbc:get_item(Nidx, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). + +set_item(Item) -> + node_flat_odbc:set_item(Item). + +get_item_name(Host, Node, Id) -> + node_flat_odbc:get_item_name(Host, Node, Id). + +node_to_path(Node) -> + node_flat_odbc:node_to_path(Node). + +path_to_node(Path) -> + node_flat_odbc:path_to_node(Path). + +get_entity_subscriptions_for_send_last(Host, Owner) -> + node_flat_odbc:get_entity_subscriptions_for_send_last(Host, Owner). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/node_pep.erl b/src/node_pep.erl index 726146b9..da032539 100644 --- a/src/node_pep.erl +++ b/src/node_pep.erl @@ -257,7 +257,7 @@ complain_if_modcaps_disabled(ServerHost) -> false -> ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub " "of host ~p. This plugin requires mod_caps " - "to be enabled, but it isn't.", + "but it does not seems enabled, please check config.", [ServerHost]); true -> ok end. diff --git a/src/odbc_queries.erl b/src/odbc_queries.erl index 2f488a0b..c12931c6 100644 --- a/src/odbc_queries.erl +++ b/src/odbc_queries.erl @@ -25,6 +25,8 @@ -module(odbc_queries). +-compile([{parse_transform, ejabberd_sql_pt}]). + -behaviour(ejabberd_config). -author("mremond@process-one.net"). @@ -40,7 +42,7 @@ get_roster_groups/3, del_user_roster_t/2, get_roster_by_jid/3, get_rostergroup_by_jid/3, del_roster/3, del_roster_sql/2, update_roster/5, - update_roster_sql/4, roster_subscribe/4, + update_roster_sql/4, roster_subscribe/1, get_subscription/3, set_private_data/4, set_private_data_sql/3, get_private_data/3, get_private_data/2, del_user_private_storage/2, @@ -54,12 +56,13 @@ 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, + 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 @@ -119,95 +122,92 @@ update(LServer, Table, Fields, Vals, Where) -> sql_transaction(LServer, F) -> ejabberd_odbc:sql_transaction(LServer, F). -get_last(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"select seconds, state from last where " - "username='">>, - Username, <<"'">>]). +get_last(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(seconds)d, @(state)s from last" + " where username=%(LUser)s")). -set_last_t(LServer, Username, Seconds, State) -> - update(LServer, <<"last">>, - [<<"username">>, <<"seconds">>, <<"state">>], - [Username, Seconds, State], - [<<"username='">>, Username, <<"'">>]). +set_last_t(LServer, LUser, TimeStamp, Status) -> + ?SQL_UPSERT(LServer, "last", + ["!username=%(LUser)s", + "seconds=%(TimeStamp)d", + "state=%(Status)s"]). -del_last(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"delete from last where username='">>, Username, - <<"'">>]). +del_last(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("delete from last where username=%(LUser)s")). -get_password(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"select password from users where username='">>, - Username, <<"';">>]). +get_password(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(password)s from users where username=%(LUser)s")). -get_password_scram(LServer, Username) -> +get_password_scram(LServer, LUser) -> ejabberd_odbc:sql_query( LServer, - [<<"select password, serverkey, salt, iterationcount from users where " - "username='">>, Username, <<"';">>]). - -set_password_t(LServer, Username, Pass) -> - ejabberd_odbc:sql_transaction(LServer, - fun () -> - update_t(<<"users">>, - [<<"username">>, - <<"password">>], - [Username, Pass], - [<<"username='">>, Username, - <<"'">>]) - end). - -set_password_scram_t(LServer, Username, + ?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d" + " from users" + " where username=%(LUser)s")). + +set_password_t(LServer, LUser, Password) -> + ejabberd_odbc:sql_transaction( + LServer, + fun () -> + ?SQL_UPSERT_T( + "users", + ["!username=%(LUser)s", + "password=%(Password)s"]) + end). + +set_password_scram_t(LServer, LUser, StoredKey, ServerKey, Salt, IterationCount) -> - ejabberd_odbc:sql_transaction(LServer, - fun () -> - update_t(<<"users">>, - [<<"username">>, - <<"password">>, - <<"serverkey">>, - <<"salt">>, - <<"iterationcount">>], - [Username, StoredKey, - ServerKey, Salt, - IterationCount], - [<<"username='">>, Username, - <<"'">>]) - end). - -add_user(LServer, Username, Pass) -> - ejabberd_odbc:sql_query(LServer, - [<<"insert into users(username, password) " - "values ('">>, - Username, <<"', '">>, Pass, <<"');">>]). + ejabberd_odbc:sql_transaction( + LServer, + fun () -> + ?SQL_UPSERT_T( + "users", + ["!username=%(LUser)s", + "password=%(StoredKey)s", + "serverkey=%(ServerKey)s", + "salt=%(Salt)s", + "iterationcount=%(IterationCount)d"]) + end). + +add_user(LServer, LUser, Password) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("insert into users(username, password) " + "values (%(LUser)s, %(Password)s)")). -add_user_scram(LServer, Username, +add_user_scram(LServer, LUser, StoredKey, ServerKey, Salt, IterationCount) -> - ejabberd_odbc:sql_query(LServer, - [<<"insert into users(username, password, serverkey, salt, iterationcount) " - "values ('">>, - Username, <<"', '">>, StoredKey, <<"', '">>, - ServerKey, <<"', '">>, - Salt, <<"', '">>, - IterationCount, <<"');">>]). - -del_user(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"delete from users where username='">>, Username, - <<"';">>]). + ejabberd_odbc:sql_query( + LServer, + ?SQL("insert into users(username, password, serverkey, salt, " + "iterationcount) " + "values (%(LUser)s, %(StoredKey)s, %(ServerKey)s," + " %(Salt)s, %(IterationCount)d)")). -del_user_return_password(_LServer, Username, Pass) -> +del_user(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("delete from users where username=%(LUser)s")). + +del_user_return_password(_LServer, LUser, Password) -> P = - ejabberd_odbc:sql_query_t([<<"select password from users where username='">>, - Username, <<"';">>]), - ejabberd_odbc:sql_query_t([<<"delete from users where username='">>, - Username, <<"' and password='">>, Pass, - <<"';">>]), + ejabberd_odbc:sql_query_t( + ?SQL("select @(password)s from users where username=%(LUser)s")), + ejabberd_odbc:sql_query_t( + ?SQL("delete from users" + " where username=%(LUser)s and password=%(Password)s")), P. list_users(LServer) -> - ejabberd_odbc:sql_query(LServer, - [<<"select username from users">>]). + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(username)s from users")). list_users(LServer, [{from, Start}, {to, End}]) when is_integer(Start) and is_integer(End) -> @@ -222,64 +222,54 @@ list_users(LServer, {offset, Start - 1}]); list_users(LServer, [{limit, Limit}, {offset, Offset}]) when is_integer(Limit) and is_integer(Offset) -> - ejabberd_odbc:sql_query(LServer, - [list_to_binary( - io_lib:format( - "select username from users " ++ - "order by username " ++ - "limit ~w offset ~w", - [Limit, Offset]))]); + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(username)s from users " + "order by username " + "limit %(Limit)d offset %(Offset)d")); list_users(LServer, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) -> - ejabberd_odbc:sql_query(LServer, - [list_to_binary( - io_lib:format( - "select username from users " ++ - "where username like '~s%' " ++ - "order by username " ++ - "limit ~w offset ~w ", - [Prefix, Limit, Offset]))]). + SPrefix = ejabberd_odbc:escape_like_arg(Prefix), + SPrefix2 = <<SPrefix/binary, $%>>, + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(username)s from users " + "where username like %(SPrefix2)s " + "order by username " + "limit %(Limit)d offset %(Offset)d")). users_number(LServer) -> - Type = ejabberd_config:get_option({odbc_type, LServer}, - fun(pgsql) -> pgsql; - (mysql) -> mysql; - (sqlite) -> sqlite; - (odbc) -> odbc - end, odbc), - case Type of - pgsql -> - case - ejabberd_config:get_option( - {pgsql_users_number_estimate, LServer}, - fun(V) when is_boolean(V) -> V end, - false) - of - true -> - ejabberd_odbc:sql_query(LServer, - [<<"select reltuples from pg_class where " - "oid = 'users'::regclass::oid">>]); - _ -> - ejabberd_odbc:sql_query(LServer, - [<<"select count(*) from users">>]) + ejabberd_odbc:sql_query( + LServer, + fun(pgsql, _) -> + case + ejabberd_config:get_option( + {pgsql_users_number_estimate, LServer}, + fun(V) when is_boolean(V) -> V end, + false) of + true -> + ejabberd_odbc:sql_query_t( + ?SQL("select @(reltuples :: bigint)d from pg_class" + " where oid = 'users'::regclass::oid")); + _ -> + ejabberd_odbc:sql_query_t( + ?SQL("select @(count(*))d from users")) end; - _ -> - ejabberd_odbc:sql_query(LServer, - [<<"select count(*) from users">>]) - end. + (_Type, _) -> + ejabberd_odbc:sql_query_t( + ?SQL("select @(count(*))d from users")) + end). users_number(LServer, [{prefix, Prefix}]) when is_binary(Prefix) -> - ejabberd_odbc:sql_query(LServer, - [list_to_binary( - io_lib:fwrite( - "select count(*) from users " ++ - %% Warning: Escape prefix at higher level to prevent SQL - %% injection. - "where username like '~s%'", - [Prefix]))]); + SPrefix = ejabberd_odbc:escape_like_arg(Prefix), + SPrefix2 = <<SPrefix/binary, $%>>, + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(count(*))d from users " + "where username like %(SPrefix2)s")); users_number(LServer, []) -> users_number(LServer). @@ -291,74 +281,71 @@ add_spool_sql(Username, XML) -> add_spool(LServer, Queries) -> ejabberd_odbc:sql_transaction(LServer, Queries). -get_and_del_spool_msg_t(LServer, Username) -> +get_and_del_spool_msg_t(LServer, LUser) -> F = fun () -> Result = - ejabberd_odbc:sql_query_t([<<"select username, xml from spool where " - "username='">>, - Username, - <<"' order by seq;">>]), - ejabberd_odbc:sql_query_t([<<"delete from spool where username='">>, - Username, <<"';">>]), + ejabberd_odbc:sql_query_t( + ?SQL("select @(username)s, @(xml)s from spool where " + "username=%(LUser)s order by seq;")), + ejabberd_odbc:sql_query_t( + ?SQL("delete from spool where username=%(LUser)s;")), Result end, ejabberd_odbc:sql_transaction(LServer, F). -del_spool_msg(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"delete from spool where username='">>, Username, - <<"';">>]). +del_spool_msg(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("delete from spool where username=%(LUser)s")). -get_roster(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"select username, jid, nick, subscription, " - "ask, askmessage, server, subscribe, " - "type from rosterusers where username='">>, - Username, <<"'">>]). +get_roster(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, " + "@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, " + "@(type)s from rosterusers where username=%(LUser)s")). -get_roster_jid_groups(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"select jid, grp from rostergroups where " - "username='">>, - Username, <<"'">>]). - -get_roster_groups(_LServer, Username, SJID) -> - ejabberd_odbc:sql_query_t([<<"select grp from rostergroups where username='">>, - Username, <<"' and jid='">>, SJID, <<"';">>]). - -del_user_roster_t(LServer, Username) -> - ejabberd_odbc:sql_transaction(LServer, - fun () -> - ejabberd_odbc:sql_query_t([<<"delete from rosterusers where " - "username='">>, - Username, - <<"';">>]), - ejabberd_odbc:sql_query_t([<<"delete from rostergroups where " - "username='">>, - Username, - <<"';">>]) - end). - -get_roster_by_jid(_LServer, Username, SJID) -> - ejabberd_odbc:sql_query_t([<<"select username, jid, nick, subscription, " - "ask, askmessage, server, subscribe, " - "type from rosterusers where username='">>, - Username, <<"' and jid='">>, SJID, <<"';">>]). - -get_rostergroup_by_jid(LServer, Username, SJID) -> - ejabberd_odbc:sql_query(LServer, - [<<"select grp from rostergroups where username='">>, - Username, <<"' and jid='">>, SJID, <<"'">>]). - -del_roster(_LServer, Username, SJID) -> - ejabberd_odbc:sql_query_t([<<"delete from rosterusers where " - "username='">>, - Username, <<"' and jid='">>, SJID, - <<"';">>]), - ejabberd_odbc:sql_query_t([<<"delete from rostergroups where " - "username='">>, - Username, <<"' and jid='">>, SJID, - <<"';">>]). +get_roster_jid_groups(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(jid)s, @(grp)s from rostergroups where " + "username=%(LUser)s")). + +get_roster_groups(_LServer, LUser, SJID) -> + ejabberd_odbc:sql_query_t( + ?SQL("select @(grp)s from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")). + +del_user_roster_t(LServer, LUser) -> + ejabberd_odbc:sql_transaction( + LServer, + fun () -> + ejabberd_odbc:sql_query_t( + ?SQL("delete from rosterusers where username=%(LUser)s")), + ejabberd_odbc:sql_query_t( + ?SQL("delete from rostergroups where username=%(LUser)s")) + end). + +get_roster_by_jid(_LServer, LUser, SJID) -> + ejabberd_odbc:sql_query_t( + ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s," + " @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s," + " @(type)s from rosterusers" + " where username=%(LUser)s and jid=%(SJID)s")). + +get_rostergroup_by_jid(LServer, LUser, SJID) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(grp)s from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")). + +del_roster(_LServer, LUser, SJID) -> + ejabberd_odbc:sql_query_t( + ?SQL("delete from rosterusers" + " where username=%(LUser)s and jid=%(SJID)s")), + ejabberd_odbc:sql_query_t( + ?SQL("delete from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")). del_roster_sql(Username, SJID) -> [[<<"delete from rosterusers where " @@ -368,27 +355,19 @@ del_roster_sql(Username, SJID) -> "username='">>, Username, <<"' and jid='">>, SJID, <<"';">>]]. -update_roster(_LServer, Username, SJID, ItemVals, +update_roster(_LServer, LUser, SJID, ItemVals, ItemGroups) -> - update_t(<<"rosterusers">>, - [<<"username">>, <<"jid">>, <<"nick">>, - <<"subscription">>, <<"ask">>, <<"askmessage">>, - <<"server">>, <<"subscribe">>, <<"type">>], - ItemVals, - [<<"username='">>, Username, <<"' and jid='">>, SJID, - <<"'">>]), - ejabberd_odbc:sql_query_t([<<"delete from rostergroups where " - "username='">>, - Username, <<"' and jid='">>, SJID, - <<"';">>]), - lists:foreach(fun (ItemGroup) -> - ejabberd_odbc:sql_query_t([<<"insert into rostergroups( " - " username, jid, grp) values ('">>, - join(ItemGroup, - <<"', '">>), - <<"');">>]) - end, - ItemGroups). + roster_subscribe(ItemVals), + ejabberd_odbc:sql_query_t( + ?SQL("delete from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")), + lists:foreach( + fun(ItemGroup) -> + ejabberd_odbc:sql_query_t( + ?SQL("insert into rostergroups(username, jid, grp) " + "values (%(LUser)s, %(SJID)s, %(ItemGroup)s)")) + end, + ItemGroups). update_roster_sql(Username, SJID, ItemVals, ItemGroups) -> @@ -410,27 +389,31 @@ update_roster_sql(Username, SJID, ItemVals, join(ItemGroup, <<"', '">>), <<"');">>] || ItemGroup <- ItemGroups]. -roster_subscribe(_LServer, Username, SJID, ItemVals) -> - update_t(<<"rosterusers">>, - [<<"username">>, <<"jid">>, <<"nick">>, - <<"subscription">>, <<"ask">>, <<"askmessage">>, - <<"server">>, <<"subscribe">>, <<"type">>], - ItemVals, - [<<"username='">>, Username, <<"' and jid='">>, SJID, - <<"'">>]). - -get_subscription(LServer, Username, SJID) -> - ejabberd_odbc:sql_query(LServer, - [<<"select subscription from rosterusers " - "where username='">>, - Username, <<"' and jid='">>, SJID, <<"'">>]). +roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) -> + ?SQL_UPSERT_T( + "rosterusers", + ["!username=%(LUser)s", + "!jid=%(SJID)s", + "nick=%(Name)s", + "subscription=%(SSubscription)s", + "ask=%(SAsk)s", + "askmessage=%(AskMessage)s", + "server='N'", + "subscribe=''", + "type='item'"]). + +get_subscription(LServer, LUser, SJID) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(subscription)s from rosterusers " + "where username=%(LUser)s and jid=%(SJID)s")). -set_private_data(_LServer, Username, LXMLNS, SData) -> - update_t(<<"private_storage">>, - [<<"username">>, <<"namespace">>, <<"data">>], - [Username, LXMLNS, SData], - [<<"username='">>, Username, <<"' and namespace='">>, - LXMLNS, <<"'">>]). +set_private_data(_LServer, LUser, XMLNS, SData) -> + ?SQL_UPSERT_T( + "private_storage", + ["!username=%(LUser)s", + "!namespace=%(XMLNS)s", + "data=%(SData)s"]). set_private_data_sql(Username, LXMLNS, SData) -> [[<<"delete from private_storage where username='">>, @@ -440,187 +423,189 @@ set_private_data_sql(Username, LXMLNS, SData) -> Username, <<"', '">>, LXMLNS, <<"', '">>, SData, <<"');">>]]. -get_private_data(LServer, Username, LXMLNS) -> - ejabberd_odbc:sql_query(LServer, - [<<"select data from private_storage where " - "username='">>, - Username, <<"' and namespace='">>, LXMLNS, - <<"';">>]). +get_private_data(LServer, LUser, XMLNS) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(data)s from private_storage" + " where username=%(LUser)s and namespace=%(XMLNS)s")). -get_private_data(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"select namespace, data from private_storage " - "where username='">>, Username, <<"';">>]). +get_private_data(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(namespace)s, @(data)s from private_storage" + " where username=%(LUser)s")). -del_user_private_storage(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"delete from private_storage where username='">>, - Username, <<"';">>]). - -set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN, - SFamily, SGiven, SLBDay, SLCTRY, SLEMail, SLFN, - SLFamily, SLGiven, SLLocality, SLMiddle, SLNickname, - SLOrgName, SLOrgUnit, SLocality, SMiddle, SNickname, - SOrgName, SOrgUnit, SVCARD, Username) -> - ejabberd_odbc:sql_transaction(LServer, - fun () -> - update_t(<<"vcard">>, - [<<"username">>, - <<"vcard">>], - [LUsername, SVCARD], - [<<"username='">>, LUsername, - <<"'">>]), - update_t(<<"vcard_search">>, - [<<"username">>, - <<"lusername">>, <<"fn">>, - <<"lfn">>, <<"family">>, - <<"lfamily">>, <<"given">>, - <<"lgiven">>, <<"middle">>, - <<"lmiddle">>, - <<"nickname">>, - <<"lnickname">>, <<"bday">>, - <<"lbday">>, <<"ctry">>, - <<"lctry">>, <<"locality">>, - <<"llocality">>, - <<"email">>, <<"lemail">>, - <<"orgname">>, - <<"lorgname">>, - <<"orgunit">>, - <<"lorgunit">>], - [Username, LUsername, SFN, - SLFN, SFamily, SLFamily, - SGiven, SLGiven, SMiddle, - SLMiddle, SNickname, - SLNickname, SBDay, SLBDay, - SCTRY, SLCTRY, SLocality, - SLLocality, SEMail, SLEMail, - SOrgName, SLOrgName, - SOrgUnit, SLOrgUnit], - [<<"lusername='">>, - LUsername, <<"'">>]) - end). - -get_vcard(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"select vcard from vcard where username='">>, - Username, <<"';">>]). +del_user_private_storage(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("delete from private_storage" + " where username=%(LUser)s")). + +set_vcard(LServer, LUser, BDay, CTRY, EMail, FN, + Family, Given, LBDay, LCTRY, LEMail, LFN, + LFamily, LGiven, LLocality, LMiddle, LNickname, + LOrgName, LOrgUnit, Locality, Middle, Nickname, + OrgName, OrgUnit, SVCARD, User) -> + ejabberd_odbc:sql_transaction( + LServer, + fun() -> + ?SQL_UPSERT(LServer, "vcard", + ["!username=%(LUser)s", + "vcard=%(SVCARD)s"]), + ?SQL_UPSERT(LServer, "vcard_search", + ["username=%(User)s", + "!lusername=%(LUser)s", + "fn=%(FN)s", + "lfn=%(LFN)s", + "family=%(Family)s", + "lfamily=%(LFamily)s", + "given=%(Given)s", + "lgiven=%(LGiven)s", + "middle=%(Middle)s", + "lmiddle=%(LMiddle)s", + "nickname=%(Nickname)s", + "lnickname=%(LNickname)s", + "bday=%(BDay)s", + "lbday=%(LBDay)s", + "ctry=%(CTRY)s", + "lctry=%(LCTRY)s", + "locality=%(Locality)s", + "llocality=%(LLocality)s", + "email=%(EMail)s", + "lemail=%(LEMail)s", + "orgname=%(OrgName)s", + "lorgname=%(LOrgName)s", + "orgunit=%(OrgUnit)s", + "lorgunit=%(LOrgUnit)s"]) + end). + +get_vcard(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(vcard)s from vcard where username=%(LUser)s")). -get_default_privacy_list(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"select name from privacy_default_list " - "where username='">>, - Username, <<"';">>]). +get_default_privacy_list(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(name)s from privacy_default_list " + "where username=%(LUser)s")). -get_default_privacy_list_t(Username) -> - ejabberd_odbc:sql_query_t([<<"select name from privacy_default_list " - "where username='">>, - Username, <<"';">>]). +get_default_privacy_list_t(LUser) -> + ejabberd_odbc:sql_query_t( + ?SQL("select @(name)s from privacy_default_list " + "where username=%(LUser)s")). -get_privacy_list_names(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"select name from privacy_list where " - "username='">>, - Username, <<"';">>]). +get_privacy_list_names(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(name)s from privacy_list" + " where username=%(LUser)s")). -get_privacy_list_names_t(Username) -> - ejabberd_odbc:sql_query_t([<<"select name from privacy_list where " - "username='">>, - Username, <<"';">>]). +get_privacy_list_names_t(LUser) -> + ejabberd_odbc:sql_query_t( + ?SQL("select @(name)s from privacy_list" + " where username=%(LUser)s")). -get_privacy_list_id(LServer, Username, SName) -> - ejabberd_odbc:sql_query(LServer, - [<<"select id from privacy_list where username='">>, - Username, <<"' and name='">>, SName, <<"';">>]). +get_privacy_list_id(LServer, LUser, Name) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(id)d from privacy_list" + " where username=%(LUser)s and name=%(Name)s")). -get_privacy_list_id_t(Username, SName) -> - ejabberd_odbc:sql_query_t([<<"select id from privacy_list where username='">>, - Username, <<"' and name='">>, SName, <<"';">>]). +get_privacy_list_id_t(LUser, Name) -> + ejabberd_odbc:sql_query_t( + ?SQL("select @(id)d from privacy_list" + " where username=%(LUser)s and name=%(Name)s")). -get_privacy_list_data(LServer, Username, SName) -> - ejabberd_odbc:sql_query(LServer, - [<<"select t, value, action, ord, match_all, " - "match_iq, match_message, match_presence_in, " - "match_presence_out from privacy_list_data " - "where id = (select id from privacy_list " - "where username='">>, - Username, <<"' and name='">>, SName, - <<"') order by ord;">>]). - -get_privacy_list_data_t(Username, SName) -> - ejabberd_odbc:sql_query_t([<<"select t, value, action, ord, match_all, " - "match_iq, match_message, match_presence_in, " - "match_presence_out from privacy_list_data " - "where id = (select id from privacy_list " - "where username='">>, - Username, <<"' and name='">>, SName, - <<"') order by ord;">>]). +get_privacy_list_data(LServer, LUser, Name) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " + "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " + "@(match_presence_out)b from privacy_list_data " + "where id =" + " (select id from privacy_list" + " where username=%(LUser)s and name=%(Name)s) " + "order by ord")). + +%% Not used? +get_privacy_list_data_t(LUser, Name) -> + ejabberd_odbc:sql_query_t( + ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " + "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " + "@(match_presence_out)b from privacy_list_data " + "where id =" + " (select id from privacy_list" + " where username=%(LUser)s and name=%(Name)s) " + "order by ord")). get_privacy_list_data_by_id(LServer, ID) -> - ejabberd_odbc:sql_query(LServer, - [<<"select t, value, action, ord, match_all, " - "match_iq, match_message, match_presence_in, " - "match_presence_out from privacy_list_data " - "where id='">>, - ID, <<"' order by ord;">>]). + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " + "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " + "@(match_presence_out)b from privacy_list_data " + "where id=%(ID)d order by ord")). get_privacy_list_data_by_id_t(ID) -> - ejabberd_odbc:sql_query_t([<<"select t, value, action, ord, match_all, " - "match_iq, match_message, match_presence_in, " - "match_presence_out from privacy_list_data " - "where id='">>, - ID, <<"' order by ord;">>]). - -set_default_privacy_list(Username, SName) -> - update_t(<<"privacy_default_list">>, - [<<"username">>, <<"name">>], [Username, SName], - [<<"username='">>, Username, <<"'">>]). - -unset_default_privacy_list(LServer, Username) -> - ejabberd_odbc:sql_query(LServer, - [<<"delete from privacy_default_list " - " where username='">>, - Username, <<"';">>]). + ejabberd_odbc:sql_query_t( + ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " + "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " + "@(match_presence_out)b from privacy_list_data " + "where id=%(ID)d order by ord")). + +set_default_privacy_list(LUser, Name) -> + ?SQL_UPSERT_T( + "privacy_default_list", + ["!username=%(LUser)s", + "name=%(Name)s"]). + +unset_default_privacy_list(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("delete from privacy_default_list" + " where username=%(LUser)s")). -remove_privacy_list(Username, SName) -> - ejabberd_odbc:sql_query_t([<<"delete from privacy_list where username='">>, - Username, <<"' and name='">>, SName, <<"';">>]). +remove_privacy_list(LUser, Name) -> + ejabberd_odbc:sql_query_t( + ?SQL("delete from privacy_list where" + " username=%(LUser)s and name=%(Name)s")). -add_privacy_list(Username, SName) -> - ejabberd_odbc:sql_query_t([<<"insert into privacy_list(username, name) " - "values ('">>, - Username, <<"', '">>, SName, <<"');">>]). +add_privacy_list(LUser, Name) -> + ejabberd_odbc:sql_query_t( + ?SQL("insert into privacy_list(username, name) " + "values (%(LUser)s, %(Name)s)")). set_privacy_list(ID, RItems) -> - ejabberd_odbc:sql_query_t([<<"delete from privacy_list_data where " - "id='">>, - ID, <<"';">>]), - lists:foreach(fun (Items) -> - ejabberd_odbc:sql_query_t([<<"insert into privacy_list_data(id, t, " - "value, action, ord, match_all, match_iq, " - "match_message, match_presence_in, match_prese" - "nce_out ) values ('">>, - ID, <<"', '">>, - join(Items, <<"', '">>), - <<"');">>]) + ejabberd_odbc:sql_query_t( + ?SQL("delete from privacy_list_data where id=%(ID)d")), + lists:foreach( + fun({SType, SValue, SAction, Order, MatchAll, MatchIQ, + MatchMessage, MatchPresenceIn, MatchPresenceOut}) -> + ejabberd_odbc:sql_query_t( + ?SQL("insert into privacy_list_data(id, t, " + "value, action, ord, match_all, match_iq, " + "match_message, match_presence_in, match_presence_out) " + "values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s," + " %(Order)d, %(MatchAll)b, %(MatchIQ)b," + " %(MatchMessage)b, %(MatchPresenceIn)b," + " %(MatchPresenceOut)b)")) end, RItems). -del_privacy_lists(LServer, Server, Username) -> -%% Characters to escape -%% Count number of records in a table given a where clause - ejabberd_odbc:sql_query(LServer, - [<<"delete from privacy_list where username='">>, - Username, <<"';">>]), - ejabberd_odbc:sql_query(LServer, - [<<"delete from privacy_list_data where " - "value='">>, - <<Username/binary, "@", Server/binary>>, - <<"';">>]), - ejabberd_odbc:sql_query(LServer, - [<<"delete from privacy_default_list where " - "username='">>, - Username, <<"';">>]). +del_privacy_lists(LServer, LUser) -> + ejabberd_odbc:sql_query( + LServer, + ?SQL("delete from privacy_list where username=%(LUser)s")), + %US = <<LUser/binary, "@", LServer/binary>>, + %ejabberd_odbc:sql_query( + % LServer, + % ?SQL("delete from privacy_list_data where value=%(US)s")), + ejabberd_odbc:sql_query( + LServer, + ?SQL("delete from privacy_default_list where username=%(LUser)s")). +%% Characters to escape escape($\000) -> <<"\\0">>; escape($\n) -> <<"\\n">>; escape($\t) -> <<"\\t">>; @@ -631,16 +616,17 @@ escape($") -> <<"\\\"">>; escape($\\) -> <<"\\\\">>; escape(C) -> <<C>>. +%% Count number of records in a table given a where clause count_records_where(LServer, Table, WhereClause) -> ejabberd_odbc:sql_query(LServer, [<<"select count(*) from ">>, Table, <<" ">>, WhereClause, <<";">>]). get_roster_version(LServer, LUser) -> - ejabberd_odbc:sql_query(LServer, - [<<"select version from roster_version where " - "username = '">>, - LUser, <<"'">>]). + ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(version)s from roster_version" + " where username = %(LUser)s")). set_roster_version(LUser, Version) -> update_t(<<"roster_version">>, diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl new file mode 100644 index 00000000..204cfec2 --- /dev/null +++ b/src/prosody2ejabberd.erl @@ -0,0 +1,341 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net> +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 20 Jan 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net> +%%%------------------------------------------------------------------- +-module(prosody2ejabberd). + +%% API +-export([from_dir/1]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("logger.hrl"). +-include("mod_roster.hrl"). +-include("mod_offline.hrl"). +-include("mod_privacy.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +from_dir(ProsodyDir) -> + case file:list_dir(ProsodyDir) of + {ok, HostDirs} -> + lists:foreach( + fun(HostDir) -> + Host = list_to_binary(HostDir), + lists:foreach( + fun(SubDir) -> + Path = filename:join( + [ProsodyDir, HostDir, SubDir]), + convert_dir(Path, Host, SubDir) + end, ["vcard", "accounts", "roster", + "private", "config", "offline", + "privacy"]) + end, HostDirs); + {error, Why} = Err -> + ?ERROR_MSG("failed to list ~s: ~s", + [ProsodyDir, file:format_error(Why)]), + Err + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +convert_dir(Path, Host, Type) -> + case file:list_dir(Path) of + {ok, Files} -> + lists:foreach( + fun(File) -> + FilePath = filename:join(Path, File), + case eval_file(FilePath) of + {ok, Data} -> + Name = iolist_to_binary(filename:rootname(File)), + convert_data(Host, Type, Name, Data); + Err -> + Err + end + end, Files); + {error, enoent} -> + ok; + {error, Why} = Err -> + ?ERROR_MSG("failed to list ~s: ~s", + [Path, file:format_error(Why)]), + Err + end. + +eval_file(Path) -> + case file:read_file(Path) of + {ok, Data} -> + State0 = luerl:init(), + State1 = luerl:set_table([item], + fun([X], State) -> {[X], State} end, + State0), + NewData = case filename:extension(Path) of + ".list" -> + <<"return {", Data/binary, "};">>; + _ -> + Data + end, + case luerl:eval(NewData, State1) of + {ok, _} = Res -> + Res; + {error, Why} = Err -> + ?ERROR_MSG("failed to eval ~s: ~p", [Path, Why]), + Err + end; + {error, Why} = Err -> + ?ERROR_MSG("failed to read file ~s: ~s", + [Path, file:format_error(Why)]), + Err + end. + +convert_data(Host, "accounts", User, [Data]) -> + Password = proplists:get_value(<<"password">>, Data, <<>>), + case ejabberd_auth:try_register(User, Host, Password) of + {atomic, ok} -> + ok; + Err -> + ?ERROR_MSG("failed to register user ~s@~s: ~p", + [User, Host, Err]), + Err + end; +convert_data(Host, "roster", User, [Data]) -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Host), + Rosters = + lists:flatmap( + fun({<<"pending">>, L}) -> + convert_pending_item(LUser, LServer, L); + ({S, L}) when is_binary(S) -> + convert_roster_item(LUser, LServer, S, L); + (_) -> + [] + end, Data), + lists:foreach(fun mod_roster:set_roster/1, Rosters); +convert_data(Host, "private", User, [Data]) -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Host), + PrivData = lists:flatmap( + fun({_TagXMLNS, Raw}) -> + case deserialize(Raw) of + [El] -> + XMLNS = fxml:get_tag_attr_s(<<"xmlns">>, El), + [{XMLNS, El}]; + _ -> + [] + end + end, Data), + mod_private:set_data(LUser, LServer, PrivData); +convert_data(Host, "vcard", User, [Data]) -> + LServer = jid:nameprep(Host), + case deserialize(Data) of + [VCard] -> + mod_vcard:set_vcard(User, LServer, VCard); + _ -> + ok + end; +convert_data(_Host, "config", _User, [Data]) -> + RoomJID = jid:from_string(proplists:get_value(<<"jid">>, Data, <<"">>)), + Config = proplists:get_value(<<"_data">>, Data, []), + RoomCfg = convert_room_config(Data), + case proplists:get_bool(<<"persistent">>, Config) of + true when RoomJID /= error -> + mod_muc:store_room(?MYNAME, RoomJID#jid.lserver, + RoomJID#jid.luser, RoomCfg); + _ -> + ok + end; +convert_data(Host, "offline", User, [Data]) -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Host), + Msgs = lists:flatmap( + fun({_, RawXML}) -> + case deserialize(RawXML) of + [El] -> el_to_offline_msg(LUser, LServer, El); + _ -> [] + end + end, Data), + mod_offline:store_offline_msg( + LServer, {LUser, LServer}, Msgs, length(Msgs), infinity); +convert_data(Host, "privacy", User, [Data]) -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Host), + Lists = proplists:get_value(<<"lists">>, Data, []), + Priv = #privacy{ + us = {LUser, LServer}, + default = proplists:get_value(<<"default">>, Data, none), + lists = lists:flatmap( + fun({Name, Vals}) -> + Items = proplists:get_value(<<"items">>, Vals, []), + case lists:map(fun convert_privacy_item/1, + Items) of + [] -> []; + ListItems -> [{Name, ListItems}] + end + end, Lists)}, + mod_privacy:set_privacy_list(Priv); +convert_data(_Host, _Type, _User, _Data) -> + ok. + +convert_pending_item(LUser, LServer, LuaList) -> + lists:flatmap( + fun({S, true}) -> + case jid:from_string(S) of + #jid{} = J -> + LJID = jid:tolower(J), + [#roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, + jid = LJID, + ask = in}]; + error -> + [] + end; + (_) -> + [] + end, LuaList). + +convert_roster_item(LUser, LServer, JIDstring, LuaList) -> + case jid:from_string(JIDstring) of + #jid{} = JID -> + LJID = jid:tolower(JID), + InitR = #roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, + jid = LJID}, + Roster = + lists:foldl( + fun({<<"groups">>, Val}, R) -> + Gs = lists:flatmap( + fun({G, true}) -> [G]; + (_) -> [] + end, Val), + R#roster{groups = Gs}; + ({<<"subscription">>, Sub}, R) -> + R#roster{subscription = jlib:binary_to_atom(Sub)}; + ({<<"ask">>, <<"subscribe">>}, R) -> + R#roster{ask = out}; + ({<<"name">>, Name}, R) -> + R#roster{name = Name} + end, InitR, LuaList), + [Roster]; + error -> + [] + end. + +convert_room_affiliations(Data) -> + lists:flatmap( + fun({J, Aff}) -> + case jid:from_string(J) of + #jid{luser = U, lserver = S} -> + [{{U, S, <<>>}, jlib:binary_to_atom(Aff)}]; + error -> + [] + end + end, proplists:get_value(<<"_affiliations">>, Data, [])). + +convert_room_config(Data) -> + Config = proplists:get_value(<<"_data">>, Data, []), + Pass = case proplists:get_value(<<"password">>, Config, <<"">>) of + <<"">> -> + []; + Password -> + [{password_protected, true}, + {password, Password}] + end, + Subj = case jid:from_string( + proplists:get_value( + <<"subject_from">>, Config, <<"">>)) of + #jid{lresource = Nick} when Nick /= <<"">> -> + [{subject, proplists:get_value(<<"subject">>, Config, <<"">>)}, + {subject_author, Nick}]; + _ -> + [] + end, + Anonymous = case proplists:get_value(<<"whois">>, Config, <<"moderators">>) of + <<"moderators">> -> true; + _ -> false + end, + [{affiliations, convert_room_affiliations(Data)}, + {allow_change_subj, proplists:get_bool(<<"changesubject">>, Config)}, + {description, proplists:get_value(<<"description">>, Config, <<"">>)}, + {members_only, proplists:get_bool(<<"members_only">>, Config)}, + {moderated, proplists:get_bool(<<"moderated">>, Config)}, + {anonymous, Anonymous}] ++ Pass ++ Subj. + +convert_privacy_item({_, Item}) -> + Action = proplists:get_value(<<"action">>, Item, <<"allow">>), + Order = proplists:get_value(<<"order">>, Item, 0), + T = jlib:binary_to_atom(proplists:get_value(<<"type">>, Item, <<"none">>)), + V = proplists:get_value(<<"value">>, Item, <<"">>), + MatchIQ = proplists:get_bool(<<"iq">>, Item), + MatchMsg = proplists:get_bool(<<"message">>, Item), + MatchPresIn = proplists:get_bool(<<"presence-in">>, Item), + MatchPresOut = proplists:get_bool(<<"presence-out">>, Item), + MatchAll = if (MatchIQ == false) and (MatchMsg == false) and + (MatchPresIn == false) and (MatchPresOut == false) -> + true; + true -> + false + end, + {Type, Value} = try case T of + none -> {T, none}; + group -> {T, V}; + jid -> {T, jid:tolower(jid:from_string(V))}; + subscription -> {T, jlib:binary_to_atom(V)} + end + catch _:_ -> + {none, none} + end, + #listitem{type = Type, + value = Value, + action = jlib:binary_to_atom(Action), + order = erlang:trunc(Order), + match_all = MatchAll, + match_iq = MatchIQ, + match_message = MatchMsg, + match_presence_in = MatchPresIn, + match_presence_out = MatchPresOut}. + +el_to_offline_msg(LUser, LServer, #xmlel{attrs = Attrs} = El) -> + case jlib:datetime_string_to_timestamp( + fxml:get_attr_s(<<"stamp">>, Attrs)) of + {_, _, _} = TS -> + Attrs1 = lists:filter( + fun(<<"stamp">>) -> false; + (<<"stamp_legacy">>) -> false; + (_) -> true + end, Attrs), + Packet = El#xmlel{attrs = Attrs1}, + case {jid:from_string(fxml:get_attr_s(<<"from">>, Attrs)), + jid:from_string(fxml:get_attr_s(<<"to">>, Attrs))} of + {#jid{} = From, #jid{} = To} -> + [#offline_msg{ + us = {LUser, LServer}, + timestamp = TS, + expire = never, + from = From, + to = To, + packet = Packet}]; + _ -> + [] + end; + _ -> + [] + end. + +deserialize(L) -> + deserialize(L, #xmlel{}, []). + +deserialize([{<<"attr">>, Attrs}|T], El, Acc) -> + deserialize(T, El#xmlel{attrs = Attrs ++ El#xmlel.attrs}, Acc); +deserialize([{<<"name">>, Name}|T], El, Acc) -> + deserialize(T, El#xmlel{name = Name}, Acc); +deserialize([{_, S}|T], #xmlel{children = Els} = El, Acc) when is_binary(S) -> + deserialize(T, El#xmlel{children = [{xmlcdata, S}|Els]}, Acc); +deserialize([{_, L}|T], #xmlel{children = Els} = El, Acc) when is_list(L) -> + deserialize(T, El#xmlel{children = deserialize(L) ++ Els}, Acc); +deserialize([], El, Acc) -> + [El|Acc]. diff --git a/src/pubsub_subscription.erl b/src/pubsub_subscription.erl index 9918a2c3..22c90414 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) -> diff --git a/src/pubsub_subscription_odbc.erl b/src/pubsub_subscription_odbc.erl index 6791c4ac..149308ad 100644 --- a/src/pubsub_subscription_odbc.erl +++ b/src/pubsub_subscription_odbc.erl @@ -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) -> |