diff options
Diffstat (limited to 'src/ejabberd_service.erl')
-rw-r--r-- | src/ejabberd_service.erl | 530 |
1 files changed, 184 insertions, 346 deletions
diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 26374c1f1..94cd68ecf 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -36,7 +36,7 @@ -behaviour(?GEN_FSM). %% External exports --export([start/0, start/2, start_link/2, send_text/2, +-export([start/2, start_link/2, send_text/2, send_element/2, socket_type/0, transform_listen_option/2]). -export([init/1, wait_for_stream/2, @@ -44,61 +44,35 @@ handle_event/3, handle_sync_event/4, code_change/4, handle_info/3, terminate/3, print_state/1, opt_type/1]). --include("ejabberd_service.hrl"). --include("mod_privacy.hrl"). - --export([get_delegated_ns/1]). +-include("ejabberd.hrl"). +-include("logger.hrl"). +-include("xmpp.hrl"). + +-record(state, + {socket :: ejabberd_socket:socket_state(), + sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket, + streamid = <<"">> :: binary(), + host_opts = dict:new() :: ?TDICT, + host = <<"">> :: binary(), + access :: atom(), + check_from = true :: boolean()}). + +-type state_name() :: wait_for_stream | wait_for_handshake | stream_established. +-type state() :: #state{}. +-type fsm_next() :: {next_state, state_name(), state()}. +-type fsm_stop() :: {stop, normal, state()}. +-type fsm_transition() :: fsm_stop() | fsm_next(). %-define(DBGFSM, true). - -ifdef(DBGFSM). - -define(FSMOPTS, [{debug, [trace]}]). - -else. - -define(FSMOPTS, []). - -endif. --define(STREAM_HEADER, - <<"<?xml version='1.0'?><stream:stream " - "xmlns:stream='http://etherx.jabber.org/stream" - "s' xmlns='jabber:component:accept' id='~s' " - "from='~s'>">>). - --define(STREAM_TRAILER, <<"</stream:stream>">>). - --define(INVALID_HEADER_ERR, - <<"<stream:stream xmlns:stream='http://etherx.ja" - "bber.org/streams'><stream:error>Invalid " - "Stream Header</stream:error></stream:stream>">>). - --define(INVALID_HANDSHAKE_ERR, - <<"<stream:error><not-authorized xmlns='urn:ietf" - ":params:xml:ns:xmpp-streams'/><text " - "xmlns='urn:ietf:params:xml:ns:xmpp-streams' " - "xml:lang='en'>Invalid Handshake</text></strea" - "m:error></stream:stream>">>). - --define(INVALID_XML_ERR, - fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)). - --define(INVALID_NS_ERR, - fxml:element_to_binary(?SERR_INVALID_NAMESPACE)). - %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- - -%% for xep-0355 -%% table contans records like {namespace, fitering attributes, pid(), -%% host, disco info for general case, bare jid disco info } - -start() -> - ets:new(delegated_namespaces, [named_table, public]), - ets:new(hooks_tmp, [named_table, public]). - start(SockData, Opts) -> supervisor:start_child(ejabberd_service_sup, [SockData, Opts]). @@ -109,20 +83,9 @@ start_link(SockData, Opts) -> socket_type() -> xml_stream. -get_delegated_ns(FsmRef) -> - (?GEN_FSM):sync_send_all_state_event(FsmRef, {get_delegated_ns}). - %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- init([{SockMod, Socket}, Opts]) -> ?INFO_MSG("(~w) External service connected", [Socket]), Access = case lists:keysearch(access, 1, Opts) of @@ -144,21 +107,6 @@ init([{SockMod, Socket}, Opts]) -> p1_sha:sha(randoms:bytes(20))), dict:from_list([{global, Pass}]) end, - %% privilege access to entities data - PrivAccess = case lists:keysearch(privilege_access, 1, Opts) of - {value, {_, PrivAcc}} -> PrivAcc; - _ -> [] - end, - Delegations = case lists:keyfind(delegations, 1, Opts) of - {delegations, Del} -> - lists:foldl( - fun({Ns, FiltAttr}, D) when Ns /= ?NS_DELEGATION -> - Attr = proplists:get_value(filtering, FiltAttr, []), - D ++ [{Ns, Attr}]; - (_Deleg, D) -> D - end, [], Del); - false -> [] - end, Shaper = case lists:keysearch(shaper_rule, 1, Opts) of {value, {_, S}} -> S; _ -> none @@ -172,223 +120,136 @@ init([{SockMod, Socket}, Opts]) -> SockMod:change_shaper(Socket, Shaper), {ok, wait_for_stream, #state{socket = Socket, sockmod = SockMod, - streamid = new_id(), host_opts = HostOpts, access = Access, - check_from = CheckFrom, privilege_access = PrivAccess, - delegations = Delegations}}. - -%%---------------------------------------------------------------------- -%% Func: StateName/2 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- - -wait_for_stream({xmlstreamstart, _Name, Attrs}, - StateData) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - <<"jabber:component:accept">> -> - To = fxml:get_attr_s(<<"to">>, Attrs), - Host = jid:nameprep(To), - if Host == error -> - Header = io_lib:format(?STREAM_HEADER, - [<<"none">>, ?MYNAME]), - send_text(StateData, - <<(list_to_binary(Header))/binary, - (?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - {stop, normal, StateData}; - true -> - Header = io_lib:format(?STREAM_HEADER, - [StateData#state.streamid, fxml:crypt(To)]), - send_text(StateData, Header), - HostOpts = case dict:is_key(Host, StateData#state.host_opts) of - true -> - StateData#state.host_opts; - false -> - case dict:find(global, StateData#state.host_opts) of - {ok, GlobalPass} -> - dict:from_list([{Host, GlobalPass}]); - error -> - StateData#state.host_opts - end - end, - {next_state, wait_for_handshake, - StateData#state{host = Host, host_opts = HostOpts}} - end; - _ -> - send_text(StateData, ?INVALID_HEADER_ERR), - {stop, normal, StateData} + streamid = new_id(), host_opts = HostOpts, + access = Access, check_from = CheckFrom}}. + +wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> + try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of + #stream_start{xmlns = NS_COMPONENT, stream_xmlns = NS_STREAM} + when NS_COMPONENT /= ?NS_COMPONENT; NS_STREAM /= ?NS_STREAM -> + send_header(StateData, ?MYNAME), + send_element(StateData, xmpp:serr_invalid_namespace()), + {stop, normal, StateData}; + #stream_start{to = To} when is_record(To, jid) -> + Host = To#jid.lserver, + send_header(StateData, Host), + HostOpts = case dict:is_key(Host, StateData#state.host_opts) of + true -> + StateData#state.host_opts; + false -> + case dict:find(global, StateData#state.host_opts) of + {ok, GlobalPass} -> + dict:from_list([{Host, GlobalPass}]); + error -> + StateData#state.host_opts + end + end, + {next_state, wait_for_handshake, + StateData#state{host = Host, host_opts = HostOpts}}; + #stream_start{} -> + send_header(StateData, ?MYNAME), + send_element(StateData, xmpp:serr_improper_addressing()), + {stop, normal, StateData}; + _ -> + send_header(StateData, ?MYNAME), + send_element(StateData, xmpp:serr_invalid_xml()), + {stop, normal, StateData} + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + send_header(StateData, ?MYNAME), + send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)), + {stop, normal, StateData} end; wait_for_stream({xmlstreamerror, _}, StateData) -> - Header = io_lib:format(?STREAM_HEADER, - [<<"none">>, ?MYNAME]), - send_text(StateData, - <<(list_to_binary(Header))/binary, (?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), + send_header(StateData, ?MYNAME), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_stream(closed, StateData) -> {stop, normal, StateData}. wait_for_handshake({xmlstreamelement, El}, StateData) -> - #xmlel{name = Name, children = Els} = El, - case {Name, fxml:get_cdata(Els)} of - {<<"handshake">>, Digest} -> - case dict:find(StateData#state.host, StateData#state.host_opts) of - {ok, Password} -> - case p1_sha:sha(<<(StateData#state.streamid)/binary, - Password/binary>>) of - Digest -> - send_text(StateData, <<"<handshake/>">>), - lists:foreach( - fun (H) -> - ejabberd_router:register_route(H, ?MYNAME), - ?INFO_MSG("Route registered for service ~p~n", - [H]), - ejabberd_hooks:run(component_connected, - [H]) - end, dict:fetch_keys(StateData#state.host_opts)), - - mod_privilege:advertise_permissions(StateData), - DelegatedNs = mod_delegation:advertise_delegations(StateData), - - RosterAccess = proplists:get_value(roster, - StateData#state.privilege_access), - - case proplists:get_value(presence, - StateData#state.privilege_access) of - <<"managed_entity">> -> - mod_privilege:initial_presences(StateData), - Fun = mod_privilege:process_presence(self()), - add_hooks(user_send_packet, Fun); - <<"roster">> when (RosterAccess == <<"both">>) or - (RosterAccess == <<"get">>) -> - mod_privilege:initial_presences(StateData), - Fun = mod_privilege:process_presence(self()), - add_hooks(user_send_packet, Fun), - Fun2 = mod_privilege:process_roster_presence(self()), - add_hooks(s2s_receive_packet, Fun2); - _ -> ok - end, - {next_state, stream_established, - StateData#state{delegations = DelegatedNs}}; - _ -> - send_text(StateData, ?INVALID_HANDSHAKE_ERR), - {stop, normal, StateData} - end; - _ -> - send_text(StateData, ?INVALID_HANDSHAKE_ERR), - {stop, normal, StateData} - end; - _ -> {next_state, wait_for_handshake, StateData} - end; + decode_element(El, wait_for_handshake, StateData); +wait_for_handshake(#handshake{data = Digest}, StateData) -> + send_element(StateData, #handshake{}), + lists:foreach( + fun (H) -> + ejabberd_router:register_route(H, ?MYNAME), + ?INFO_MSG("Route registered for service ~p~n", + [H]), + ejabberd_hooks:run(component_connected, [H]) + end, dict:fetch_keys(StateData#state.host_opts)), + {next_state, stream_established, StateData}; + %% case dict:find(StateData#state.host, StateData#state.host_opts) of + %% {ok, Password} -> + %% case p1_sha:sha(<<(StateData#state.streamid)/binary, + %% Password/binary>>) of + %% Digest -> + %% send_element(StateData, #handshake{}), + %% lists:foreach( + %% fun (H) -> + %% ejabberd_router:register_route(H, ?MYNAME), + %% ?INFO_MSG("Route registered for service ~p~n", + %% [H]), + %% ejabberd_hooks:run(component_connected, [H]) + %% end, dict:fetch_keys(StateData#state.host_opts)), + %% {next_state, stream_established, StateData}; + %% _ -> + %% send_element(StateData, xmpp:serr_not_authorized()), + %% {stop, normal, StateData} + %% end; + %% _ -> + %% send_element(StateData, xmpp:serr_not_authorized()), + %% {stop, normal, StateData} + %% end; wait_for_handshake({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; wait_for_handshake({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_handshake(closed, StateData) -> - {stop, normal, StateData}. + {stop, normal, StateData}; +wait_for_handshake(_Pkt, StateData) -> + {next_state, wait_for_handshake, StateData}. stream_established({xmlstreamelement, El}, StateData) -> - NewEl = jlib:remove_attr(<<"xmlns">>, El), - #xmlel{name = Name, attrs = Attrs} = NewEl, - From = fxml:get_attr_s(<<"from">>, Attrs), - FromJID = case StateData#state.check_from of - %% If the admin does not want to check the from field - %% when accept packets from any address. - %% In this case, the component can send packet of - %% behalf of the server users. - false -> jid:from_string(From); - %% The default is the standard behaviour in XEP-0114 - _ -> - FromJID1 = jid:from_string(From), - case FromJID1 of - #jid{lserver = Server} -> - case dict:is_key(Server, StateData#state.host_opts) of - true -> FromJID1; - false -> error - end; - _ -> error - end - end, - To = fxml:get_attr_s(<<"to">>, Attrs), - ToJID = case To of - <<"">> -> error; - _ -> jid:from_string(To) - end, - if (Name == <<"iq">>) and (ToJID /= error) and (FromJID /= error) -> - mod_privilege:process_iq(StateData, FromJID, ToJID, NewEl); - (Name == <<"presence">>) and (ToJID /= error) and (FromJID /= error) -> - ejabberd_router:route(FromJID, ToJID, NewEl); - (Name == <<"message">>) and (ToJID /= error) and (FromJID /= error) -> - mod_privilege:process_message(StateData, FromJID, ToJID, NewEl); + decode_element(El, stream_established, StateData); +stream_established(El, StateData) when ?is_stanza(El) -> + From = xmpp:get_from(El), + To = xmpp:get_to(El), + Lang = xmpp:get_lang(El), + if From == undefined orelse To == undefined -> + Txt = <<"Missing 'from' or 'to' attribute">>, + send_error(StateData, El, xmpp:err_jid_malformed(Txt, Lang)); true -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El), - Txt = <<"Incorrect stanza name or from/to JID">>, - Err = jlib:make_error_reply(NewEl, ?ERRT_BAD_REQUEST(Lang, Txt)), - send_element(StateData, Err), - error + case check_from(From, StateData) of + true -> + ejabberd_router:route(From, To, El); + false -> + Txt = <<"Improper domain part of 'from' attribute">>, + send_error(StateData, El, xmpp:err_not_allowed(Txt, Lang)) + end end, {next_state, stream_established, StateData}; stream_established({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; stream_established({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; stream_established(closed, StateData) -> - {stop, normal, StateData}. - -%%---------------------------------------------------------------------- -%% Func: StateName/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -%state_name(Event, From, StateData) -> -% Reply = ok, -% {reply, Reply, state_name, StateData}. + {stop, normal, StateData}; +stream_established(_Event, StateData) -> + {next_state, stream_established, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -handle_sync_event({get_delegated_ns}, _From, StateName, StateData) -> - Reply = {StateData#state.host, StateData#state.delegations}, - {reply, Reply, StateName, StateData}; - -handle_sync_event(_Event, _From, StateName, StateData) -> +handle_sync_event(_Event, _From, StateName, + StateData) -> Reply = ok, {reply, Reply, StateName, StateData}. code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_info/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- handle_info({send_text, Text}, StateName, StateData) -> send_text(StateData, Text), {next_state, StateName, StateData}; @@ -397,64 +258,20 @@ handle_info({send_element, El}, StateName, StateData) -> {next_state, StateName, StateData}; handle_info({route, From, To, Packet}, StateName, StateData) -> - case acl:match_rule(global, StateData#state.access, - From) - of + case acl:match_rule(global, StateData#state.access, From) of allow -> - #xmlel{name = Name, attrs = Attrs, children = Els} = - Packet, - Attrs2 = - jlib:replace_from_to_attrs(jid:to_string(From), - jid:to_string(To), Attrs), - Text = fxml:element_to_binary(#xmlel{name = Name, - attrs = Attrs2, children = Els}), - send_text(StateData, Text); - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)), - ejabberd_router:route_error(To, From, Err, Packet) + Pkt = xmpp:set_from_to(Packet, From, To), + send_element(StateData, Pkt); + deny -> + Lang = xmpp:get_lang(Packet), + Err = xmpp:err_not_allowed(<<"Denied by ACL">>, Lang), + ejabberd_router:route_error(To, From, Packet, Err) end, {next_state, StateName, StateData}; - -handle_info({user_presence, Packet, From}, - stream_established, StateData) -> - To = jid:from_string(StateData#state.host), - PacketNew = jlib:replace_from_to(From, To, Packet), - send_element(StateData, PacketNew), - {next_state, stream_established, StateData}; - -handle_info({roster_presence, Packet, From}, - stream_established, StateData) -> - %% check that current presence stanza is equivalent to last - PresenceNew = jlib:remove_attr(<<"to">>, Packet), - Dict = StateData#state.last_pres, - LastPresence = - try dict:fetch(From, Dict) - catch _:_ -> - undefined - end, - case mod_privilege:compare_presences(LastPresence, PresenceNew) of - false -> - #xmlel{attrs = Attrs} = PresenceNew, - Presence = PresenceNew#xmlel{attrs = [{<<"to">>, StateData#state.host} | Attrs]}, - send_element(StateData, Presence), - DictNew = dict:store(From, PresenceNew, Dict), - StateDataNew = StateData#state{last_pres = DictNew}, - {next_state, stream_established, StateDataNew}; - _ -> - {next_state, stream_established, StateData} - end; - handle_info(Info, StateName, StateData) -> ?ERROR_MSG("Unexpected info: ~p", [Info]), {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- terminate(Reason, StateName, StateData) -> ?INFO_MSG("terminated: ~p", [Reason]), case StateName of @@ -462,30 +279,12 @@ terminate(Reason, StateName, StateData) -> lists:foreach(fun (H) -> ejabberd_router:unregister_route(H), ejabberd_hooks:run(component_disconnected, - [StateData#state.host, Reason]) + [H, Reason]) end, - dict:fetch_keys(StateData#state.host_opts)), - - lists:foreach(fun({Ns, _FilterAttr}) -> - ets:delete(delegated_namespaces, Ns), - remove_iq_handlers(Ns) - end, StateData#state.delegations), - - RosterAccess = proplists:get_value(roster, StateData#state.privilege_access), - case proplists:get_value(presence, StateData#state.privilege_access) of - <<"managed_entity">> -> - Fun = mod_privilege:process_presence(self()), - remove_hooks(user_send_packet, Fun); - <<"roster">> when (RosterAccess == <<"both">>) or - (RosterAccess == <<"get">>) -> - Fun = mod_privilege:process_presence(self()), - remove_hooks(user_send_packet, Fun), - Fun2 = mod_privilege:process_roster_presence(self()), - remove_hooks(s2s_receive_packet, Fun2); - _ -> ok - end; + dict:fetch_keys(StateData#state.host_opts)); _ -> ok end, + catch send_trailer(StateData), (StateData#state.sockmod):close(StateData#state.socket), ok. @@ -500,13 +299,68 @@ print_state(State) -> State. %%% Internal functions %%%---------------------------------------------------------------------- +-spec send_text(state(), iodata()) -> ok. send_text(StateData, Text) -> (StateData#state.sockmod):send(StateData#state.socket, Text). +-spec send_element(state(), xmpp_element()) -> ok. send_element(StateData, El) -> - send_text(StateData, fxml:element_to_binary(El)). + El1 = xmpp:encode(El, ?NS_COMPONENT), + send_text(StateData, fxml:element_to_binary(El1)). + +-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok. +send_error(StateData, Stanza, Error) -> + Type = xmpp:get_type(Stanza), + if Type == error; Type == result; + Type == <<"error">>; Type == <<"result">> -> + ok; + true -> + send_element(StateData, xmpp:make_error(Stanza, Error)) + end. +-spec send_header(state(), binary()) -> ok. +send_header(StateData, Host) -> + Header = xmpp:encode( + #stream_start{xmlns = ?NS_COMPONENT, + stream_xmlns = ?NS_STREAM, + from = jid:make(Host), + id = StateData#state.streamid}), + send_text(StateData, fxml:element_to_header(Header)). + +-spec send_trailer(state()) -> ok. +send_trailer(StateData) -> + send_text(StateData, <<"</stream:stream>">>). + +-spec decode_element(xmlel(), state_name(), state()) -> fsm_transition(). +decode_element(#xmlel{} = El, StateName, StateData) -> + try xmpp:decode(El, ?NS_COMPONENT, [ignore_els]) of + Pkt -> ?MODULE:StateName(Pkt, StateData) + catch error:{xmpp_codec, Why} -> + case xmpp:is_stanza(El) of + true -> + Lang = xmpp:get_lang(El), + Txt = xmpp:format_error(Why), + send_error(StateData, El, xmpp:err_bad_request(Txt, Lang)); + false -> + ok + end, + {next_state, StateName, StateData} + end. + +-spec check_from(jid(), state()) -> boolean(). +check_from(_From, #state{check_from = false}) -> + %% If the admin does not want to check the from field + %% when accept packets from any address. + %% In this case, the component can send packet of + %% behalf of the server users. + true; +check_from(From, StateData) -> + %% The default is the standard behaviour in XEP-0114 + Server = From#jid.lserver, + dict:is_key(Server, StateData#state.host_opts). + +-spec new_id() -> binary(). new_id() -> randoms:get_string(). transform_listen_option({hosts, Hosts, O}, Opts) -> @@ -543,19 +397,3 @@ fsm_limit_opts(Opts) -> opt_type(max_fsm_queue) -> fun (I) when is_integer(I), I > 0 -> I end; opt_type(_) -> [max_fsm_queue]. - -remove_iq_handlers(Ns) -> - lists:foreach(fun(Host) -> - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, Ns), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, Ns) - end, ?MYHOSTS). - -add_hooks(Hook, Fun) -> - lists:foreach(fun(Host) -> - ejabberd_hooks:add(Hook, Host,Fun, 100) - end, ?MYHOSTS). - -remove_hooks(Hook, Fun) -> - lists:foreach(fun(Host) -> - ejabberd_hooks:delete(Hook, Host, Fun, 100) - end, ?MYHOSTS). |