xmpp.chapril.org-ejabberd/src/ejabberd_c2s.erl

676 lines
20 KiB
Erlang
Raw Normal View History

2002-11-18 21:39:47 +01:00
%%%----------------------------------------------------------------------
%%% File : ejabberd_c2s.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose :
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(ejabberd_c2s).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_fsm).
%% External exports
-export([start/1, receiver/2, sender/1, send_text/2, send_element/2]).
2002-11-18 21:39:47 +01:00
%% gen_fsm callbacks
%-export([init/1, state_name/2, state_name/3, handle_event/3,
% handle_sync_event/4, handle_info/3, terminate/3]).
%
-export([init/1, wait_for_stream/2, wait_for_auth/2, session_established/2,
handle_event/3,
handle_sync_event/4,
code_change/4,
handle_info/3,
terminate/3]).
2002-11-18 21:39:47 +01:00
-include("ejabberd.hrl").
-include("namespaces.hrl").
2002-11-18 21:39:47 +01:00
-define(SETS, gb_sets).
-record(state, {socket, sender, receiver, streamid,
user = "", server = ?MYNAME, resource = "",
pres_t = ?SETS:new(),
pres_f = ?SETS:new(),
pres_a = ?SETS:new(),
pres_i = ?SETS:new(),
pres_last, pres_pri,
pres_timestamp,
pres_invis = false}).
%-define(DBGFSM, true).
2002-11-18 21:39:47 +01:00
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, []).
-endif.
-define(STREAM_HEADER,
"<?xml version='1.0'?>"
2002-11-18 21:39:47 +01:00
"<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"id='~s' from='~s'>"
).
-define(STREAM_TRAILER, "</stream:stream>").
-define(INVALID_NS_ERR, "<stream:error>Invalid Namespace</stream:error>").
2002-11-18 21:39:47 +01:00
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Socket) ->
gen_fsm:start(ejabberd_c2s, [Socket], ?FSMOPTS).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([Socket]) ->
SenderPid = spawn(?MODULE, sender, [Socket]),
ReceiverPid = spawn(?MODULE, receiver, [Socket, self()]),
{ok, wait_for_stream, #state{socket = Socket,
receiver = ReceiverPid,
sender = SenderPid,
streamid = new_id()}}.
2002-11-18 21:39:47 +01:00
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
% TODO
Header = io_lib:format(?STREAM_HEADER,
[StateData#state.streamid, ?MYNAME]),
send_text(StateData#state.sender, Header),
2002-11-18 21:39:47 +01:00
case lists:keysearch("xmlns:stream", 1, Attrs) of
{value, {"xmlns:stream", "http://etherx.jabber.org/streams"}} ->
% TODO
{next_state, wait_for_auth, StateData};
_ ->
send_text(StateData#state.sender, ?INVALID_NS_ERR ?STREAM_TRAILER),
{stop, normal, StateData}
2002-11-18 21:39:47 +01:00
end;
wait_for_stream(closed, StateData) ->
{stop, normal, StateData}.
wait_for_auth({xmlstreamelement, El}, StateData) ->
case is_auth_packet(El) of
{auth, ID, {U, P, D, ""}} ->
Err = jlib:make_error_reply(El, "406", "Not Acceptable"),
send_element(StateData#state.sender, Err),
{next_state, wait_for_auth, StateData};
{auth, ID, {U, P, D, R}} ->
io:format("AUTH: ~p~n", [{U, P, D, R}]),
case ejabberd_auth:check_password(U, P,
StateData#state.streamid, D) of
true ->
ejabberd_sm:open_session(U, R),
Res = jlib:make_result_iq_reply(El),
send_element(StateData#state.sender, Res),
{Fs, Ts} = mod_roster:get_subscription_lists(U),
{next_state, session_established,
StateData#state{user = U,
resource = R,
pres_f = ?SETS:from_list(Fs),
pres_t = ?SETS:from_list(Ts)}};
_ ->
Err = jlib:make_error_reply(El, "401", "Unauthorized"),
send_element(StateData#state.sender, Err),
{next_state, wait_for_auth, StateData}
end;
_ ->
case jlib:iq_query_info(El) of
{iq, ID, Type, ?NS_REGISTER, SubEl} ->
ResIQ = mod_register:process_iq(
{"", "", ""}, {"", ?MYNAME, ""},
{iq, ID, Type, ?NS_REGISTER, SubEl}),
Res1 = jlib:replace_from_to({"", ?MYNAME, ""},
{"", "", ""},
jlib:iq_to_xml(ResIQ)),
Res = jlib:remove_attr("to", Res1),
send_element(StateData#state.sender, Res),
{next_state, wait_for_auth, StateData};
_ ->
{next_state, wait_for_auth, StateData}
end
end;
2002-11-18 21:39:47 +01:00
wait_for_auth({xmlstreamend, Name}, StateData) ->
% TODO
{stop, normal, StateData};
wait_for_auth(closed, StateData) ->
{stop, normal, StateData}.
session_established({xmlstreamelement, El}, StateData) ->
{xmlelement, Name, Attrs, Els} = El,
Server = StateData#state.server,
FromJID = {StateData#state.user,
Server,
StateData#state.resource},
To = xml:get_attr_s("to", Attrs),
ToJID = case To of
"" ->
{"", Server, ""};
_ ->
jlib:string_to_jid(To)
end,
NewState =
case ToJID of
error ->
% TODO
StateData;
_ ->
case Name of
"presence" ->
case ToJID of
{"", Server, ""} ->
?DEBUG("presence_update(~p,~n\t~p,~n\t~p)",
[FromJID, El, StateData]),
presence_update(FromJID, El, StateData);
_ ->
presence_track(FromJID, ToJID, El, StateData)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, El),
StateData
end
end,
{next_state, session_established, NewState};
session_established({xmlstreamend, Name}, StateData) ->
{stop, normal, StateData};
session_established(closed, StateData) ->
% TODO
{stop, normal, StateData}.
2002-11-18 21:39:47 +01:00
%%----------------------------------------------------------------------
%% 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}.
2002-11-18 21:39:47 +01:00
%%----------------------------------------------------------------------
%% 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}.
code_change(OldVsn, StateName, StateData, Extra) ->
{ok, StateName, StateData}.
2002-11-18 21:39:47 +01:00
%%----------------------------------------------------------------------
%% 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.sender, Text),
{next_state, StateName, StateData};
handle_info(replaced, StateName, StateData) ->
% TODO
%send_text(StateData#state.sender, Text),
{stop, normal, StateData#state{user = ""}};
handle_info({route, From, To, Packet}, StateName, StateData) ->
{xmlelement, Name, Attrs, Els} = Packet,
{FU, FS, FR} = From,
{Pass, NewAttrs, NewState} =
case Name of
"presence" ->
case xml:get_attr_s("type", Attrs) of
"probe" ->
process_presence_probe(From, To, StateData),
{false, Attrs, StateData};
"error" ->
NewA = remove_element(From, StateData#state.pres_a),
{true, Attrs, StateData#state{pres_a = NewA}};
"invisible" ->
Attrs1 = lists:keydelete("type", 1, Attrs),
{true, [{"type", "unavailable"} | Attrs1], StateData};
"subscribe" ->
{true, Attrs, StateData};
"subscribed" ->
{true, Attrs, StateData};
"unsubscribe" ->
{true, Attrs, StateData};
"unsubscribed" ->
{true, Attrs, StateData};
_ ->
{true, Attrs, StateData}
end;
"broadcast" ->
?DEBUG("broadcast!!!!!!!!!!!~n~p~n", [Els]),
NewSt = case Els of
[{item, IJID, ISubscription}] ->
roster_change(IJID, ISubscription,
StateData);
_ ->
StateData
end,
{false, Attrs, NewSt};
"iq" ->
IQ = jlib:iq_query_info(Packet),
case IQ of
{iq, ID, Type, ?NS_VCARD, SubEl} ->
ResIQ = mod_vcard:process_sm_iq(From, To, IQ),
ejabberd_router:route(To,
From,
jlib:iq_to_xml(ResIQ)),
{false, Attrs, StateData};
%{iq, ID, Type, ?NS_VCARD, SubEl} ->
% TODO: don't pass packets until roster loaded
%{true, Attrs, StateData};
_ ->
{true, Attrs, StateData}
end;
_ ->
{true, Attrs, StateData}
end,
if Pass ->
Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
jlib:jid_to_string(To),
NewAttrs),
Text = xml:element_to_string({xmlelement, Name, Attrs2, Els}),
send_text(StateData#state.sender, Text);
true ->
ok
end,
{next_state, StateName, NewState}.
2002-11-18 21:39:47 +01:00
%%----------------------------------------------------------------------
%% 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),
From = {StateData#state.user,
StateData#state.server,
StateData#state.resource},
Packet = {xmlelement, "presence", [{"type", "unavailable"}], []},
ejabberd_sm:unset_presence(StateData#state.user,
StateData#state.resource),
presence_broadcast(From, StateData#state.pres_a, Packet),
presence_broadcast(From, StateData#state.pres_i, Packet)
end,
StateData#state.sender ! close,
2002-11-18 21:39:47 +01:00
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, Reason} ->
exit(XMLStreamPid, closed),
2002-11-18 21:39:47 +01:00
gen_fsm:send_event(C2SPid, closed),
ok
end.
sender(Socket) ->
receive
{send_text, Text} ->
2002-11-18 21:39:47 +01:00
gen_tcp:send(Socket,Text),
sender(Socket);
close ->
gen_tcp:close(Socket),
2002-11-18 21:39:47 +01:00
ok
end.
send_text(Pid, Text) ->
Pid ! {send_text, Text}.
2002-11-18 21:39:47 +01:00
send_element(Pid, El) ->
send_text(Pid, xml:element_to_string(El)).
new_id() ->
randoms:get_string().
is_auth_packet(El) ->
case jlib:iq_query_info(El) of
{iq, ID, Type, ?NS_AUTH, SubEl} ->
{xmlelement, _, _, Els} = SubEl,
{auth, ID,
get_auth_tags(Els, "", "", "", "")};
_ ->
false
end.
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}.
process_presence_probe(From, To, StateData) ->
case StateData#state.pres_last of
undefined ->
ok;
_ ->
Cond1 = (not StateData#state.pres_invis)
and ?SETS:is_element(From, StateData#state.pres_f)
and (not ?SETS:is_element(From, StateData#state.pres_i)),
Cond2 = StateData#state.pres_invis
and ?SETS:is_element(From, StateData#state.pres_f)
and ?SETS:is_element(From, StateData#state.pres_a),
if
Cond1 ->
ejabberd_router:route(To, From,
StateData#state.pres_last);
Cond2 ->
ejabberd_router:route(To, From,
{xmlelement, "presence",
[],
[]});
true ->
ok
end
end.
presence_update(From, Packet, StateData) ->
{xmlelement, Name, Attrs, Els} = Packet,
case xml:get_attr_s("type", Attrs) of
"unavailable" ->
ejabberd_sm:unset_presence(StateData#state.user,
StateData#state.resource),
presence_broadcast(From, StateData#state.pres_a, Packet),
presence_broadcast(From, StateData#state.pres_i, Packet),
StateData#state{pres_last = undefined,
pres_a = ?SETS:new(),
pres_i = ?SETS:new(),
pres_invis = false};
"invisible" ->
NewState =
if
not StateData#state.pres_invis ->
presence_broadcast(From, StateData#state.pres_a,
Packet),
presence_broadcast(From, StateData#state.pres_i,
Packet),
S1 = StateData#state{pres_last = undefined,
pres_a = ?SETS:new(),
pres_i = ?SETS:new(),
pres_invis = true},
presence_broadcast_first(From, S1, Packet);
true ->
StateData
end,
NewState;
"error" ->
StateData;
"subscribe" ->
StateData;
"subscribed" ->
StateData;
"unsubscribe" ->
StateData;
"unsubscribed" ->
StateData;
_ ->
update_priority(xml:get_subtag(Packet, "priority"), StateData),
FromUnavail = (StateData#state.pres_last == undefined) or
StateData#state.pres_invis,
?DEBUG("from unavail = ~p~n", [FromUnavail]),
NewState =
if
FromUnavail ->
% TODO: watching ourself
mod_offline:resend_offline_messages(
StateData#state.user),
presence_broadcast_first(
From, StateData#state{pres_last = Packet,
pres_invis = false
}, Packet);
true ->
presence_broadcast_to_trusted(From,
StateData#state.pres_f,
StateData#state.pres_a,
Packet),
StateData#state{pres_last = Packet,
pres_invis = false
}
end,
NewState
end.
presence_track(From, To, Packet, StateData) ->
{xmlelement, Name, Attrs, Els} = Packet,
LTo = jlib:jid_tolower(To),
User = StateData#state.user,
case xml:get_attr_s("type", Attrs) of
"unavailable" ->
ejabberd_router:route(From, To, Packet),
I = remove_element(LTo, StateData#state.pres_i),
A = remove_element(LTo, StateData#state.pres_a),
StateData#state{pres_i = I,
pres_a = A};
"invisible" ->
ejabberd_router:route(From, To, Packet),
I = ?SETS:add_element(LTo, StateData#state.pres_i),
A = remove_element(LTo, StateData#state.pres_a),
StateData#state{pres_i = I,
pres_a = A};
"subscribe" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
mod_roster:out_subscription(User, To, subscribe),
StateData;
"subscribed" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
mod_roster:out_subscription(User, To, subscribed),
StateData;
"unsubscribe" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
mod_roster:out_subscription(User, To, unsubscribe),
StateData;
"unsubscribed" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
mod_roster:out_subscription(User, To, unsubscribed),
StateData;
"error" ->
ejabberd_router:route(From, To, Packet),
StateData;
_ ->
ejabberd_router:route(From, To, Packet),
I = remove_element(LTo, StateData#state.pres_i),
A = ?SETS:add_element(LTo, StateData#state.pres_a),
StateData#state{pres_i = I,
pres_a = A}
end.
presence_broadcast(From, JIDSet, Packet) ->
lists:foreach(fun(JID) ->
ejabberd_router:route(From, JID, Packet)
end, ?SETS:to_list(JIDSet)).
presence_broadcast_to_trusted(From, T, A, Packet) ->
lists:foreach(fun(JID) ->
case ?SETS:is_element(JID, T) of
true ->
ejabberd_router:route(From, JID, Packet);
_ ->
ok
end
end, ?SETS:to_list(A)).
presence_broadcast_first(From, StateData, Packet) ->
{U, S, _} = From,
?SETS:fold(fun(JID, X) ->
ejabberd_router:route({U, S, ""}, JID,
{xmlelement, "presence",
[{"type", "probe"}],
[]}),
X
end,
[],
StateData#state.pres_t),
if
StateData#state.pres_invis ->
StateData;
true ->
As = ?SETS:fold(fun(JID, A) ->
ejabberd_router:route(From, JID, Packet),
?SETS:add_element(JID, A)
end,
StateData#state.pres_a,
StateData#state.pres_f),
StateData#state{pres_a = As}
end.
remove_element(E, Set) ->
case ?SETS:is_element(E, Set) of
true ->
?SETS:del_element(E, Set);
_ ->
Set
end.
roster_change(IJID, ISubscription, StateData) ->
LIJID = jlib:jid_tolower(IJID),
IsFrom = (ISubscription == both) or (ISubscription == from),
IsTo = (ISubscription == both) or (ISubscription == to),
FSet = if
IsFrom ->
?SETS:add_element(LIJID, StateData#state.pres_f);
true ->
remove_element(LIJID, StateData#state.pres_f)
end,
TSet = if
IsTo ->
?SETS:add_element(LIJID, StateData#state.pres_t);
true ->
remove_element(LIJID, StateData#state.pres_t)
end,
case StateData#state.pres_last of
unknown ->
StateData#state{pres_f = FSet, pres_t = TSet};
P ->
?DEBUG("roster changed for ~p~n", [StateData#state.user]),
From = {StateData#state.user,
StateData#state.server,
StateData#state.resource},
Cond1 = (not StateData#state.pres_invis) and IsFrom,
Cond2 = (not IsFrom)
and (?SETS:is_element(LIJID, StateData#state.pres_a) or
?SETS:is_element(LIJID, StateData#state.pres_i)),
if
Cond1 ->
?DEBUG("C1: ~p~n", [LIJID]),
ejabberd_router:route(From, IJID, P),
A = ?SETS:add_element(LIJID,
StateData#state.pres_a),
StateData#state{pres_a = A,
pres_f = FSet,
pres_t = TSet};
Cond2 ->
?DEBUG("C2: ~p~n", [LIJID]),
ejabberd_router:route(From, IJID,
{xmlelement, "presence",
[{"type", "unavailable"}], []}),
I = remove_element(LIJID,
StateData#state.pres_i),
A = remove_element(LIJID,
StateData#state.pres_a),
StateData#state{pres_i = I,
pres_a = A,
pres_f = FSet,
pres_t = TSet};
true ->
StateData#state{pres_f = FSet, pres_t = TSet}
end
end.
update_priority(El, StateData) ->
Pri = case El of
false ->
0;
_ ->
case catch list_to_integer(xml:get_tag_cdata(El)) of
P when is_integer(P) ->
P;
_ ->
0
end
end,
ejabberd_sm:set_presence(StateData#state.user,
StateData#state.resource,
Pri).