%%%---------------------------------------------------------------------- %%% File : ejabberd_s2s_out.erl %%% Author : Alexey Shchepin %%% Purpose : %%% Created : 6 Dec 2002 by Alexey Shchepin %%% Id : $Id$ %%%---------------------------------------------------------------------- -module(ejabberd_s2s_out). -author('alexey@sevcom.net'). -vsn('$Revision$ '). -behaviour(gen_fsm). %% External exports -export([start/2, receiver/2, send_text/2, send_element/2]). %% gen_fsm callbacks -export([init/1, wait_for_stream/2, wait_for_key/2, wait_for_verification/2, session_established/2, handle_info/3, terminate/3]). -record(state, {socket, receiver, streamid, myself = "localhost", server, type, xmlpid}). -include("ejabberd.hrl"). -define(DBGFSM, true). -ifdef(DBGFSM). -define(FSMOPTS, [{debug, [trace]}]). -else. -define(FSMOPTS, []). -endif. -define(STREAM_HEADER, "" "" ). -define(STREAM_TRAILER, ""). -define(INVALID_HEADER_ERR, "" "Invalid Stream Header" "" ). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start(Host, Type) -> gen_fsm:start(ejabberd_s2s_out, [Host, Type], ?FSMOPTS). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, StateName, StateData} | %% {ok, StateName, StateData, Timeout} | %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- init([Host, Type]) -> {ok, Socket} = gen_tcp:connect(Host, 5569, [binary, {packet, 0}]), XMLStreamPid = xml_stream:start(self()), %ReceiverPid = spawn(?MODULE, receiver, [Socket, self()]), send_text(Socket, ?STREAM_HEADER), {ok, wait_for_stream, #state{socket = Socket, xmlpid = XMLStreamPid, streamid = new_id(), server = Host, type = Type}}. %%---------------------------------------------------------------------- %% Func: StateName/2 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- state_name(Event, StateData) -> {next_state, state_name, StateData}. wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> % TODO case {xml:get_attr_s("xmlns", Attrs), xml:get_attr_s("xmlns:db", Attrs)} of {"jabber:server", "jabber:server:dialback"} -> case StateData#state.type of new -> % TODO {next_state, wait_for_key, StateData}; {verify, Pid, Key} -> send_element(StateData#state.socket, {xmlelement, "db:verify", [{"from", "127.0.0.1"}, {"to", StateData#state.server}], [{xmlcdata, Key}]}), {next_state, wait_for_verification, StateData} end; _ -> send_text(StateData#state.socket, ?INVALID_HEADER_ERR), {stop, normal, StateData} end; wait_for_stream(closed, StateData) -> {stop, normal, StateData}. wait_for_key({xmlstreamelement, El}, StateData) -> case is_key_packet(El) of {key, To, From, Id, Key} -> io:format("GET KEY: ~p~n", [{To, From, Id, Key}]), % TODO {next_state, wait_for_key, StateData}; {verify, To, From, Id, Key} -> io:format("VERIFY KEY: ~p~n", [{To, From, Id, Key}]), % TODO {next_state, wait_for_key, StateData}; _ -> {next_state, wait_for_key, StateData} end; wait_for_key({xmlstreamend, Name}, StateData) -> % TODO {stop, normal, StateData}; wait_for_key(closed, StateData) -> {stop, normal, StateData}. wait_for_verification({xmlstreamelement, El}, StateData) -> case is_verify_res(El) of {result, To, From, Id, Type} -> case Type of "valid" -> io:format("VALID KEY~n", []); % TODO _ -> % TODO ok end, {stop, normal, StateData}; _ -> {next_state, wait_for_verification, StateData} end; wait_for_verification({xmlstreamend, Name}, StateData) -> % TODO {stop, normal, StateData}; wait_for_verification(closed, StateData) -> {stop, normal, StateData}. session_established({xmlstreamelement, El}, StateData) -> {xmlelement, Name, Attrs, Els} = El, % TODO FromJID = {StateData#state.server}, To = xml:get_attr_s("to", Attrs), ToJID = case To of "" -> {"", StateData#state.server, ""}; _ -> jlib:string_to_jid(To) end, case ToJID of error -> % TODO error; _ -> %?DEBUG("FromJID=~w, ToJID=~w, El=~w~n", [FromJID, ToJID, El]), ejabberd_router:route(FromJID, ToJID, El) end, {next_state, session_established, StateData}; session_established(closed, StateData) -> % TODO {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}. %%---------------------------------------------------------------------- %% 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(Event, From, StateName, StateData) -> Reply = ok, {reply, Reply, 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#state.socket, Text), {next_state, StateName, StateData}; handle_info({tcp, Socket, Data}, StateName, StateData) -> xml_stream:send_text(StateData#state.xmlpid, Data), {next_state, StateName, StateData}; handle_info({tcp_closed, Socket}, StateName, StateData) -> self() ! closed, {next_state, StateName, StateData}; handle_info({tcp_error, Socket, Reason}, StateName, StateData) -> self() ! closed, {next_state, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: terminate/3 %% Purpose: Shutdown the fsm %% Returns: any %%---------------------------------------------------------------------- terminate(Reason, StateName, StateData) -> % case StateData#state.user of % "" -> % ok; % _ -> % %ejabberd_sm:close_session(StateData#state.user, % % StateData#state.resource) % end, gen_tcp:close(StateData#state.socket), ok. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- receiver(Socket, C2SPid) -> XMLStreamPid = xml_stream:start(C2SPid), receiver(Socket, C2SPid, XMLStreamPid). receiver(Socket, C2SPid, XMLStreamPid) -> case gen_tcp:recv(Socket, 0) of {ok, Text} -> xml_stream:send_text(XMLStreamPid, Text), receiver(Socket, C2SPid, XMLStreamPid); {error, closed} -> exit(XMLStreamPid, closed), gen_fsm:send_event(C2SPid, closed), ok end. send_text(Socket, Text) -> gen_tcp:send(Socket,Text). send_element(Socket, El) -> send_text(Socket, xml:element_to_string(El)). new_id() -> lists:flatten(io_lib:format("~p", [random:uniform(65536*65536)])). is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:result" -> {key, xml:get_attr_s("to", Attrs), xml:get_attr_s("from", Attrs), xml:get_attr_s("id", Attrs), xml:get_cdata(Els)}; is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:verify" -> {verify, xml:get_attr_s("to", Attrs), xml:get_attr_s("from", Attrs), xml:get_attr_s("id", Attrs), xml:get_cdata(Els)}; is_key_packet(_) -> false. is_verify_res({xmlelement, Name, Attrs, Els}) when Name == "db:result" -> {result, xml:get_attr_s("to", Attrs), xml:get_attr_s("from", Attrs), xml:get_attr_s("id", Attrs), xml:get_attr_s("type", Attrs)}; is_verify_res({xmlelement, Name, Attrs, Els}) when Name == "db:verify" -> {result, xml:get_attr_s("to", Attrs), xml:get_attr_s("from", Attrs), xml:get_attr_s("id", Attrs), xml:get_attr_s("type", Attrs)}; is_verify_res(_) -> false. get_auth_tags([{xmlelement, Name, Attrs, Els}| L], U, P, D, R) -> CData = xml:get_cdata(Els), case Name of "username" -> get_auth_tags(L, CData, P, D, R); "password" -> get_auth_tags(L, U, CData, D, R); "digest" -> get_auth_tags(L, U, P, CData, R); "resource" -> get_auth_tags(L, U, P, D, CData); _ -> get_auth_tags(L, U, P, D, R) end; get_auth_tags([_ | L], U, P, D, R) -> get_auth_tags(L, U, P, D, R); get_auth_tags([], U, P, D, R) -> {U, P, D, R}.