diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index 532a06f3f..d9ff728b7 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -10,33 +10,27 @@ -author('alexey@sevcom.net'). -vsn('$Revision$ '). --export([start/0, init/0]). +-export([start/0, init/2]). start() -> - register(ejabberd_listener, spawn(?MODULE, init, [])). + register(ejabberd_listener_c2s, + spawn(?MODULE, init, [5522, ejabberd_c2s])), + register(ejabberd_listener_s2s, + spawn(?MODULE, init, [5269, ejabberd_s2s_in])). -init() -> - {ok, ListenSocket} = gen_tcp:listen(5522, [binary, +init(Port, CallbackModule) -> + {ok, ListenSocket} = gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]), - accept(ListenSocket). + accept(ListenSocket, CallbackModule). -accept(ListenSocket) -> +accept(ListenSocket, CallbackModule) -> case gen_tcp:accept(ListenSocket) of {ok,Socket} -> - ejabberd_c2s:start(Socket), - accept(ListenSocket) - end. - - - -do_recv(Sock, Bs) -> - case gen_tcp:recv(Sock, 0) of - {ok, B} -> - do_recv(Sock, [Bs, B]); - {error, closed} -> - {ok, list_to_binary(Bs)} + apply(CallbackModule, start, [Socket]), + %ejabberd_c2s:start(Socket), + accept(ListenSocket, CallbackModule) end. diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl new file mode 100644 index 000000000..b0300b81f --- /dev/null +++ b/src/ejabberd_s2s_in.erl @@ -0,0 +1,274 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_s2s_in.erl +%%% Author : Alexey Shchepin +%%% Purpose : +%%% Created : 6 Dec 2002 by Alexey Shchepin +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(ejabberd_s2s_in). +-author('alexey@sevcom.net'). +-vsn('$Revision$ '). + +-behaviour(gen_fsm). + +%% External exports +-export([start/1, receiver/2, send_text/2, send_element/2]). + +%% gen_fsm callbacks +-export([init/1, wait_for_stream/2, wait_for_key/2, session_established/2, + handle_info/3, + terminate/3]). + +-record(state, {socket, receiver, streamid, + myself = "localhost", server}). + +-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(Socket) -> + gen_fsm:start(ejabberd_s2s_in, [Socket], ?FSMOPTS). + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_fsm +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, StateName, StateData} | +%% {ok, StateName, StateData, Timeout} | +%% ignore | +%% {stop, StopReason} +%%---------------------------------------------------------------------- +init([Socket]) -> + ReceiverPid = spawn(?MODULE, receiver, [Socket, self()]), + {ok, wait_for_stream, #state{socket = Socket, + receiver = ReceiverPid, + streamid = new_id()}}. + +%%---------------------------------------------------------------------- +%% 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"} -> + send_text(StateData#state.socket, ?STREAM_HEADER), + {next_state, wait_for_key, StateData}; + _ -> + 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 + ejabberd_s2s_out:start(From, {verify, self(), Key}), + {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}. + +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}. + +%%---------------------------------------------------------------------- +%% 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. + +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}. + + + + diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl new file mode 100644 index 000000000..b51a61d2a --- /dev/null +++ b/src/ejabberd_s2s_out.erl @@ -0,0 +1,341 @@ +%%%---------------------------------------------------------------------- +%%% 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}. + + + +