diff options
-rw-r--r-- | src/mod_irc.erl | 1227 | ||||
-rw-r--r-- | src/mod_irc_connection.erl | 1368 | ||||
-rw-r--r-- | src/mod_irc_mnesia.erl | 2 | ||||
-rw-r--r-- | src/mod_irc_riak.erl | 2 | ||||
-rw-r--r-- | src/mod_irc_sql.erl | 2 | ||||
-rw-r--r-- | src/xmpp.erl | 20 |
6 files changed, 974 insertions, 1647 deletions
diff --git a/src/mod_irc.erl b/src/mod_irc.erl index 2206028b..91f43716 100644 --- a/src/mod_irc.erl +++ b/src/mod_irc.erl @@ -34,7 +34,8 @@ %% API -export([start_link/2, start/2, stop/1, export/1, import/1, import/3, closed_connection/3, get_connection_params/3, - data_to_binary/2]). + data_to_binary/2, process_disco_info/1, process_disco_items/1, + process_register/1, process_vcard/1, process_command/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, @@ -42,11 +43,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). - --include("adhoc.hrl"). - +-include("xmpp.hrl"). -include("mod_irc.hrl"). -define(DEFAULT_IRC_ENCODING, <<"iso8859-15">>). @@ -125,6 +122,18 @@ init([Host, Opts]) -> catch ets:new(irc_connection, [named_table, public, {keypos, #irc_connection.jid_server_host}]), + IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, + one_queue), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO, + ?MODULE, process_disco_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS, + ?MODULE, process_disco_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER, + ?MODULE, process_register, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD, + ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_COMMANDS, + ?MODULE, process_command, IQDisc), ejabberd_router:register_route(MyHost, Host), {ok, #state{host = MyHost, server_host = Host, @@ -176,8 +185,13 @@ handle_info(_Info, State) -> {noreply, State}. %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %%-------------------------------------------------------------------- -terminate(_Reason, State) -> - ejabberd_router:unregister_route(State#state.host), ok. +terminate(_Reason, #state{host = MyHost}) -> + ejabberd_router:unregister_route(MyHost), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_COMMANDS). %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} @@ -203,287 +217,222 @@ stop_supervisor(Host) -> supervisor:terminate_child(ejabberd_sup, Proc), supervisor:delete_child(ejabberd_sup, Proc). -do_route(Host, ServerHost, Access, From, To, Packet) -> +do_route(Host, ServerHost, Access, From, + #jid{luser = LUser, lresource = LResource} = To, Packet) -> case acl:match_rule(ServerHost, Access, From) of - allow -> do_route1(Host, ServerHost, From, To, Packet); - _ -> - #xmlel{attrs = Attrs} = Packet, - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - ErrText = <<"Access denied by service policy">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(To, From, Err) + allow -> + case Packet of + #iq{} when LUser == <<"">>, LResource == <<"">> -> + ejabberd_router:process_iq(From, To, Packet); + #iq{} when LUser == <<"">>, LResource /= <<"">> -> + Err = xmpp:err_service_unavailable(), + ejabberd_router:route_error(To, From, Packet, Err); + _ -> + sm_route(Host, ServerHost, From, To, Packet) + end; + deny -> + Lang = xmpp:get_lang(Packet), + Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang), + ejabberd_router:route_error(To, From, Packet, Err) + end. + +process_disco_info(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_info(#iq{type = get, lang = Lang, to = To, + sub_els = [#disco_info{node = Node}]} = IQ) -> + ServerHost = ejabberd_router:host_of_route(To#jid.lserver), + Info = ejabberd_hooks:run_fold(disco_info, ServerHost, + [], [ServerHost, ?MODULE, <<"">>, <<"">>]), + case iq_disco(ServerHost, Node, Lang) of + undefined -> + xmpp:make_iq_result(IQ, #disco_info{}); + DiscoInfo -> + xmpp:make_iq_result(IQ, DiscoInfo#disco_info{xdata = Info}) + end. + +process_disco_items(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_items(#iq{type = get, lang = Lang, to = To, + sub_els = [#disco_items{node = Node}]} = IQ) -> + case Node of + undefined -> + xmpp:make_iq_result(IQ, #disco_items{}); + <<"join">> -> + xmpp:make_iq_result(IQ, #disco_items{}); + <<"register">> -> + xmpp:make_iq_result(IQ, #disco_items{}); + ?NS_COMMANDS -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + xmpp:make_iq_result( + IQ, #disco_items{node = Node, + items = command_items(ServerHost, Host, Lang)}); + _ -> + Txt = <<"Node not found">>, + xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)) + end. + +process_register(#iq{type = get, to = To, from = From, lang = Lang} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + case get_form(ServerHost, Host, From, Lang) of + {result, Res} -> + xmpp:make_iq_result(IQ, Res); + {error, Error} -> + xmpp:make_error(IQ, Error) + end; +process_register(#iq{type = set, lang = Lang, to = To, from = From, + sub_els = [#register{xdata = #xdata{} = X}]} = IQ) -> + case X#xdata.type of + cancel -> + xmpp:make_iq_result(IQ, #register{}); + submit -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + case set_form(ServerHost, Host, From, Lang, X) of + {result, Res} -> + xmpp:make_iq_result(IQ, Res); + {error, Error} -> + xmpp:make_error(IQ, Error) + end; + _ -> + Txt = <<"Incorrect value of 'type' attribute">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) + end; +process_register(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"No data form found">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)). + +process_vcard(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_vcard(#iq{type = get, lang = Lang} = IQ) -> + xmpp:make_iq_result(IQ, iq_get_vcard(Lang)). + +process_command(#iq{type = get, lang = Lang} = IQ) -> + Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_command(#iq{type = set, lang = Lang, to = To, from = From, + sub_els = [#adhoc_command{node = Node} = Request]} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + case lists:keyfind(Node, 1, commands(ServerHost)) of + {_, _, Function} -> + try Function(From, To, Request) of + ignore -> + ignore; + {error, Error} -> + xmpp:make_error(IQ, Error); + Command -> + xmpp:make_iq_result(IQ, Command) + catch E:R -> + ?ERROR_MSG("ad-hoc handler failed: ~p", + [{E, {R, erlang:get_stacktrace()}}]), + xmpp:make_error(IQ, xmpp:internal_server_error()) + end; + _ -> + Txt = <<"Node not found">>, + xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)) end. -do_route1(Host, ServerHost, From, To, Packet) -> +sm_route(Host, ServerHost, From, To, Packet) -> #jid{user = ChanServ, resource = Resource} = To, - #xmlel{} = Packet, - case ChanServ of - <<"">> -> - case Resource of - <<"">> -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS, - sub_el = SubEl, lang = Lang} = - IQ -> - Node = fxml:get_tag_attr_s(<<"node">>, SubEl), - Info = ejabberd_hooks:run_fold(disco_info, ServerHost, - [], - [ServerHost, ?MODULE, - <<"">>, <<"">>]), - case iq_disco(ServerHost, Node, Lang) of - [] -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = []}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - DiscoInfo -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - DiscoInfo ++ Info}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)) - end; - #iq{type = get, xmlns = (?NS_DISCO_ITEMS) = XMLNS, - sub_el = SubEl, lang = Lang} = - IQ -> - Node = fxml:get_tag_attr_s(<<"node">>, SubEl), - case Node of - <<>> -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = []}]}, - Res = jlib:iq_to_xml(ResIQ); - <<"join">> -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = []}]}, - Res = jlib:iq_to_xml(ResIQ); - <<"register">> -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = []}]}, - Res = jlib:iq_to_xml(ResIQ); - ?NS_COMMANDS -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}, - {<<"node">>, Node}], - children = - command_items(ServerHost, - Host, - Lang)}]}, - Res = jlib:iq_to_xml(ResIQ); - _ -> - Txt = <<"Node not found">>, - Res = jlib:make_error_reply( - Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)) - end, - ejabberd_router:route(To, From, Res); - #iq{xmlns = ?NS_REGISTER} = IQ -> - process_register(ServerHost, Host, From, To, IQ); - #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, - lang = Lang} = - IQ -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - #iq{type = set, xmlns = ?NS_COMMANDS, lang = Lang, - sub_el = SubEl} = - IQ -> - Request = adhoc:parse_request(IQ), - case lists:keysearch(Request#adhoc_request.node, 1, - commands(ServerHost)) - of - {value, {_, _, Function}} -> - case catch Function(From, To, Request) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p~nfor ad-hoc handler of ~p", - [Reason, {From, To, IQ}]), - Res = IQ#iq{type = error, - sub_el = - [SubEl, - ?ERR_INTERNAL_SERVER_ERROR]}; - ignore -> Res = ignore; - {error, Error} -> - Res = IQ#iq{type = error, - sub_el = [SubEl, Error]}; - Command -> - Res = IQ#iq{type = result, sub_el = [Command]} - end, - if Res /= ignore -> - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - true -> ok - end; - _ -> - Txt = <<"Node not found">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)), - ejabberd_router:route(To, From, Err) - end; - #iq{} = _IQ -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> ok - end; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), - ejabberd_router:route(To, From, Err) - end; - _ -> - case str:tokens(ChanServ, <<"%">>) of - [<<_, _/binary>> = Channel, <<_, _/binary>> = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of - [] -> - ?DEBUG("open new connection~n", []), - {Username, Encoding, Port, Password} = - get_connection_params(Host, ServerHost, From, Server), - ConnectionUsername = case Packet of + case str:tokens(ChanServ, <<"%">>) of + [<<_, _/binary>> = Channel, <<_, _/binary>> = Server] -> + case ets:lookup(irc_connection, {From, Server, Host}) of + [] -> + ?DEBUG("open new connection~n", []), + {Username, Encoding, Port, Password} = + get_connection_params(Host, ServerHost, From, Server), + ConnectionUsername = case Packet of %% If the user tries to join a %% chatroom, the packet for sure %% contains the desired username. - #xmlel{name = <<"presence">>} -> - Resource; + #presence{} -> Resource; %% Otherwise, there is no firm %% conclusion from the packet. %% Better to use the configured %% username (which defaults to the %% username part of the JID). _ -> Username - end, - Ident = extract_ident(Packet), - RemoteAddr = extract_ip_address(Packet), - RealName = get_realname(ServerHost), - WebircPassword = get_webirc_password(ServerHost), - {ok, Pid} = mod_irc_connection:start(From, Host, - ServerHost, Server, - ConnectionUsername, - Encoding, Port, - Password, Ident, RemoteAddr, RealName, WebircPassword, ?MODULE), - ets:insert(irc_connection, - #irc_connection{jid_server_host = - {From, Server, Host}, - pid = Pid}), - mod_irc_connection:route_chan(Pid, Channel, Resource, - Packet), - ok; - [R] -> - Pid = R#irc_connection.pid, - ?DEBUG("send to process ~p~n", [Pid]), - mod_irc_connection:route_chan(Pid, Channel, Resource, - Packet), - ok - end; - _ -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - case str:tokens(ChanServ, <<"!">>) of - [<<_, _/binary>> = Nick, <<_, _/binary>> = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of + end, + Ident = extract_ident(Packet), + RemoteAddr = extract_ip_address(Packet), + RealName = get_realname(ServerHost), + WebircPassword = get_webirc_password(ServerHost), + {ok, Pid} = mod_irc_connection:start( + From, Host, ServerHost, Server, + ConnectionUsername, Encoding, Port, + Password, Ident, RemoteAddr, RealName, + WebircPassword, ?MODULE), + ets:insert(irc_connection, + #irc_connection{ + jid_server_host = {From, Server, Host}, + pid = Pid}), + mod_irc_connection:route_chan(Pid, Channel, Resource, Packet); + [R] -> + Pid = R#irc_connection.pid, + ?DEBUG("send to process ~p~n", [Pid]), + mod_irc_connection:route_chan(Pid, Channel, Resource, Packet) + end; + _ -> + Lang = xmpp:get_lang(Packet), + case str:tokens(ChanServ, <<"!">>) of + [<<_, _/binary>> = Nick, <<_, _/binary>> = Server] -> + case ets:lookup(irc_connection, {From, Server, Host}) of [] -> Txt = <<"IRC connection not found">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)), - ejabberd_router:route(To, From, Err); + Err = xmpp:err_service_unavailable(Txt, Lang), + ejabberd_router:route_error(To, From, Packet, Err); [R] -> Pid = R#irc_connection.pid, ?DEBUG("send to process ~p~n", [Pid]), - mod_irc_connection:route_nick(Pid, Nick, Packet), - ok - end; - _ -> - Txt = <<"Failed to parse chanserv">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_BAD_REQUEST(Lang, Txt)), - ejabberd_router:route(To, From, Err) - end - end + mod_irc_connection:route_nick(Pid, Nick, Packet) + end; + _ -> + Txt = <<"Failed to parse chanserv">>, + Err = xmpp:err_bad_request(Txt, Lang), + ejabberd_router:route_error(To, From, Packet, Err) + end end. closed_connection(Host, From, Server) -> ets:delete(irc_connection, {From, Server, Host}). -iq_disco(_ServerHost, <<>>, Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"irc">>}, - {<<"name">>, - translate:translate(Lang, <<"IRC Transport">>)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_REGISTER}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}]; +iq_disco(_ServerHost, undefined, Lang) -> + #disco_info{ + identities = [#identity{category = <<"conference">>, + type = <<"irc">>, + name = translate:translate(Lang, <<"IRC Transport">>)}], + features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_MUC, + ?NS_REGISTER, ?NS_VCARD, ?NS_COMMANDS]}; iq_disco(ServerHost, Node, Lang) -> - case lists:keysearch(Node, 1, commands(ServerHost)) of - {value, {_, Name, _}} -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-node">>}, - {<<"name">>, translate:translate(Lang, Name)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_XDATA}], children = []}]; - _ -> [] + case lists:keyfind(Node, commands(ServerHost)) of + {_, Name, _} -> + #disco_info{ + identities = [#identity{category = <<"automation">>, + type = <<"command-node">>, + name = translate:translate(Lang, Name)}], + features = [?NS_COMMANDS, ?NS_XDATA]}; + _ -> + undefined end. iq_get_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_irc">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd IRC module">>))/binary, - "\nCopyright (c) 2003-2016 ProcessOne">>}]}]. + Desc = translate:translate(Lang, <<"ejabberd IRC module">>), + Copyright = <<"Copyright (c) 2003-2016 ProcessOne">>, + #vcard_temp{fn = <<"ejabberd/mod_irc">>, + url = ?EJABBERD_URI, + desc = <<Desc/binary, $\n, Copyright/binary>>}. command_items(ServerHost, Host, Lang) -> - lists:map(fun ({Node, Name, _Function}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Host}, {<<"node">>, Node}, - {<<"name">>, - translate:translate(Lang, Name)}], - children = []} - end, - commands(ServerHost)). + lists:map(fun({Node, Name, _Function}) -> + #disco_item{jid = jid:make(Host), + node = Node, + name = translate:translate(Lang, Name)} + end, commands(ServerHost)). commands(ServerHost) -> [{<<"join">>, <<"Join channel">>, fun adhoc_join/3}, @@ -494,243 +443,120 @@ commands(ServerHost) -> adhoc_register(ServerHost, From, To, Request) end}]. -process_register(ServerHost, Host, From, To, - #iq{} = IQ) -> - case catch process_irc_register(ServerHost, Host, From, - To, IQ) - of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); - ResIQ -> - if ResIQ /= ignore -> - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); - true -> ok - end - end. - -find_xdata_el(#xmlel{children = SubEls}) -> - find_xdata_el1(SubEls). - -find_xdata_el1([]) -> false; -find_xdata_el1([#xmlel{name = Name, attrs = Attrs, - children = SubEls} - | Els]) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_XDATA -> - #xmlel{name = Name, attrs = Attrs, children = SubEls}; - _ -> find_xdata_el1(Els) - end; -find_xdata_el1([_ | Els]) -> find_xdata_el1(Els). - -process_irc_register(ServerHost, Host, From, _To, - #iq{type = Type, xmlns = XMLNS, lang = Lang, - sub_el = SubEl} = - IQ) -> - case Type of - set -> - XDataEl = find_xdata_el(SubEl), - case XDataEl of - false -> - Txt1 = <<"No data form found">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, Txt1)]}; - #xmlel{attrs = Attrs} -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"cancel">> -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = []}]}; - <<"submit">> -> - XData = jlib:parse_xdata_submit(XDataEl), - case XData of - invalid -> - Txt2 = <<"Incorrect data form">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt2)]}; - _ -> - Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, - SubEl), - <<"/">>), - case set_form(ServerHost, Host, From, Node, Lang, - XData) - of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = Res}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end - end; - _ -> - Txt3 = <<"Incorrect value of 'type' attribute">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt3)]} - end - end; - get -> - Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl), - <<"/">>), - case get_form(ServerHost, Host, From, Node, Lang) of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = Res}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end - end. - get_data(ServerHost, Host, From) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:get_data(LServer, Host, From). -get_form(ServerHost, Host, From, [], Lang) -> +get_form(ServerHost, Host, From, Lang) -> #jid{user = User, server = Server} = From, DefaultEncoding = get_default_encoding(Host), Customs = case get_data(ServerHost, Host, From) of - error -> + error -> Txt1 = <<"Database failure">>, - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt1)}; - empty -> {User, []}; - Data -> get_username_and_connection_params(Data) + {error, xmpp:err_internal_server_error(Txt1, Lang)}; + empty -> {User, []}; + Data -> get_username_and_connection_params(Data) end, case Customs of - {error, _Error} -> Customs; - {Username, ConnectionsParams} -> - {result, - [#xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need an x:data capable client to " - "configure mod_irc settings">>)}]}, - #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Registration in mod_irc for ">>))/binary, - User/binary, "@", Server/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Enter username, encodings, ports and " - "passwords you wish to use for connecting " - "to IRC servers">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"IRC Username">>)}, - {<<"var">>, <<"username">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Username}]}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"fixed">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"If you want to specify" - " different ports, " - "passwords, encodings " - "for IRC servers, " - "fill this list with " - "values in format " - "'{\"irc server\", " - "\"encoding\", port, " - "\"password\"}'. " - "By default this " - "service use \"~s\" " - "encoding, port ~p, " - "empty password.">>), - [DefaultEncoding, - ?DEFAULT_IRC_PORT]))}]}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"fixed">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Example: [{\"irc.lucky.net\", \"koi8-r\", " - "6667, \"secret\"}, {\"vendetta.fef.net\", " - "\"iso8859-1\", 7000}, {\"irc.sometestserver.n" - "et\", \"utf-8\"}].">>)}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-multi">>}, - {<<"label">>, - translate:translate(Lang, - <<"Connections parameters">>)}, - {<<"var">>, <<"connections_params">>}], - children = - lists:map(fun (S) -> - #xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, S}]} - end, - str:tokens(list_to_binary( - io_lib:format( - "~p.", - [conn_params_to_list( - ConnectionsParams)])), - <<"\n">>))}]}]} - end; -get_form(_ServerHost, _Host, _, _, _Lang) -> - {error, ?ERR_SERVICE_UNAVAILABLE}. + {error, _Error} -> + Customs; + {Username, ConnectionsParams} -> + Fs = [#xdata_field{type = 'text-single', + label = translate:translate(Lang, <<"IRC Username">>), + var = <<"username">>, + values = [Username]}, + #xdata_field{type = fixed, + values = [iolist_to_binary( + io_lib:format( + translate:translate( + Lang, + <<"If you want to specify" + " different ports, " + "passwords, encodings " + "for IRC servers, " + "fill this list with " + "values in format " + "'{\"irc server\", " + "\"encoding\", port, " + "\"password\"}'. " + "By default this " + "service use \"~s\" " + "encoding, port ~p, " + "empty password.">>), + [DefaultEncoding, ?DEFAULT_IRC_PORT]))]}, + #xdata_field{type = fixed, + values = [translate:translate( + Lang, + <<"Example: [{\"irc.lucky.net\", \"koi8-r\", " + "6667, \"secret\"}, {\"vendetta.fef.net\", " + "\"iso8859-1\", 7000}, {\"irc.sometestserver.n" + "et\", \"utf-8\"}].">>)]}, + #xdata_field{type = 'text-multi', + label = translate:translate( + Lang, <<"Connections parameters">>), + var = <<"connections_params">>, + values = str:tokens(list_to_binary( + io_lib:format( + "~p.", + [conn_params_to_list( + ConnectionsParams)])), + <<"\n">>)}], + X = #xdata{type = form, + title = <<(translate:translate( + Lang, <<"Registration in mod_irc for ">>))/binary, + User/binary, "@", Server/binary>>, + instructions = + [translate:translate( + Lang, + <<"Enter username, encodings, ports and " + "passwords you wish to use for connecting " + "to IRC servers">>)], + fields = Fs}, + {result, + #register{instructions = + translate:translate(Lang, + <<"You need an x:data capable client to " + "configure mod_irc settings">>), + xdata = X}} + end. set_data(ServerHost, Host, From, Data) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:set_data(LServer, Host, From, data_to_binary(From, Data)). -set_form(ServerHost, Host, From, [], Lang, XData) -> - case {lists:keysearch(<<"username">>, 1, XData), - lists:keysearch(<<"connections_params">>, 1, XData)} - of - {{value, {_, [Username]}}, {value, {_, Strings}}} -> - EncString = lists:foldl(fun (S, Res) -> - <<Res/binary, S/binary, "\n">> - end, - <<"">>, Strings), - case erl_scan:string(binary_to_list(EncString)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, ConnectionsParams} -> - case set_data(ServerHost, Host, From, - [{username, Username}, - {connections_params, ConnectionsParams}]) - of - {atomic, _} -> {result, []}; - _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Database failure">>)} - end; - _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Parse error">>)} - end; - _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Scan error">>)} - end; - _ -> {error, ?ERR_NOT_ACCEPTABLE} - end; -set_form(_ServerHost, _Host, _, _, _Lang, _XData) -> - {error, ?ERR_SERVICE_UNAVAILABLE}. +set_form(ServerHost, Host, From, Lang, XData) -> + case {xmpp_util:get_xdata_values(<<"username">>, XData), + xmpp_util:get_xdata_values(<<"connections_params">>, XData)} of + {[Username], [_|_] = Strings} -> + EncString = lists:foldl(fun (S, Res) -> + <<Res/binary, S/binary, "\n">> + end, <<"">>, Strings), + case erl_scan:string(binary_to_list(EncString)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, ConnectionsParams} -> + case set_data(ServerHost, Host, From, + [{username, Username}, + {connections_params, ConnectionsParams}]) of + {atomic, _} -> + {result, undefined}; + _ -> + Txt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(Txt, Lang)} + end; + _ -> + Txt = <<"Parse error">>, + {error, xmpp:err_not_acceptable(Txt, Lang)} + end; + _ -> + {error, xmpp:err_not_acceptable(<<"Scan error">>, Lang)} + end; + _ -> + Txt = <<"Incorrect value in data form">>, + {error, xmpp:err_not_acceptable(Txt, Lang)} + end. get_connection_params(Host, From, IRCServer) -> [_ | HostTail] = str:tokens(Host, <<".">>), @@ -805,212 +631,118 @@ get_connection_params(Host, ServerHost, From, iolist_to_binary(NewPassword)} end. -adhoc_join(_From, _To, - #adhoc_request{action = <<"cancel">>} = Request) -> - adhoc:produce_response(Request, - #adhoc_response{status = canceled}); -adhoc_join(From, To, - #adhoc_request{lang = Lang, node = _Node, - action = _Action, xdata = XData} = - Request) -> - if XData == false -> - Form = #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Join IRC channel">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"channel">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"IRC channel (don't put the first #)">>)}], - children = - [#xmlel{name = <<"required">>, - attrs = [], children = []}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"server">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"IRC server">>)}], - children = - [#xmlel{name = <<"required">>, - attrs = [], children = []}]}]}, - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = [Form]}); +adhoc_join(_From, _To, #adhoc_command{action = cancel} = Request) -> + xmpp_util:make_adhoc_response(Request, #adhoc_command{status = canceled}); +adhoc_join(_From, _To, #adhoc_command{lang = Lang, xdata = undefined} = Request) -> + X = #xdata{type = form, + title = translate:translate(Lang, <<"Join IRC channel">>), + fields = [#xdata_field{var = <<"channel">>, + type = 'text-single', + label = translate:translate( + Lang, <<"IRC channel (don't put the first #)">>), + required = true}, + #xdata_field{var = <<"server">>, + type = 'text-single', + label = translate:translate(Lang, <<"IRC server">>), + required = true}]}, + xmpp_utils:make_adhoc_response( + Request, #adhoc_command{status = executing, xdata = X}); +adhoc_join(From, To, #adhoc_command{lang = Lang, xdata = X} = Request) -> + Channel = case xmpp_util:get_xdata_values(<<"channel">>, X) of + [C] -> C; + _ -> false + end, + Server = case xmpp_util:get_xdata_values(<<"server">>, X) of + [S] -> S; + _ -> false + end, + if Channel /= false, Server /= false -> + RoomJID = jid:make(<<Channel/binary, "%", Server/binary>>, + To#jid.server), + Reason = translate:translate(Lang, <<"Join the IRC channel here.">>), + Body = iolist_to_binary( + io_lib:format( + translate:translate( + Lang, <<"Join the IRC channel in this Jabber ID: ~s">>), + [jid:to_string(RoomJID)])), + Invite = #message{ + body = xmpp:mk_text(Body, Lang), + sub_els = [#muc_user{ + invites = [#muc_invite{from = From, + reason = Reason}]}, + #x_conference{reason = Reason, + jid = RoomJID}]}, + ejabberd_router:route(RoomJID, From, Invite), + xmpp_util:make_adhoc_response( + Request, #adhoc_command{status = completed}); true -> - case jlib:parse_xdata_submit(XData) of - invalid -> - Txt1 = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt1)}; - Fields -> - Channel = case lists:keysearch(<<"channel">>, 1, Fields) - of - {value, {<<"channel">>, [C]}} -> C; - _ -> false - end, - Server = case lists:keysearch(<<"server">>, 1, Fields) - of - {value, {<<"server">>, [S]}} -> S; - _ -> false - end, - if Channel /= false, Server /= false -> - RoomJID = <<Channel/binary, "%", Server/binary, "@", - (To#jid.server)/binary>>, - Invite = #xmlel{name = <<"message">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"invite">>, - attrs = - [{<<"from">>, - jid:to_string(From)}], - children = - [#xmlel{name - = - <<"reason">>, - attrs - = - [], - children - = - [{xmlcdata, - translate:translate(Lang, - <<"Join the IRC channel here.">>)}]}]}]}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_XCONFERENCE}], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Join the IRC channel here.">>)}]}, - #xmlel{name = <<"body">>, - attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Join the IRC channel in this Jabber ID: ~s">>), - [RoomJID]))}]}]}, - ejabberd_router:route(jid:from_string(RoomJID), From, - Invite), - adhoc:produce_response(Request, - #adhoc_response{status = - completed}); - true -> {error, ?ERR_BAD_REQUEST} - end - end + Txt = <<"Missing 'channel' or 'server' in the data form">>, + {error, xmpp:err_bad_request(Txt, Lang)} end. +-spec adhoc_register(binary(), jid(), jid(), adhoc_command()) -> + adhoc_command() | {error, error()}. adhoc_register(_ServerHost, _From, _To, - #adhoc_request{action = <<"cancel">>} = Request) -> - adhoc:produce_response(Request, - #adhoc_response{status = canceled}); + #adhoc_command{action = cancel} = Request) -> + xmpp_util:make_adhoc_response(Request, #adhoc_command{status = canceled}); adhoc_register(ServerHost, From, To, - #adhoc_request{lang = Lang, node = _Node, xdata = XData, - action = Action} = - Request) -> + #adhoc_command{lang = Lang, xdata = X, + action = Action} = Request) -> #jid{user = User} = From, #jid{lserver = Host} = To, - if XData == false -> - case get_data(ServerHost, Host, From) of - error -> Username = User, ConnectionsParams = []; - empty -> Username = User, ConnectionsParams = []; - Data -> - {Username, ConnectionsParams} = - get_username_and_connection_params(Data) - end, - Error = false; - true -> - case jlib:parse_xdata_submit(XData) of - invalid -> - Txt1 = <<"Incorrect data form">>, - Error = {error, ?ERRT_BAD_REQUEST(Lang, Txt1)}, - Username = false, - ConnectionsParams = false; - Fields -> - Username = case lists:keysearch(<<"username">>, 1, - Fields) - of - {value, {<<"username">>, U}} -> U; - _ -> User - end, - ConnectionsParams = parse_connections_params(Fields), - Error = false - end - end, - if Error /= false -> Error; - Action == <<"complete">> -> - case set_data(ServerHost, Host, From, - [{username, Username}, - {connections_params, ConnectionsParams}]) - of - {atomic, _} -> - adhoc:produce_response(Request, - #adhoc_response{status = completed}); - _ -> - Txt2 = <<"Database failure">>, - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt2)} - end; + {Username, ConnectionsParams} = + if X == undefined -> + case get_data(ServerHost, Host, From) of + error -> {User, []}; + empty -> {User, []}; + Data -> get_username_and_connection_params(Data) + end; + true -> + {case xmpp_util:get_xdata_values(<<"username">>, X) of + [U] -> U; + _ -> User + end, parse_connections_params(X)} + end, + if Action == complete -> + case set_data(ServerHost, Host, From, + [{username, Username}, + {connections_params, ConnectionsParams}]) of + {atomic, _} -> + xmpp_util:make_adhoc_response( + Request, #adhoc_command{status = completed}); + _ -> + Txt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(Txt, Lang)} + end; true -> - Form = generate_adhoc_register_form(Lang, Username, - ConnectionsParams), - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = [Form], - actions = - [<<"next">>, - <<"complete">>]}) + Form = generate_adhoc_register_form(Lang, Username, + ConnectionsParams), + xmpp_util:make_adhoc_response( + Request, #adhoc_command{ + status = executing, + xdata = Form, + actions = #adhoc_actions{next = true, + complete = true}}) end. generate_adhoc_register_form(Lang, Username, ConnectionsParams) -> - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, <<"IRC settings">>)}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Enter username and encodings you wish " - "to use for connecting to IRC servers. " - " Press 'Next' to get more fields to " - "fill in. Press 'Complete' to save settings.">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"username">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, <<"IRC username">>)}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}, - #xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Username}]}]}] - ++ - generate_connection_params_fields(Lang, - ConnectionsParams, 1, [])}. + #xdata{type = form, + title = translate:translate(Lang, <<"IRC settings">>), + instructions = [translate:translate( + Lang, + <<"Enter username and encodings you wish " + "to use for connecting to IRC servers. " + " Press 'Next' to get more fields to " + "fill in. Press 'Complete' to save settings.">>)], + fields = [#xdata_field{ + var = <<"username">>, + type = 'text-single', + label = translate:translate(Lang, <<"IRC username">>), + required = true, + values = [Username]} + | generate_connection_params_fields( + Lang, ConnectionsParams, 1, [])]}. generate_connection_params_fields(Lang, [], Number, Acc) -> @@ -1061,91 +793,67 @@ generate_connection_params_field(Lang, Server, Encoding, end, NumberString = iolist_to_binary(integer_to_list(Number)), - [#xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"password", NumberString/binary>>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - iolist_to_binary( - io_lib:format( - translate:translate(Lang, <<"Password ~b">>), - [Number]))}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, PasswordUsed}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"port", NumberString/binary>>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - iolist_to_binary( - io_lib:format(translate:translate(Lang, <<"Port ~b">>), - [Number]))}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, PortUsed}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"encoding", NumberString/binary>>}, - {<<"type">>, <<"list-single">>}, - {<<"label">>, - list_to_binary( - io_lib:format(translate:translate( - Lang, - <<"Encoding for server ~b">>), - [Number]))}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, EncodingUsed}]} - | lists:map(fun (E) -> - #xmlel{name = <<"option">>, - attrs = [{<<"label">>, E}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, E}]}]} - end, - ?POSSIBLE_ENCODINGS)]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"server", NumberString/binary>>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - list_to_binary( - io_lib:format(translate:translate(Lang, <<"Server ~b">>), - [Number]))}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Server}]}]}]. - -parse_connections_params(Fields) -> + [#xdata_field{var = <<"password", NumberString/binary>>, + type = 'text-single', + label = iolist_to_binary( + io_lib:format( + translate:translate(Lang, <<"Password ~b">>), + [Number])), + values = [PasswordUsed]}, + #xdata_field{var = <<"port", NumberString/binary>>, + type = 'text-single', + label = iolist_to_binary( + io_lib:format( + translate:translate(Lang, <<"Port ~b">>), + [Number])), + values = [PortUsed]}, + #xdata_field{var = <<"encoding", NumberString/binary>>, + type = 'list-single', + label = list_to_binary( + io_lib:format( + translate:translate(Lang, <<"Encoding for server ~b">>), + [Number])), + values = [EncodingUsed], + options = [#xdata_option{label = E, value = E} + || E <- ?POSSIBLE_ENCODINGS]}, + #xdata_field{var = <<"server", NumberString/binary>>, + type = 'text-single', + label = list_to_binary( + io_lib:format( + translate:translate(Lang, <<"Server ~b">>), + [Number])), + values = [Server]}]. + +parse_connections_params(#xdata{fields = Fields}) -> Servers = lists:flatmap( - fun({<<"server", Var/binary>>, Value}) -> - [{Var, Value}]; + fun(#xdata_field{var = <<"server", Var/binary>>, + values = Values}) -> + [{Var, Values}]; (_) -> [] end, Fields), Encodings = lists:flatmap( - fun({<<"encoding", Var/binary>>, Value}) -> - [{Var, Value}]; + fun(#xdata_field{var = <<"encoding", Var/binary>>, + values = Values}) -> + [{Var, Values}]; (_) -> [] end, Fields), Ports = lists:flatmap( - fun({<<"port", Var/binary>>, Value}) -> - [{Var, Value}]; + fun(#xdata_field{var = <<"port", Var/binary>>, + values = Values}) -> + [{Var, Values}]; (_) -> [] end, Fields), Passwords = lists:flatmap( - fun({<<"password", Var/binary>>, Value}) -> - [{Var, Value}]; + fun(#xdata_field{var = <<"password", Var/binary>>, + values = Values}) -> + [{Var, Values}]; (_) -> [] end, Fields), - parse_connections_params(Servers, Encodings, Ports, - Passwords). + parse_connections_params(Servers, Encodings, Ports, Passwords). retrieve_connections_params(ConnectionParams, ServerN) -> @@ -1263,28 +971,19 @@ mod_opt_type(host) -> fun iolist_to_binary/1; mod_opt_type(_) -> [access, db_type, default_encoding, host]. +-spec extract_ident(stanza()) -> binary(). extract_ident(Packet) -> - case fxml:get_subtag(Packet, <<"headers">>) of - {xmlel, _Name, _Attrs, Headers} -> - extract_header(<<"X-Irc-Ident">>, Headers); - _ -> - "chatmovil" - end. + Hdrs = extract_headers(Packet), + proplists:get_value(<<"X-Irc-Ident">>, Hdrs, <<"chatmovil">>). +-spec extract_ip_address(stanza()) -> binary(). extract_ip_address(Packet) -> - case fxml:get_subtag(Packet, <<"headers">>) of - {xmlel, _Name, _Attrs, Headers} -> - extract_header(<<"X-Forwarded-For">>, Headers); - _ -> - "127.0.0.1" + Hdrs = extract_headers(Packet), + proplists:get_value(<<"X-Forwarded-For">>, Hdrs, <<"127.0.0.1">>). + +-spec extract_headers(stanza()) -> [{binary(), binary()}]. +extract_headers(Packet) -> + case xmpp:get_subtag(Packet, #shim{}) of + #shim{headers = Hs} -> Hs; + false -> [] end. - -extract_header(HeaderName, [{xmlel, _Name, _Attrs, [{xmlcdata, Value}]} | Tail]) -> - case fxml:get_attr(<<"name">>, _Attrs) of - {value, HeaderName} -> - binary_to_list(Value); - _ -> - extract_header(HeaderName, Tail) - end; -extract_header(_HeaderName, _Headers) -> - false. diff --git a/src/mod_irc_connection.erl b/src/mod_irc_connection.erl index 098c8c28..fb301330 100644 --- a/src/mod_irc_connection.erl +++ b/src/mod_irc_connection.erl @@ -41,8 +41,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). +-include("xmpp.hrl"). -define(SETS, gb_sets). @@ -66,6 +65,8 @@ inbuf = <<"">> :: binary(), outbuf = <<"">> :: binary()}). +-type state() :: #state{}. + %-define(DBGFSM, true). -ifdef(DBGFSM). @@ -228,27 +229,13 @@ code_change(_OldVsn, StateName, StateData, _Extra) -> (iolist_to_binary(S))/binary>>} end). -get_password_from_presence(#xmlel{name = <<"presence">>, - children = Els}) -> - case lists:filter(fun (El) -> - case El of - #xmlel{name = <<"x">>, attrs = Attrs} -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC -> true; - _ -> false - end; - _ -> false - end - end, - Els) - of - [ElXMUC | _] -> - case fxml:get_subtag(ElXMUC, <<"password">>) of - #xmlel{name = <<"password">>} = PasswordTag -> - {true, fxml:get_tag_cdata(PasswordTag)}; - _ -> false - end; - _ -> false +-spec get_password_from_presence(presence()) -> {true, binary()} | false. +get_password_from_presence(#presence{} = Pres) -> + case xmpp:get_subtag(Pres, #muc{}) of + #muc{password = Password} -> + {true, Password}; + _ -> + false end. %%---------------------------------------------------------------------- @@ -257,284 +244,243 @@ get_password_from_presence(#xmlel{name = <<"presence">>, %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- -handle_info({route_chan, Channel, Resource, - #xmlel{name = <<"presence">>, attrs = Attrs} = - Presence}, +handle_info({route_chan, _, _, #presence{type = error}}, _, StateData) -> + {stop, normal, StateData}; +handle_info({route_chan, Channel, _, #presence{type = unavailable}}, StateName, StateData) -> - NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of - <<"unavailable">> -> - send_stanza_unavailable(Channel, StateData), - S1 = (?SEND((io_lib:format("PART #~s\r\n", - [Channel])))), - S1#state{channels = - dict:erase(Channel, S1#state.channels)}; - <<"subscribe">> -> StateData; - <<"subscribed">> -> StateData; - <<"unsubscribe">> -> StateData; - <<"unsubscribed">> -> StateData; - <<"error">> -> stop; - _ -> - Nick = case Resource of - <<"">> -> StateData#state.nick; - _ -> Resource - end, - S1 = if Nick /= StateData#state.nick -> - S11 = (?SEND((io_lib:format("NICK ~s\r\n", - [Nick])))), - S11#state{nickchannel = Channel}; - true -> StateData - end, - case dict:is_key(Channel, S1#state.channels) of - true -> S1; - _ -> - case get_password_from_presence(Presence) of - {true, Password} -> - S2 = - (?SEND((io_lib:format("JOIN #~s ~s\r\n", - [Channel, - Password])))); - _ -> - S2 = (?SEND((io_lib:format("JOIN #~s\r\n", - [Channel])))) - end, - S2#state{channels = - dict:store(Channel, (?SETS):new(), - S1#state.channels)} - end - end, - if NewStateData == stop -> {stop, normal, StateData}; - true -> - case dict:fetch_keys(NewStateData#state.channels) of - [] -> {stop, normal, NewStateData}; - _ -> {next_state, StateName, NewStateData} - end - end; + send_stanza_unavailable(Channel, StateData), + S1 = (?SEND((io_lib:format("PART #~s\r\n", [Channel])))), + S2 = S1#state{channels = dict:erase(Channel, S1#state.channels)}, + {next_state, StateName, S2}; handle_info({route_chan, Channel, Resource, - #xmlel{name = <<"message">>, attrs = Attrs} = El}, + #presence{type = available} = Presence}, StateName, StateData) -> - NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of - <<"groupchat">> -> - case fxml:get_path_s(El, [{elem, <<"subject">>}, cdata]) - of - <<"">> -> - ejabberd_router:route( - jid:make( - iolist_to_binary([Channel, - <<"%">>, - StateData#state.server]), - StateData#state.host, - StateData#state.nick), - StateData#state.user, El), - Body = fxml:get_path_s(El, - [{elem, <<"body">>}, - cdata]), - case Body of - <<"/quote ", Rest/binary>> -> - ?SEND(<<Rest/binary, "\r\n">>); - <<"/msg ", Rest/binary>> -> - ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); - <<"/me ", Rest/binary>> -> - Strings = str:tokens(Rest, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format( - "PRIVMSG #~s :\001ACTION ~s\001\r\n", - [Channel, S]) - end, - Strings)), - ?SEND(Res); - <<"/ctcp ", Rest/binary>> -> - Words = str:tokens(Rest, <<" ">>), - case Words of - [CtcpDest | _] -> - CtcpCmd = str:to_upper( - str:substr(Rest, - str:str(Rest, - <<" ">>) - + 1)), - Res = - io_lib:format("PRIVMSG ~s :\001~s\001\r\n", - [CtcpDest, - CtcpCmd]), - ?SEND(Res); - _ -> ok - end; - _ -> - Strings = str:tokens(Body, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format("PRIVMSG #~s :~s\r\n", - [Channel, S]) - end, - Strings)), - ?SEND(Res) - end; - Subject -> - Strings = str:tokens(Subject, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format("TOPIC #~s :~s\r\n", - [Channel, S]) - end, - Strings)), - ?SEND(Res) - end; - Type - when Type == <<"chat">>; - Type == <<"">>; - Type == <<"normal">> -> - Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]), - case Body of - <<"/quote ", Rest/binary>> -> - ?SEND(<<Rest/binary, "\r\n">>); - <<"/msg ", Rest/binary>> -> - ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); - <<"/me ", Rest/binary>> -> - Strings = str:tokens(Rest, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format( - "PRIVMSG ~s :\001ACTION ~s\001\r\n", - [Resource, S]) - end, - Strings)), - ?SEND(Res); - <<"/ctcp ", Rest/binary>> -> - Words = str:tokens(Rest, <<" ">>), - case Words of - [CtcpDest | _] -> - CtcpCmd = str:to_upper( - str:substr(Rest, - str:str(Rest, - <<" ">>) - + 1)), - Res = io_lib:format("PRIVMSG ~s :~s\r\n", - [CtcpDest, - <<"\001", - CtcpCmd/binary, - "\001">>]), - ?SEND(Res); - _ -> ok - end; - _ -> - Strings = str:tokens(Body, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format( - "PRIVMSG ~s :~s\r\n", - [Resource, S]) - end, - Strings)), - ?SEND(Res) - end; - <<"error">> -> stop; - _ -> StateData - end, - if NewStateData == stop -> {stop, normal, StateData}; - true -> {next_state, StateName, NewStateData} - end; -handle_info({route_chan, Channel, Resource, - #xmlel{name = <<"iq">>} = El}, + Nick = case Resource of + <<"">> -> StateData#state.nick; + _ -> Resource + end, + S1 = if Nick /= StateData#state.nick -> + S11 = (?SEND((io_lib:format("NICK ~s\r\n", [Nick])))), + S11#state{nickchannel = Channel}; + true -> StateData + end, + {next_state, StateName, + case dict:is_key(Channel, S1#state.channels) of + true -> S1; + _ -> + case get_password_from_presence(Presence) of + {true, Password} -> + S2 = ?SEND((io_lib:format("JOIN #~s ~s\r\n", + [Channel, Password]))); + _ -> + S2 = ?SEND((io_lib:format("JOIN #~s\r\n", [Channel]))) + end, + S2#state{channels = dict:store(Channel, ?SETS:new(), + S1#state.channels)} + end}; +handle_info({route_chan, Channel, _Resource, #message{type = groupchat} = Msg}, StateName, StateData) -> + {next_state, StateName, + case xmpp:get_text(Msg#message.subject) of + <<"">> -> + ejabberd_router:route( + jid:make( + iolist_to_binary([Channel, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, Msg), + Body = xmpp:get_text(Msg#message.body), + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<<Rest/binary, "\r\n">>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG #~s :\001ACTION ~s\001\r\n", + [Channel, S]) + end, + Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr( + Rest, str:str(Rest, <<" ">>) + 1)), + Res = io_lib:format("PRIVMSG ~s :\001~s\001\r\n", + [CtcpDest, CtcpCmd]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format("PRIVMSG #~s :~s\r\n", + [Channel, S]) + end, Strings)), + ?SEND(Res) + end; + Subject -> + Strings = str:tokens(Subject, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format("TOPIC #~s :~s\r\n", + [Channel, S]) + end, + Strings)), + ?SEND(Res) + end}; +handle_info({route_chan, _Channel, Resource, #message{type = Type} = Msg}, + StateName, StateData) when Type == chat; Type == normal -> + Body = xmpp:get_text(Msg#message.body), + {next_state, StateName, + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<<Rest/binary, "\r\n">>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :\001ACTION ~s\001\r\n", + [Resource, S]) + end, Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr( + Rest, str:str(Rest, <<" ">>) + 1)), + Res = io_lib:format("PRIVMSG ~s :~s\r\n", + [CtcpDest, + <<"\001", CtcpCmd/binary, "\001">>]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format("PRIVMSG ~s :~s\r\n", + [Resource, S]) + end, Strings)), + ?SEND(Res) + end}; +handle_info({route_chan, _, _, #message{type = error}}, _, StateData) -> + {stop, normal, StateData}; +handle_info({route_chan, Channel, Resource, + #iq{type = T, sub_els = [_]} = Packet}, + StateName, StateData) when T == set; T == get -> From = StateData#state.user, - To = jid:make(iolist_to_binary([Channel, <<"%">>, - StateData#state.server]), - StateData#state.host, StateData#state.nick), - _ = case jlib:iq_query_info(El) of - #iq{xmlns = ?NS_MUC_ADMIN} = IQ -> - iq_admin(StateData, Channel, From, To, IQ); - #iq{xmlns = ?NS_VERSION} -> - Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n", - [Resource]), - _ = (?SEND(Res)), - Err = jlib:make_error_reply(El, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{xmlns = ?NS_TIME} -> - Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n", - [Resource]), - _ = (?SEND(Res)), - Err = jlib:make_error_reply(El, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{xmlns = ?NS_VCARD} -> - Res = io_lib:format("WHOIS ~s \r\n", [Resource]), - _ = (?SEND(Res)), - Err = jlib:make_error_reply(El, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{} -> - Err = jlib:make_error_reply(El, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> ok - end, + To = jid:make(iolist_to_binary([Channel, <<"%">>, StateData#state.server]), + StateData#state.host, StateData#state.nick), + try xmpp:decode_els(Packet) of + #iq{sub_els = [SubEl]} = IQ -> + case xmpp:get_ns(SubEl) of + ?NS_MUC_ADMIN -> + iq_admin(StateData, Channel, From, To, IQ); + ?NS_VERSION -> + Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n", + [Resource]), + _ = (?SEND(Res)), + Err = xmpp:err_feature_not_implemented(), + ejabberd_router:route_error(To, From, Packet, Err); + ?NS_TIME -> + Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n", + [Resource]), + _ = (?SEND(Res)), + Err = xmpp:err_feature_not_implemented(), + ejabberd_router:route_error(To, From, Packet, Err); + ?NS_VCARD -> + Res = io_lib:format("WHOIS ~s \r\n", [Resource]), + _ = (?SEND(Res)), + Err = xmpp:err_feature_not_implemented(), + ejabberd_router:route_error(To, From, Packet, Err); + _ -> + Err = xmpp:err_feature_not_implemented(), + ejabberd_router:route_error(To, From, Packet, Err) + end + catch _:{xmpp_codec, Why} -> + Err = xmpp:err_bad_request( + xmpp:format_error(Why), xmpp:get_lang(Packet)), + ejabberd_router:route_error(To, From, Packet, Err) + end, {next_state, StateName, StateData}; -handle_info({route_chan, _Channel, _Resource, _Packet}, - StateName, StateData) -> +handle_info({route_chan, Channel, _, #iq{} = IQ}, StateName, StateData) -> + From = StateData#state.user, + To = jid:make(iolist_to_binary([Channel, <<"%">>, StateData#state.server]), + StateData#state.host, StateData#state.nick), + Err = xmpp:err_feature_not_implemented(), + ejabberd_router:route_error(To, From, IQ, Err), {next_state, StateName, StateData}; -handle_info({route_nick, Nick, - #xmlel{name = <<"message">>, attrs = Attrs} = El}, +handle_info({route_nick, Nick, #message{type = chat} = Msg}, StateName, StateData) -> - NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of - <<"chat">> -> - Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]), - case Body of - <<"/quote ", Rest/binary>> -> - ?SEND(<<Rest/binary, "\r\n">>); - <<"/msg ", Rest/binary>> -> - ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); - <<"/me ", Rest/binary>> -> - Strings = str:tokens(Rest, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format( - "PRIVMSG ~s :\001ACTION ~s\001\r\n", - [Nick, S]) - end, - Strings)), - ?SEND(Res); - <<"/ctcp ", Rest/binary>> -> - Words = str:tokens(Rest, <<" ">>), - case Words of - [CtcpDest | _] -> - CtcpCmd = str:to_upper( - str:substr(Rest, - str:str(Rest, - <<" ">>) - + 1)), - Res = io_lib:format("PRIVMSG ~s :~s\r\n", - [CtcpDest, - <<"\001", - CtcpCmd/binary, - "\001">>]), - ?SEND(Res); - _ -> ok - end; - _ -> - Strings = str:tokens(Body, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format( - "PRIVMSG ~s :~s\r\n", - [Nick, S]) - end, - Strings)), - ?SEND(Res) - end; - <<"error">> -> stop; - _ -> StateData - end, - if NewStateData == stop -> {stop, normal, StateData}; - true -> {next_state, StateName, NewStateData} - end; + Body = xmpp:get_text(Msg#message.body), + {next_state, StateName, + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<<Rest/binary, "\r\n">>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :\001ACTION ~s\001\r\n", + [Nick, S]) + end, + Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr(Rest, + str:str(Rest, + <<" ">>) + + 1)), + Res = io_lib:format("PRIVMSG ~s :~s\r\n", + [CtcpDest, + <<"\001", + CtcpCmd/binary, + "\001">>]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :~s\r\n", + [Nick, S]) + end, + Strings)), + ?SEND(Res) + end}; +handle_info({route_nick, _, #message{type = error}}, _, StateData) -> + {stop, normal, StateData}; handle_info({route_nick, _Nick, _Packet}, StateName, StateData) -> {next_state, StateName, StateData}; @@ -561,13 +507,13 @@ handle_info({ircstring, <<$:, String/binary>>}, {error, {error, error_unknown_num(StateData, String, - <<"cancel">>), + cancel), StateData}}; [_, <<$5, _, _>> | _] -> {error, {error, error_unknown_num(StateData, String, - <<"cancel">>), + cancel), StateData}}; _ -> ?DEBUG("unknown irc command '~s'~n", @@ -702,11 +648,8 @@ terminate(_Reason, _StateName, FullStateData) -> {Error, StateData} = case FullStateData of {error, SError, SStateData} -> {SError, SStateData}; _ -> - {#xmlel{name = <<"error">>, - attrs = [{<<"code">>, <<"502">>}], - children = - [{xmlcdata, - <<"Server Connect Failed">>}]}, + {xmpp:err_internal_server_error( + <<"Server Connect Failed">>, ?MYLANG), FullStateData} end, (StateData#state.mod):closed_connection(StateData#state.host, @@ -714,9 +657,7 @@ terminate(_Reason, _StateName, FullStateData) -> StateData#state.server), bounce_messages(<<"Server Connect Failed">>), lists:foreach(fun (Chan) -> - Stanza = #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"error">>}], - children = [Error]}, + Stanza = xmpp:make_error(#presence{}, Error), send_stanza(Chan, StateData, Stanza) end, dict:fetch_keys(StateData#state.channels)), @@ -726,34 +667,24 @@ terminate(_Reason, _StateName, FullStateData) -> end, ok. +-spec send_stanza(binary(), state(), stanza()) -> ok. send_stanza(Chan, StateData, Stanza) -> ejabberd_router:route( - jid:make( - iolist_to_binary([Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - StateData#state.nick), + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, + StateData#state.nick), StateData#state.user, Stanza). +-spec send_stanza_unavailable(binary(), state()) -> ok. send_stanza_unavailable(Chan, StateData) -> - Affiliation = <<"member">>, - Role = <<"none">>, - Stanza = #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - Affiliation}, - {<<"role">>, Role}], - children = []}, - #xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"110">>}], - children = []}]}]}, + Affiliation = member, + Role = none, + Stanza = #presence{ + type = unavailable, + sub_els = [#muc_user{ + items = [#muc_item{affiliation = Affiliation, + role = Role}], + status_codes = [110]}]}, send_stanza(Chan, StateData, Stanza). %%%---------------------------------------------------------------------- @@ -776,20 +707,14 @@ send_text(#state{socket = Socket, encoding = Encoding}, bounce_messages(Reason) -> receive - {send_element, El} -> - #xmlel{attrs = Attrs} = El, - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - _ -> - Err = jlib:make_error_reply(El, <<"502">>, Reason), - From = jid:from_string(fxml:get_attr_s(<<"from">>, - Attrs)), - To = jid:from_string(fxml:get_attr_s(<<"to">>, - Attrs)), - ejabberd_router:route(To, From, Err) - end, - bounce_messages(Reason) - after 0 -> ok + {send_element, El} -> + From = xmpp:get_from(El), + To = xmpp:get_to(El), + Lang = xmpp:get_lang(El), + Err = xmpp:err_internal_server_error(Reason, Lang), + ejabberd_router:route_error(To, From, El, Err), + bounce_messages(Reason) + after 0 -> ok end. route_chan(Pid, Channel, Resource, Packet) -> @@ -842,51 +767,32 @@ process_channel_list_user(StateData, Chan, User) -> {U2, <<"admin">>, <<"moderator">>}; _ -> {User1, <<"member">>, <<"participant">>} end, - ejabberd_router:route(jid:make(iolist_to_binary([Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, User2), - StateData#state.user, - #xmlel{name = <<"presence">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - Affiliation}, - {<<"role">>, - Role}], - children = []}]}]}), + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, User2), + StateData#state.user, + #presence{ + sub_els = [#muc_user{items = [#muc_item{affiliation = Affiliation, + role = Role}]}]}), case catch dict:update(Chan, fun (Ps) -> (?SETS):add_element(User2, Ps) end, - StateData#state.channels) - of - {'EXIT', _} -> StateData; - NS -> StateData#state{channels = NS} + StateData#state.channels) of + {'EXIT', _} -> StateData; + NS -> StateData#state{channels = NS} end. process_channel_topic(StateData, Chan, String) -> Msg = ejabberd_regexp:replace(String, <<".*332[^:]*:">>, <<"">>), - Msg1 = filter_message(Msg), - ejabberd_router:route(jid:make(iolist_to_binary([Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"subject">>, attrs = [], - children = [{xmlcdata, Msg1}]}, - #xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - <<"Topic for #", Chan/binary, - ": ", Msg1/binary>>}]}]}). + Subject = filter_message(Msg), + Body = <<"Topic for #", Chan/binary, ": ", Subject/binary>>, + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host), + StateData#state.user, + #message{type = groupchat, + subject = xmpp:mk_text(Subject), + body = xmpp:mk_text(Body)}). process_channel_topic_who(StateData, Chan, String) -> Words = str:tokens(String, <<" ">>), @@ -901,30 +807,17 @@ process_channel_topic_who(StateData, Chan, String) -> _ -> String end, Msg2 = filter_message(Msg1), - ejabberd_router:route(jid:make(iolist_to_binary([Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #message{type = groupchat, body = xmpp:mk_text(Msg2)}). error_nick_in_use(_StateData, String) -> Msg = ejabberd_regexp:replace(String, <<".*433 +[^ ]* +">>, <<"">>), Msg1 = filter_message(Msg), - #xmlel{name = <<"error">>, - attrs = - [{<<"code">>, <<"409">>}, {<<"type">>, <<"cancel">>}], - children = - [#xmlel{name = <<"conflict">>, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []}, - #xmlel{name = <<"text">>, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], - children = [{xmlcdata, Msg1}]}]}. + xmpp:err_conflict(Msg1, ?MYLANG). process_nick_in_use(StateData, String) -> Error = error_nick_in_use(StateData, String), @@ -933,121 +826,73 @@ process_nick_in_use(StateData, String) -> % Shouldn't happen with a well behaved server StateData; Chan -> - ejabberd_router:route(jid:make(iolist_to_binary([Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - StateData#state.nick), - StateData#state.user, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"error">>}], - children = [Error]}), - StateData#state{nickchannel = undefined} + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + xmpp:make_error(#presence{}, Error)), + StateData#state{nickchannel = undefined} end. process_num_error(StateData, String) -> - Error = error_unknown_num(StateData, String, - <<"continue">>), - lists:foreach(fun (Chan) -> - ejabberd_router:route( - jid:make( - iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - StateData#state.nick), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = - [{<<"type">>, - <<"error">>}], - children = [Error]}) - end, - dict:fetch_keys(StateData#state.channels)), + Error = error_unknown_num(StateData, String, continue), + lists:foreach( + fun(Chan) -> + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, $%, StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + xmpp:make_error(#message{}, Error)) + end, dict:fetch_keys(StateData#state.channels)), StateData. process_endofwhois(StateData, _String, Nick) -> - ejabberd_router:route(jid:make(iolist_to_binary([Nick, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - <<"End of WHOIS">>}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]), + StateData#state.host), + StateData#state.user, + #message{type = chat, body = xmpp:mk_text(<<"End of WHOIS">>)}). process_whois311(StateData, String, Nick, Ident, Irchost) -> Fullname = ejabberd_regexp:replace(String, <<".*311[^:]*:">>, <<"">>), - ejabberd_router:route(jid:make(iolist_to_binary([Nick, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - [<<"WHOIS: ">>, - Nick, - <<" is ">>, - Ident, - <<"@">>, - Irchost, - <<" : ">>, - Fullname])}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #message{type = chat, + body = xmpp:mk_text( + iolist_to_binary( + [<<"WHOIS: ">>, Nick, <<" is ">>, Ident, + <<"@">>, Irchost, <<" : ">>, Fullname]))}). process_whois312(StateData, String, Nick, Ircserver) -> Ircserverdesc = ejabberd_regexp:replace(String, <<".*312[^:]*:">>, <<"">>), - ejabberd_router:route(jid:make(iolist_to_binary([Nick, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - [<<"WHOIS: ">>, - Nick, - <<" use ">>, - Ircserver, - <<" : ">>, - Ircserverdesc])}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #message{type = chat, + body = xmpp:mk_text( + iolist_to_binary( + [<<"WHOIS: ">>, Nick, <<" use ">>, Ircserver, + <<" : ">>, Ircserverdesc]))}). process_whois319(StateData, String, Nick) -> Chanlist = ejabberd_regexp:replace(String, <<".*319[^:]*:">>, <<"">>), - ejabberd_router:route(jid:make(iolist_to_binary( - [Nick, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - [<<"WHOIS: ">>, - Nick, - <<" is on ">>, - Chanlist])}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #message{type = chat, + body = xmpp:mk_text( + iolist_to_binary( + [<<"WHOIS: ">>, Nick, <<" is on ">>, Chanlist]))}). process_chanprivmsg(StateData, Chan, From, String) -> [FromUser | _] = str:tokens(From, <<"!">>), @@ -1059,17 +904,11 @@ process_chanprivmsg(StateData, Chan, From, String) -> _ -> Msg end, Msg2 = filter_message(Msg1), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #message{type = groupchat, body = xmpp:mk_text(Msg2)}). process_channotice(StateData, Chan, From, String) -> [FromUser | _] = str:tokens(From, <<"!">>), @@ -1081,17 +920,11 @@ process_channotice(StateData, Chan, From, String) -> _ -> <<"/me NOTICE: ", Msg/binary>> end, Msg2 = filter_message(Msg1), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #message{type = groupchat, body = xmpp:mk_text(Msg2)}). process_privmsg(StateData, _Nick, From, String) -> [FromUser | _] = str:tokens(From, <<"!">>), @@ -1103,17 +936,11 @@ process_privmsg(StateData, _Nick, From, String) -> _ -> Msg end, Msg2 = filter_message(Msg1), - ejabberd_router:route(jid:make(iolist_to_binary( - [FromUser, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([FromUser, <<"!">>, StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #message{type = chat, body = xmpp:mk_text(Msg2)}). process_notice(StateData, _Nick, From, String) -> [FromUser | _] = str:tokens(From, <<"!">>), @@ -1125,17 +952,11 @@ process_notice(StateData, _Nick, From, String) -> _ -> <<"/me NOTICE: ", Msg/binary>> end, Msg2 = filter_message(Msg1), - ejabberd_router:route(jid:make(iolist_to_binary( - [FromUser, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([FromUser, <<"!">>, StateData#state.server]), + StateData#state.host), + StateData#state.user, + #message{type = chat, body = xmpp:mk_text(Msg2)}). process_version(StateData, _Nick, From) -> [FromUser | _] = str:tokens(From, <<"!">>), @@ -1160,54 +981,30 @@ process_topic(StateData, Chan, From, String) -> Msg = ejabberd_regexp:replace(String, <<".*TOPIC[^:]*:">>, <<"">>), Msg1 = filter_message(Msg), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"subject">>, attrs = [], - children = [{xmlcdata, Msg1}]}, - #xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - <<"/me has changed the subject to: ", - Msg1/binary>>}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #message{type = groupchat, + subject = xmpp:mk_text(Msg1), + body = xmpp:mk_text(<<"/me has changed the subject to: ", + Msg1/binary>>)}). process_part(StateData, Chan, From, String) -> [FromUser | FromIdent] = str:tokens(From, <<"!">>), Msg = ejabberd_regexp:replace(String, <<".*PART[^:]*:">>, <<"">>), Msg1 = filter_message(Msg), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - <<"member">>}, - {<<"role">>, - <<"none">>}], - children = []}]}, - #xmlel{name = <<"status">>, attrs = [], - children = - [{xmlcdata, - list_to_binary( - [Msg1, " (", - FromIdent, ")"])}]}]}), + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #presence{type = unavailable, + sub_els = [#muc_user{ + items = [#muc_item{affiliation = member, + role = none}]}], + status = xmpp:mk_text( + list_to_binary([Msg1, " (", FromIdent, ")"]))}), case catch dict:update(Chan, fun (Ps) -> remove_element(FromUser, Ps) end, StateData#state.channels) @@ -1221,81 +1018,40 @@ process_quit(StateData, From, String) -> Msg = ejabberd_regexp:replace(String, <<".*QUIT[^:]*:">>, <<"">>), Msg1 = filter_message(Msg), - dict:map(fun (Chan, Ps) -> - case (?SETS):is_member(FromUser, Ps) of - true -> - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - FromUser), - StateData#state.user, - #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, - <<"unavailable">>}], - children = - [#xmlel{name = - <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name - = - <<"item">>, - attrs - = - [{<<"affiliation">>, - <<"member">>}, - {<<"role">>, - <<"none">>}], - children - = - []}]}, - #xmlel{name = - <<"status">>, - attrs = [], - children = - [{xmlcdata, - list_to_binary( - [Msg1, " (", - FromIdent, - ")"])}]}]}), - remove_element(FromUser, Ps); - _ -> Ps - end - end, - StateData#state.channels), + dict:map( + fun(Chan, Ps) -> + case (?SETS):is_member(FromUser, Ps) of + true -> + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, $%, StateData#state.server]), + StateData#state.host, + FromUser), + StateData#state.user, + #presence{type = unavailable, + sub_els = [#muc_user{ + items = [#muc_item{ + affiliation = member, + role = none}]}], + status = xmpp:mk_text( + list_to_binary([Msg1, " (", FromIdent, ")"]))}), + remove_element(FromUser, Ps); + _ -> + Ps + end + end, StateData#state.channels), StateData. process_join(StateData, Channel, From, _String) -> [FromUser | FromIdent] = str:tokens(From, <<"!">>), [Chan | _] = binary:split(Channel, <<":#">>), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - #xmlel{name = <<"presence">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - <<"member">>}, - {<<"role">>, - <<"participant">>}], - children = []}]}, - #xmlel{name = <<"status">>, attrs = [], - children = - [{xmlcdata, - list_to_binary(FromIdent)}]}]}), + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #presence{ + sub_els = [#muc_user{items = [#muc_item{affiliation = member, + role = participant}]}], + status = xmpp:mk_text(list_to_binary(FromIdent))}), case catch dict:update(Chan, fun (Ps) -> (?SETS):add_element(FromUser, Ps) end, StateData#state.channels) @@ -1306,160 +1062,67 @@ process_join(StateData, Channel, From, _String) -> process_mode_o(StateData, Chan, _From, Nick, Affiliation, Role) -> - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, Nick), - StateData#state.user, - #xmlel{name = <<"presence">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - Affiliation}, - {<<"role">>, - Role}], - children = []}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, Nick), + StateData#state.user, + #presence{ + sub_els = [#muc_user{items = [#muc_item{affiliation = Affiliation, + role = Role}]}]}). process_kick(StateData, Chan, From, Nick, String) -> Msg = lists:last(str:tokens(String, <<":">>)), Msg2 = <<Nick/binary, " kicked by ", From/binary, " (", (filter_message(Msg))/binary, ")">>, - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, Nick), - StateData#state.user, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - <<"none">>}, - {<<"role">>, - <<"none">>}], - children = []}, - #xmlel{name = <<"status">>, - attrs = - [{<<"code">>, - <<"307">>}], - children = []}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host), + StateData#state.user, + #message{type = groupchat, body = xmpp:mk_text(Msg2)}), + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, Nick), + StateData#state.user, + #presence{type = unavailable, + sub_els = [#muc_user{items = [#muc_item{ + affiliation = none, + role = none}], + status_codes = [307]}]}). process_nick(StateData, From, NewNick) -> [FromUser | _] = str:tokens(From, <<"!">>), [Nick | _] = binary:split(NewNick, <<":">>), - NewChans = dict:map(fun (Chan, Ps) -> - case (?SETS):is_member(FromUser, Ps) of - true -> - ejabberd_router:route(jid:make( - iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - FromUser), - StateData#state.user, - #xmlel{name = - <<"presence">>, - attrs = - [{<<"type">>, - <<"unavailable">>}], - children = - [#xmlel{name - = - <<"x">>, - attrs - = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children - = - [#xmlel{name - = - <<"item">>, - attrs - = - [{<<"affiliation">>, - <<"member">>}, - {<<"role">>, - <<"participant">>}, - {<<"nick">>, - Nick}], - children - = - []}, - #xmlel{name - = - <<"status">>, - attrs - = - [{<<"code">>, - <<"303">>}], - children - = - []}]}]}), - ejabberd_router:route(jid:make( - iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - Nick), - StateData#state.user, - #xmlel{name = - <<"presence">>, - attrs = [], - children = - [#xmlel{name - = - <<"x">>, - attrs - = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children - = - [#xmlel{name - = - <<"item">>, - attrs - = - [{<<"affiliation">>, - <<"member">>}, - {<<"role">>, - <<"participant">>}], - children - = - []}]}]}), - (?SETS):add_element(Nick, - remove_element(FromUser, - Ps)); - _ -> Ps - end - end, - StateData#state.channels), + NewChans = + dict:map( + fun(Chan, Ps) -> + case (?SETS):is_member(FromUser, Ps) of + true -> + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, $%, StateData#state.server]), + StateData#state.host, + FromUser), + StateData#state.user, + #presence{ + type = unavailable, + sub_els = [#muc_user{ + items = [#muc_item{ + affiliation = member, + role = participant, + nick = Nick}], + status_codes = [303]}]}), + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, $%, StateData#state.server]), + StateData#state.host, Nick), + StateData#state.user, + #presence{ + sub_els = [#muc_user{ + items = [#muc_item{ + affiliation = member, + role = participant}]}]}), + (?SETS):add_element(Nick, remove_element(FromUser, Ps)); + _ -> Ps + end + end, StateData#state.channels), if FromUser == StateData#state.nick -> StateData#state{nick = Nick, nickchannel = undefined, channels = NewChans}; @@ -1467,43 +1130,23 @@ process_nick(StateData, From, NewNick) -> end. process_error(StateData, String) -> - lists:foreach(fun (Chan) -> - ejabberd_router:route(jid:make( - iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - StateData#state.nick), - StateData#state.user, - #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, - <<"error">>}], - children = - [#xmlel{name = - <<"error">>, - attrs = - [{<<"code">>, - <<"502">>}], - children = - [{xmlcdata, - String}]}]}) - end, - dict:fetch_keys(StateData#state.channels)). + lists:foreach( + fun(Chan) -> + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, $%, StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + xmpp:make_error( + #presence{}, + xmpp:err_internal_server_error(String, ?MYLANG))) + end, dict:fetch_keys(StateData#state.channels)). error_unknown_num(_StateData, String, Type) -> Msg = ejabberd_regexp:replace(String, <<".*[45][0-9][0-9] +[^ ]* +">>, <<"">>), Msg1 = filter_message(Msg), - #xmlel{name = <<"error">>, - attrs = [{<<"code">>, <<"500">>}, {<<"type">>, Type}], - children = - [#xmlel{name = <<"undefined-condition">>, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []}, - #xmlel{name = <<"text">>, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], - children = [{xmlcdata, Msg1}]}]}. + xmpp:err_undefined_condition(Type, Msg1, ?MYLANG). remove_element(E, Set) -> case (?SETS):is_element(E, Set) of @@ -1512,49 +1155,33 @@ remove_element(E, Set) -> end. iq_admin(StateData, Channel, From, To, - #iq{type = Type, xmlns = XMLNS, sub_el = SubEl} = IQ) -> - case catch process_iq_admin(StateData, Channel, Type, - SubEl) - of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); - Res -> - if Res /= ignore -> - ResIQ = case Res of - {result, ResEls} -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = ResEls}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); - true -> ok - end + #iq{type = Type, sub_els = [SubEl]} = IQ) -> + try process_iq_admin(StateData, Channel, Type, SubEl) of + ignore -> + ignore; + {result, Result} -> + ejabberd_router:route(To, From, xmpp:make_iq_result(IQ, Result)); + {error, Error} -> + ejabberd_router:route_error(To, From, IQ, Error) + catch E:R -> + ?ERROR_MSG("failed to process admin query from ~s: ~p", + [jid:to_string(From), {E, {R, erlang:get_stacktrace()}}]), + ejabberd_router:route_error( + To, From, IQ, xmpp:internal_server_error()) end. -process_iq_admin(StateData, Channel, set, SubEl) -> - case fxml:get_subtag(SubEl, <<"item">>) of - false -> {error, ?ERR_BAD_REQUEST}; - ItemEl -> - Nick = fxml:get_tag_attr_s(<<"nick">>, ItemEl), - Affiliation = fxml:get_tag_attr_s(<<"affiliation">>, - ItemEl), - Role = fxml:get_tag_attr_s(<<"role">>, ItemEl), - Reason = fxml:get_path_s(ItemEl, - [{elem, <<"reason">>}, cdata]), - process_admin(StateData, Channel, Nick, Affiliation, - Role, Reason) - end; -process_iq_admin(_StateData, _Channel, get, _SubEl) -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}. - -process_admin(_StateData, _Channel, <<"">>, - _Affiliation, _Role, _Reason) -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}; -process_admin(StateData, Channel, Nick, _Affiliation, - <<"none">>, Reason) -> +process_iq_admin(_StateData, _Channel, set, #muc_admin{items = []}) -> + {error, xmpp:err_bad_request()}; +process_iq_admin(StateData, Channel, set, #muc_admin{items = [Item|_]}) -> + process_admin(StateData, Channel, Item); +process_iq_admin(_StateData, _Channel, _, _SubEl) -> + {error, xmpp:err_feature_not_implemented()}. + +process_admin(_StateData, _Channel, #muc_item{nick = <<"">>}) -> + {error, xmpp:err_feature_not_implemented()}; +process_admin(StateData, Channel, #muc_item{nick = Nick, + reason = Reason, + role = none}) -> case Reason of <<"">> -> send_text(StateData, @@ -1564,10 +1191,9 @@ process_admin(StateData, Channel, Nick, _Affiliation, io_lib:format("KICK #~s ~s :~s\r\n", [Channel, Nick, Reason])) end, - {result, []}; -process_admin(_StateData, _Channel, _Nick, _Affiliation, - _Role, _Reason) -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}. + {result, undefined}; +process_admin(_StateData, _Channel, _Item) -> + {error, xmpp:err_feature_not_implemented()}. filter_message(Msg) -> list_to_binary( diff --git a/src/mod_irc_mnesia.erl b/src/mod_irc_mnesia.erl index 9f8117ad..95cceb54 100644 --- a/src/mod_irc_mnesia.erl +++ b/src/mod_irc_mnesia.erl @@ -13,7 +13,7 @@ %% API -export([init/2, get_data/3, set_data/4, import/2]). --include("jlib.hrl"). +-include("jid.hrl"). -include("mod_irc.hrl"). -include("logger.hrl"). diff --git a/src/mod_irc_riak.erl b/src/mod_irc_riak.erl index 6ac7befd..a71859c5 100644 --- a/src/mod_irc_riak.erl +++ b/src/mod_irc_riak.erl @@ -13,7 +13,7 @@ %% API -export([init/2, get_data/3, set_data/4, import/2]). --include("jlib.hrl"). +-include("jid.hrl"). -include("mod_irc.hrl"). %%%=================================================================== diff --git a/src/mod_irc_sql.erl b/src/mod_irc_sql.erl index 8aa428e5..7905db91 100644 --- a/src/mod_irc_sql.erl +++ b/src/mod_irc_sql.erl @@ -15,7 +15,7 @@ %% API -export([init/2, get_data/3, set_data/4, import/1, import/2, export/1]). --include("jlib.hrl"). +-include("jid.hrl"). -include("mod_irc.hrl"). -include("ejabberd_sql_pt.hrl"). diff --git a/src/xmpp.erl b/src/xmpp.erl index a927a6a9..b4324315 100644 --- a/src/xmpp.erl +++ b/src/xmpp.erl @@ -41,7 +41,7 @@ err_resource_constraint/0, err_resource_constraint/2, err_service_unavailable/0, err_service_unavailable/2, err_subscription_required/0, err_subscription_required/2, - err_undefined_condition/0, err_undefined_condition/2, + err_undefined_condition/1, err_undefined_condition/3, err_unexpected_request/0, err_unexpected_request/2]). %% XMPP stream errors @@ -567,14 +567,16 @@ err_subscription_required(Text, Lang) -> err(auth, 'subscription-required', 407, Text, Lang). %% No error type is defined for <undefined-confition/>. -%% We choose "modify" as it's used in RFC 6120 example. --spec err_undefined_condition() -> error(). -err_undefined_condition() -> - err(modify, 'undefined-condition', 500). - --spec err_undefined_condition(binary(), binary() | undefined) -> error(). -err_undefined_condition(Text, Lang) -> - err(modify, 'undefined-condition', 500, Text, Lang). +%% Let user provide the type. +-spec err_undefined_condition('auth' | 'cancel' | 'continue' | + 'modify' | 'wait') -> error(). +err_undefined_condition(Type) -> + err(Type, 'undefined-condition', 500). + +-spec err_undefined_condition('auth' | 'cancel' | 'continue' | 'modify' | 'wait', + binary(), binary() | undefined) -> error(). +err_undefined_condition(Type, Text, Lang) -> + err(Type, 'undefined-condition', 500, Text, Lang). %% RFC 6120 says error type SHOULD be "wait" or "modify". %% RFC 3920 and XEP-0082 says it SHOULD be "wait". |