25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-10-31 15:21:38 +01:00
xmpp.chapril.org-ejabberd/src/ejabberd_c2s.erl

2234 lines
69 KiB
Erlang

%%%----------------------------------------------------------------------
%%% File : ejabberd_c2s.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Serve C2S connection
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_c2s).
-author('alexey@process-one.net').
-update_info({update, 0}).
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
%% External exports
-export([start/2,
stop/1,
start_link/3,
send_text/2,
send_element/2,
socket_type/0,
get_presence/1,
get_subscribed/1]).
%% API:
-export([add_rosteritem/3, del_rosteritem/2]).
%% gen_fsm callbacks
-export([init/1,
wait_for_stream/2,
wait_for_auth/2,
wait_for_feature_request/2,
wait_for_bind/2,
wait_for_session/2,
wait_for_sasl_response/2,
session_established/2,
handle_event/3,
handle_sync_event/4,
code_change/4,
handle_info/3,
terminate/3,
print_state/1,
migrate/3
]).
-export([get_state/1]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("mod_privacy.hrl").
-define(SETS, gb_sets).
-define(DICT, dict).
%% pres_a contains all the presence available send (either through roster mechanism or directed).
%% Directed presence unavailable remove user from pres_a.
-record(state, {socket,
sockmod,
socket_monitor,
xml_socket,
streamid,
sasl_state,
access,
shaper,
zlib = false,
tls = false,
tls_required = false,
tls_enabled = false,
tls_options = [],
authenticated = false,
jid,
user = undefined, server = list_to_binary(?MYNAME), resource = undefined,
sid,
pres_t = ?SETS:new(),
pres_f = ?SETS:new(),
pres_a = ?SETS:new(),
pres_i = ?SETS:new(),
pres_last, pres_pri,
pres_timestamp,
privacy_list = #userlist{},
conn = unknown,
auth_module = unknown,
ip,
fsm_limit_opts,
lang}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, [{spawn_opt,[{fullsweep_after,10}]}]).
-endif.
%% Module start with or without supervisor:
-ifdef(NO_TRANSIENT_SUPERVISORS).
-define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s,
[SockData, Opts, FSMLimitOpts],
FSMLimitOpts ++ ?FSMOPTS)).
-else.
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_c2s_sup,
[SockData, Opts, FSMLimitOpts])).
-endif.
%% This is the timeout to apply between event when starting a new
%% session:
-define(C2S_OPEN_TIMEOUT, 60000).
-define(C2S_HIBERNATE_TIMEOUT, 90000).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_CLIENT).
-define(PREFIXED_NS, [{?NS_XMPP, ?NS_XMPP_pfx}]).
-define(STREAM_HEADER,
"<?xml version='1.0'?>"
"<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"id='~s' from='~s'~s~s>"
).
-define(INVALID_NS_ERR, exmpp_stream:error('invalid-namespace')).
-define(INVALID_XML_ERR, exmpp_stream:error('xml-not-well-formed')).
-define(HOST_UNKNOWN_ERR, exmpp_stream:error('host-unknown')).
-define(SERRT_CONFLICT, exmpp_stream:error('conflict')).
-define(POLICY_VIOLATION_ERR(Lang, Text),
exmpp_stream:error('policy-violation', {Lang, Text})).
-define(INVALID_FROM, exmpp_stream:error('invalid-from')).
-define(STANZA_ERROR(NS, Condition),
% exmpp_xml:xmlel_to_xmlelement(exmpp_stanza:error(NS, Condition),
% [?NS_JABBER_CLIENT], [{?NS_XMPP, "stream"}])).
exmpp_stanza:error(NS, Condition)).
-define(ERR_FEATURE_NOT_IMPLEMENTED(NS),
?STANZA_ERROR(NS, 'feature-not-implemented')).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(StateName, #state{fsm_limit_opts = Opts} = State) ->
start(StateName, State, Opts);
start(SockData, Opts) ->
start(SockData, Opts, fsm_limit_opts(Opts)).
start(SockData, Opts, FSMLimitOpts) ->
?SUPERVISOR_START.
start_link(SockData, Opts, FSMLimitOpts) ->
?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts, FSMLimitOpts],
FSMLimitOpts ++ ?FSMOPTS).
socket_type() ->
xml_stream.
%% Return Username, Resource and presence information
get_presence(FsmRef) ->
?GEN_FSM:sync_send_all_state_event(FsmRef, {get_presence}, 1000).
%%TODO: for debug only
get_state(FsmRef) ->
?GEN_FSM:sync_send_all_state_event(FsmRef, get_state, 1000).
add_rosteritem(FsmRef, IJID, ISubscription) ->
?GEN_FSM:send_all_state_event(FsmRef, {add_rosteritem, IJID, ISubscription}).
del_rosteritem(FsmRef, IJID) ->
?GEN_FSM:send_all_state_event(FsmRef, {del_rosteritem, IJID}).
stop(FsmRef) ->
?GEN_FSM:send_event(FsmRef, closed).
migrate(FsmRef, Node, After) ->
?GEN_FSM:send_all_state_event(FsmRef, {migrate, Node, After}).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts, FSMLimitOpts]) ->
Access = case lists:keysearch(access, 1, Opts) of
{value, {_, A}} -> A;
_ -> all
end,
Shaper = case lists:keysearch(shaper, 1, Opts) of
{value, {_, S}} -> S;
_ -> none
end,
XMLSocket =
case lists:keysearch(xml_socket, 1, Opts) of
{value, {_, XS}} -> XS;
_ -> false
end,
Zlib = lists:member(zlib, Opts),
StartTLS = lists:member(starttls, Opts),
StartTLSRequired = lists:member(starttls_required, Opts),
TLSEnabled = lists:member(tls, Opts),
TLS = StartTLS orelse StartTLSRequired orelse TLSEnabled,
TLSOpts1 =
lists:filter(fun({certfile, _}) -> true;
(_) -> false
end, Opts),
TLSOpts = [verify_none | TLSOpts1],
IP = case lists:keysearch(frontend_ip, 1, Opts) of
{value, {_, IP1}} ->
IP1;
_ ->
peerip(SockMod, Socket)
end,
%% Check if IP is blacklisted:
case is_ip_blacklisted(IP) of
true ->
?INFO_MSG("Connection attempt from blacklisted IP: ~s",
[jlib:ip_to_list(IP)]),
{stop, normal};
false ->
Socket1 =
if
TLSEnabled ->
SockMod:starttls(Socket, TLSOpts);
true ->
Socket
end,
SocketMonitor = SockMod:monitor(Socket1),
{ok, wait_for_stream, #state{socket = Socket1,
sockmod = SockMod,
socket_monitor = SocketMonitor,
xml_socket = XMLSocket,
zlib = Zlib,
tls = TLS,
tls_required = StartTLSRequired,
tls_enabled = TLSEnabled,
tls_options = TLSOpts,
streamid = new_id(),
access = Access,
shaper = Shaper,
ip = IP,
fsm_limit_opts = FSMLimitOpts},
?C2S_OPEN_TIMEOUT}
end;
init([StateName, StateData, _FSMLimitOpts]) ->
MRef = (StateData#state.sockmod):monitor(StateData#state.socket),
if StateName == session_established ->
Conn = get_conn_type(StateData),
Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, StateData#state.auth_module}],
{Time, _} = StateData#state.sid,
SID = {Time, self()},
Priority = case StateData#state.pres_last of
undefined ->
undefined;
El ->
exmpp_presence:get_priority(El)
end,
ejabberd_sm:open_session(SID, StateData#state.jid, Priority, Info),
NewStateData = StateData#state{sid = SID, socket_monitor = MRef},
{ok, StateName, NewStateData};
true ->
{ok, StateName, StateData#state{socket_monitor = MRef}}
end.
%% Return list of all available resources of contacts,
get_subscribed(FsmRef) ->
?GEN_FSM:sync_send_all_state_event(FsmRef, get_subscribed, 1000).
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) ->
DefaultLang = case ?MYLANG of
undefined ->
"en";
DL ->
DL
end,
Header = exmpp_stream:opening_reply(Opening,
StateData#state.streamid, DefaultLang),
case NS of
?NS_XMPP ->
ServerB = exmpp_stringprep:nameprep(
exmpp_stream:get_receiving_entity(Opening)),
Server = binary_to_list(ServerB),
case ?IS_MY_HOST(Server) of
true ->
Lang = exmpp_stream:get_lang(Opening),
change_shaper(StateData,
exmpp_jid:make(ServerB)),
case exmpp_stream:get_version(Opening) of
{1, 0} ->
send_header(StateData, Server, "1.0", DefaultLang),
case StateData#state.authenticated of
false ->
Realm =
case ejabberd_config:get_local_option({sasl_realm, Server}) of
undefined ->
"";
Realm0 ->
Realm0
end,
SASLState =
cyrsasl:server_new(
"jabber", Server, Realm, [],
fun(U) ->
ejabberd_auth:get_password_with_authmodule(
U, Server)
end,
fun(U, P) ->
ejabberd_auth:check_password_with_authmodule(
U, Server, P)
end,
fun(U, P, D, DG) ->
ejabberd_auth:check_password_with_authmodule(
U, Server, P, D, DG)
end,
StateData#state.socket),
MechsPrepared = [exmpp_server_sasl:feature(
cyrsasl:listmech(Server))],
SockMod =
(StateData#state.sockmod):get_sockmod(
StateData#state.socket),
TLSRequired = StateData#state.tls_required,
Mechs =
case TLSRequired of
true ->
case (SockMod == gen_tcp) of
true ->
[];
false ->
MechsPrepared
end;
false ->
MechsPrepared
end,
SockMod =
(StateData#state.sockmod):get_sockmod(
StateData#state.socket),
Zlib = StateData#state.zlib,
CompressFeature =
case Zlib andalso
((SockMod == gen_tcp) orelse
(SockMod == tls)) of
true ->
[exmpp_server_compression:feature(["zlib"])];
_ ->
[]
end,
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
TLSRequired = StateData#state.tls_required,
TLSFeature =
case (TLS == true) andalso
(TLSEnabled == false) andalso
(SockMod == gen_tcp) of
true ->
[exmpp_server_tls:feature(TLSRequired)];
false ->
[]
end,
Other_Feats = ejabberd_hooks:run_fold(
c2s_stream_features,
ServerB,
[], [ServerB]),
send_element(StateData,
exmpp_stream:features(
TLSFeature ++
CompressFeature ++
Mechs ++
Other_Feats)),
fsm_next_state(wait_for_feature_request,
StateData#state{
server = ServerB,
sasl_state = SASLState,
lang = Lang});
_ ->
case StateData#state.resource of
undefined ->
RosterVersioningFeature =
ejabberd_hooks:run_fold(
roster_get_versioning_feature,
ServerB,
[], [ServerB]),
Other_Feats = ejabberd_hooks:run_fold(
c2s_stream_features,
ServerB,
[], [ServerB]),
send_element(
StateData,
exmpp_stream:features(
[exmpp_server_binding:feature(),
exmpp_server_session:feature()]
++ RosterVersioningFeature
++ Other_Feats)),
fsm_next_state(wait_for_bind,
StateData#state{
server = ServerB,
lang = Lang});
_ ->
send_element(
StateData,
exmpp_stream:features([])),
fsm_next_state(wait_for_session,
StateData#state{
server = ServerB,
lang = Lang})
end
end;
_ ->
send_header(StateData, Server, "", DefaultLang),
if
(not StateData#state.tls_enabled) and
StateData#state.tls_required ->
send_element(StateData,
exmpp_xml:append_child(Header,
exmpp_stream:error('policy-violation',
{"en", "Use of STARTTLS required"}))),
send_trailer(StateData),
{stop, normal, StateData};
true ->
fsm_next_state(wait_for_auth,
StateData#state{
server = ServerB,
lang = Lang})
end
end;
_ ->
send_header(StateData, ?MYNAME, "", DefaultLang),
send_element(StateData, ?HOST_UNKNOWN_ERR),
send_trailer(StateData),
{stop, normal, StateData}
end;
_ ->
send_header(StateData, ?MYNAME, "", DefaultLang),
send_element(StateData, ?INVALID_NS_ERR),
send_trailer(StateData),
{stop, normal, StateData}
end;
wait_for_stream(timeout, StateData) ->
{stop, normal, StateData};
wait_for_stream({xmlstreamelement, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream({xmlstreamend, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_header(StateData, ?MYNAME, "1.0", ""),
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
{stop, normal, StateData}.
wait_for_auth({xmlstreamelement, El}, StateData) ->
ServerString = binary_to_list(StateData#state.server),
case is_auth_packet(El) of
{auth, _ID, get, {_U, _, _, _}} ->
Fields = case ejabberd_auth:plain_password_required(
ServerString) of
false -> both;
true -> plain
end,
send_element(StateData,
exmpp_server_legacy_auth:fields(El, Fields)),
fsm_next_state(wait_for_auth, StateData);
{auth, _ID, set, {_U, _P, _D, undefined}} ->
Err = exmpp_stanza:error(El#xmlel.ns, 'not-acceptable',
{"en", "No resource provided"}),
send_element(StateData, exmpp_iq:error(El, Err)),
fsm_next_state(wait_for_auth, StateData);
{auth, _ID, set, {U, P, D, R}} ->
try
JID = exmpp_jid:make(U, StateData#state.server, R),
UBinary = exmpp_jid:prep_node(JID),
case acl:match_rule(ServerString,
StateData#state.access, JID) of
allow ->
DGen = fun(PW) ->
sha:sha(StateData#state.streamid ++ PW) end,
case ejabberd_auth:check_password_with_authmodule(
U, ServerString, P,
D, DGen) of
{true, AuthModule} ->
?INFO_MSG(
"(~w) Accepted legacy authentication for ~s by ~s",
[StateData#state.socket,
exmpp_jid:to_binary(JID), AuthModule]),
SID = {now(), self()},
Conn = get_conn_type(StateData),
%% Info = [{ip, StateData#state.ip}, {conn, Conn},
%% {auth_module, AuthModule}],
Res = exmpp_server_legacy_auth:success(El),
send_element(StateData, Res),
%% ejabberd_sm:open_session(
%% SID, exmpp_jid:make(U, StateData#state.server, R), Info),
change_shaper(StateData, JID),
{Fs, Ts} = ejabberd_hooks:run_fold(
roster_get_subscription_lists,
StateData#state.server,
{[], []},
[UBinary, StateData#state.server]),
LJID = jlib:short_prepd_bare_jid(JID),
Fs1 = [LJID | Fs],
Ts1 = [LJID | Ts],
PrivList = ejabberd_hooks:run_fold(
privacy_get_user_list, StateData#state.server,
#userlist{},
[UBinary, StateData#state.server]),
maybe_migrate(session_established,
StateData#state{
sasl_state = 'undefined',
%not used anymore, let the GC work.
user = list_to_binary(U),
resource = list_to_binary(R),
jid = JID,
sid = SID,
conn = Conn,
auth_module = AuthModule,
pres_f = ?SETS:from_list(Fs1),
pres_t = ?SETS:from_list(Ts1),
privacy_list = PrivList});
_ ->
?INFO_MSG(
"(~w) Failed legacy authentication for ~s",
[StateData#state.socket,
exmpp_jid:to_binary(JID)]),
Res = exmpp_iq:error_without_original(El,
'not-authorized'),
send_element(StateData, Res),
fsm_next_state(wait_for_auth, StateData)
end;
_ ->
?INFO_MSG(
"(~w) Forbidden legacy authentication for ~s",
[StateData#state.socket,
exmpp_jid:to_binary(JID)]),
Res = exmpp_iq:error_without_original(El,
'not-allowed'),
send_element(StateData, Res),
fsm_next_state(wait_for_auth, StateData)
end
catch
throw:_Exception ->
?INFO_MSG(
"(~w) Forbidden legacy authentication for "
"username '~s' with resource '~s'",
[StateData#state.socket, U, R]),
Res1 = exmpp_iq:error_without_original(El, 'jid-malformed'),
send_element(StateData, Res1),
fsm_next_state(wait_for_auth, StateData)
end;
_ ->
process_unauthenticated_stanza(StateData, El),
fsm_next_state(wait_for_auth, StateData)
end;
wait_for_auth(timeout, StateData) ->
{stop, normal, StateData};
wait_for_auth({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData),
{stop, normal, StateData};
wait_for_auth({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_auth(closed, StateData) ->
{stop, normal, StateData}.
wait_for_feature_request({xmlstreamelement, #xmlel{ns = NS, name = Name} = El},
StateData) ->
Zlib = StateData#state.zlib,
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
TLSRequired = StateData#state.tls_required,
SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket),
case {NS, Name} of
{?NS_SASL, 'auth'} when not ((SockMod == gen_tcp) and TLSRequired) ->
{auth, Mech, ClientIn} = exmpp_server_sasl:next_step(El),
case cyrsasl:server_start(StateData#state.sasl_state,
Mech,
ClientIn) of
{ok, Props} ->
catch (StateData#state.sockmod):reset_stream(
StateData#state.socket),
send_element(StateData, exmpp_server_sasl:success()),
U = proplists:get_value(username, Props),
AuthModule = proplists:get_value(auth_module, Props),
?INFO_MSG("(~w) Accepted authentication for ~s by ~s",
[StateData#state.socket, U, AuthModule]),
fsm_next_state(wait_for_stream,
StateData#state{
streamid = new_id(),
authenticated = true,
auth_module = AuthModule,
user = list_to_binary(U) });
{continue, ServerOut, NewSASLState} ->
send_element(StateData,
exmpp_server_sasl:challenge(ServerOut)),
fsm_next_state(wait_for_sasl_response,
StateData#state{
sasl_state = NewSASLState});
{error, Error, Username} ->
?INFO_MSG(
"(~w) Failed authentication for ~s@~s",
[StateData#state.socket,
Username, StateData#state.server]),
send_element(StateData,
exmpp_server_sasl:failure(Error)),
{next_state, wait_for_feature_request, StateData,
?C2S_OPEN_TIMEOUT};
{error, Error} ->
send_element(StateData,
exmpp_server_sasl:failure(Error)),
fsm_next_state(wait_for_feature_request, StateData)
end;
{?NS_TLS, 'starttls'} when TLS == true,
TLSEnabled == false,
SockMod == gen_tcp ->
ServerString = binary_to_list(StateData#state.server),
TLSOpts = case ejabberd_config:get_local_option(
{domain_certfile, ServerString}) of
undefined ->
StateData#state.tls_options;
CertFile ->
[{certfile, CertFile} |
lists:keydelete(
certfile, 1, StateData#state.tls_options)]
end,
Socket = StateData#state.socket,
Proceed = exmpp_xml:node_to_list(
exmpp_server_tls:proceed(), [?DEFAULT_NS], ?PREFIXED_NS),
TLSSocket = (StateData#state.sockmod):starttls(
Socket, TLSOpts,
Proceed),
fsm_next_state(wait_for_stream,
StateData#state{socket = TLSSocket,
streamid = new_id(),
tls_enabled = true
});
{?NS_COMPRESS, 'compress'} when Zlib == true,
((SockMod == gen_tcp) or
(SockMod == tls)) ->
case exmpp_server_compression:selected_method(El) of
undefined ->
send_element(StateData,
exmpp_server_compression:failure('setup-failed')),
fsm_next_state(wait_for_feature_request, StateData);
<<"zlib">> ->
Socket = StateData#state.socket,
ZlibSocket = (StateData#state.sockmod):compress(
Socket,
exmpp_server_compression:compressed()),
fsm_next_state(wait_for_stream,
StateData#state{socket = ZlibSocket,
streamid = new_id()
});
_ ->
send_element(StateData,
exmpp_server_compression:failure('unsupported-method')),
fsm_next_state(wait_for_feature_request,
StateData)
end;
_ ->
if
(SockMod == gen_tcp) and TLSRequired ->
Lang = StateData#state.lang,
send_element(StateData, exmpp_stream:error(
'policy-violation', {Lang, "Use of STARTTLS required"})),
send_trailer(StateData),
{stop, normal, StateData};
true ->
process_unauthenticated_stanza(StateData, El),
fsm_next_state(wait_for_feature_request, StateData)
end
end;
wait_for_feature_request(timeout, StateData) ->
{stop, normal, StateData};
wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
send_element(StateData, exmpp_stream:closing()),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_feature_request({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_feature_request(closed, StateData) ->
{stop, normal, StateData}.
wait_for_sasl_response({xmlstreamelement, #xmlel{ns = NS, name = Name} = El},
StateData) ->
case {NS, Name} of
{?NS_SASL, 'response'} ->
{response, ClientIn} = exmpp_server_sasl:next_step(El),
case cyrsasl:server_step(StateData#state.sasl_state,
ClientIn) of
{ok, Props} ->
catch (StateData#state.sockmod):reset_stream(
StateData#state.socket),
send_element(StateData, exmpp_server_sasl:success()),
U = proplists:get_value(username, Props),
AuthModule = proplists:get_value(auth_module, Props),
?INFO_MSG("(~w) Accepted authentication for ~s by ~s",
[StateData#state.socket, U, AuthModule]),
fsm_next_state(wait_for_stream,
StateData#state{
streamid = new_id(),
authenticated = true,
auth_module = AuthModule,
user = list_to_binary(U)});
{continue, ServerOut, NewSASLState} ->
send_element(StateData,
exmpp_server_sasl:challenge(ServerOut)),
fsm_next_state(wait_for_sasl_response,
StateData#state{sasl_state = NewSASLState});
{error, Error, Username} ->
?INFO_MSG(
"(~w) Failed authentication for ~s@~s",
[StateData#state.socket,
Username, StateData#state.server]),
send_element(StateData,
exmpp_server_sasl:failure(Error)),
fsm_next_state(wait_for_feature_request, StateData);
{error, Error} ->
send_element(StateData,
exmpp_server_sasl:failure(Error)),
fsm_next_state(wait_for_feature_request, StateData)
end;
_ ->
process_unauthenticated_stanza(StateData, El),
fsm_next_state(wait_for_feature_request, StateData)
end;
wait_for_sasl_response(timeout, StateData) ->
{stop, normal, StateData};
wait_for_sasl_response({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData),
{stop, normal, StateData};
wait_for_sasl_response({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_sasl_response(closed, StateData) ->
{stop, normal, StateData}.
wait_for_bind({xmlstreamelement, El}, StateData) ->
try
R = case exmpp_server_binding:wished_resource(El) of
undefined ->
lists:concat([randoms:get_string() | tuple_to_list(now())]);
Resource ->
Resource
end,
%%ServerB = StateData#state.server,
%%RosterVersioningFeature =
%% ejabberd_hooks:run_fold(roster_get_versioning_feature,
%% ServerB, [], [ServerB]),
%%send_element(StateData,
%% exmpp_stream:features([exmpp_server_session:feature()
%% | RosterVersioningFeature])),
JID = exmpp_jid:make(StateData#state.user, StateData#state.server, R),
Res = exmpp_server_binding:bind(El, JID),
send_element(StateData, Res),
fsm_next_state(wait_for_session,
StateData#state{resource = exmpp_jid:resource(JID), jid = JID})
catch
throw:{stringprep, resourceprep, _, _} ->
Err = exmpp_server_binding:error(El, 'bad-request'),
send_element(StateData, Err),
fsm_next_state(wait_for_bind, StateData);
throw:_Exception ->
fsm_next_state(wait_for_bind, StateData)
end;
wait_for_bind(timeout, StateData) ->
{stop, normal, StateData};
wait_for_bind({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData),
{stop, normal, StateData};
wait_for_bind({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_bind(closed, StateData) ->
{stop, normal, StateData}.
wait_for_session({xmlstreamelement, El}, StateData) ->
try
ServerString = binary_to_list(StateData#state.server),
JID = StateData#state.jid,
true = exmpp_server_session:want_establishment(El),
case acl:match_rule(ServerString,
StateData#state.access, JID) of
allow ->
?INFO_MSG("(~w) Opened session for ~s",
[StateData#state.socket,
exmpp_jid:to_binary(JID)]),
%%send_element(StateData, exmpp_stream:features([])),
Res = exmpp_server_session:establish(El),
send_element(StateData, Res),
change_shaper(StateData, JID),
{Fs, Ts} = ejabberd_hooks:run_fold(
roster_get_subscription_lists,
StateData#state.server,
{[], []},
[StateData#state.user, StateData#state.server]),
LJID = jlib:short_prepd_bare_jid(JID),
Fs1 = [LJID | Fs],
Ts1 = [LJID | Ts],
PrivList =
ejabberd_hooks:run_fold(
privacy_get_user_list, StateData#state.server,
#userlist{},
[StateData#state.user, StateData#state.server]),
SID = {now(), self()},
Conn = get_conn_type(StateData),
%% Info = [{ip, StateData#state.ip}, {conn, Conn},
%% {auth_module, StateData#state.auth_module}],
%% ejabberd_sm:open_session(
%% SID, JID, Info),
maybe_migrate(session_established,
StateData#state{
sasl_state = 'undefined',
%%not used anymore, let the GC work.
sid = SID,
conn = Conn,
pres_f = ?SETS:from_list(Fs1),
pres_t = ?SETS:from_list(Ts1),
privacy_list = PrivList});
_ ->
ejabberd_hooks:run(forbidden_session_hook,
StateData#state.server, [JID]),
?INFO_MSG("(~w) Forbidden session for ~s",
[StateData#state.socket,
exmpp_jid:to_binary(JID)]),
Err = exmpp_server_session:error(El, 'not-allowed'),
send_element(StateData, Err),
fsm_next_state(wait_for_session, StateData)
end
catch
_Exception ->
fsm_next_state(wait_for_session, StateData)
end;
wait_for_session(timeout, StateData) ->
{stop, normal, StateData};
wait_for_session({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData),
{stop, normal, StateData};
wait_for_session({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_session(closed, StateData) ->
{stop, normal, StateData}.
session_established({xmlstreamelement, El}, StateData) ->
%% Check 'from' attribute in stanza RFC 3920 Section 9.1.2
case check_from(El, StateData#state.jid) of
'invalid-from' ->
send_element(StateData, ?INVALID_FROM),
send_trailer(StateData),
{stop, normal, StateData};
_ ->
session_established2(El, StateData)
end;
%% We hibernate the process to reduce memory consumption after a
%% configurable activity timeout
session_established(timeout, StateData) ->
%% TODO: Options must be stored in state:
Options = [],
proc_lib:hibernate(?GEN_FSM, enter_loop,
[?MODULE, Options, session_established, StateData]),
fsm_next_state(session_established, StateData);
session_established({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData),
{stop, normal, StateData};
session_established({xmlstreamerror, "XML stanza is too big" = E}, StateData) ->
send_element(StateData, ?POLICY_VIOLATION_ERR(StateData#state.lang, E)),
send_trailer(StateData),
{stop, normal, StateData};
session_established({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
session_established(closed, StateData) ->
{stop, normal, StateData}.
%% Process packets sent by user (coming from user on c2s XMPP
%% connection)
session_established2(El, StateData) ->
try
User = StateData#state.user,
Server = StateData#state.server,
FromJID = StateData#state.jid,
To = exmpp_stanza:get_recipient(El),
ToJID = case To of
undefined ->
exmpp_jid:bare(StateData#state.jid);
_ ->
exmpp_jid:parse(To)
end,
NewEl = case exmpp_stanza:get_lang(El) of
undefined ->
case StateData#state.lang of
undefined -> El;
Lang ->
exmpp_stanza:set_lang(El, Lang)
end;
_ ->
El
end,
NewState = case El of
#xmlel{ns = ?NS_JABBER_CLIENT, name = 'presence'} ->
PresenceEl = ejabberd_hooks:run_fold(
c2s_update_presence,
Server,
NewEl,
[User, Server]),
ejabberd_hooks:run(
user_send_packet,
Server,
[FromJID, ToJID, PresenceEl]),
case {exmpp_jid:node(ToJID),
exmpp_jid:domain(ToJID),
exmpp_jid:resource(ToJID)} of
{User, Server,undefined} ->
?DEBUG("presence_update(~p,~n\t~p,~n\t~p)",
[FromJID, PresenceEl, StateData]),
presence_update(FromJID, PresenceEl,
StateData);
_ ->
presence_track(FromJID, ToJID, PresenceEl,
StateData)
end;
#xmlel{ns = ?NS_JABBER_CLIENT, name = 'iq'} ->
case exmpp_iq:xmlel_to_iq(El) of
#iq{kind = request, ns = ?NS_PRIVACY} = IQ_Rec ->
process_privacy_iq(
FromJID, ToJID, IQ_Rec, StateData);
_ ->
ejabberd_hooks:run(
user_send_packet,
Server,
[FromJID, ToJID, NewEl]),
check_privacy_route(FromJID, StateData, FromJID,
ToJID, NewEl),
StateData
end;
#xmlel{ns = ?NS_JABBER_CLIENT, name = 'message'} ->
ejabberd_hooks:run(user_send_packet,
Server,
[FromJID, ToJID, NewEl]),
ejabberd_router:route(FromJID, ToJID, NewEl),
StateData;
_ ->
StateData
end,
ejabberd_hooks:run(c2s_loop_debug, [{xmlstreamelement, El}]),
fsm_next_state(session_established, NewState)
catch
throw:{stringprep, _, _, _} ->
case exmpp_stanza:get_type(El) of
<<"error">> ->
ok;
<<"result">> ->
ok;
_ ->
Err = exmpp_stanza:reply_with_error(El, 'jid-malformed'),
send_element(StateData, Err)
end,
ejabberd_hooks:run(c2s_loop_debug, [{xmlstreamelement, El}]),
fsm_next_state(session_established, StateData);
throw:Exception ->
io:format("SESSION ESTABLISHED: Exception=~p~n", [Exception]),
fsm_next_state(session_established, StateData)
end.
%%----------------------------------------------------------------------
%% 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({migrate, Node, After}, StateName, StateData) when Node /= node() ->
fsm_migrate(StateName, StateData, Node, After * 2);
handle_event({add_rosteritem, IJID, ISubscription}, StateName, StateData) ->
NewStateData = roster_change(IJID, ISubscription, StateData),
fsm_next_state(StateName, NewStateData);
handle_event({del_rosteritem, IJID}, StateName, StateData) ->
NewStateData = roster_change(IJID, none, StateData),
fsm_next_state(StateName, NewStateData);
handle_event(_Event, StateName, StateData) ->
fsm_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}
%%----------------------------------------------------------------------
%TODO: for debug only
handle_sync_event(get_state,_From,StateName,StateData) ->
{reply,{StateName, StateData}, StateName, StateData};
handle_sync_event({get_presence}, _From, StateName, StateData) ->
User = binary_to_list(StateData#state.user),
PresLast = StateData#state.pres_last,
Show = case PresLast of
undefined -> "unavailable";
_ -> exmpp_presence:get_show(PresLast)
end,
Status = case PresLast of
undefined -> "";
_ -> exmpp_presence:get_status(PresLast)
end,
Resource = binary_to_list(StateData#state.resource),
Reply = {User, Resource, atom_to_list(Show), Status},
fsm_reply(Reply, StateName, StateData);
handle_sync_event(get_subscribed, _From, StateName, StateData) ->
Subscribed = ?SETS:to_list(StateData#state.pres_f),
{reply, Subscribed, StateName, StateData};
handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok,
fsm_reply(Reply, StateName, StateData).
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_info/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info({send_text, Text}, StateName, StateData) ->
% XXX OLD FORMAT: This clause should be removed.
send_text(StateData, Text),
ejabberd_hooks:run(c2s_loop_debug, [Text]),
fsm_next_state(StateName, StateData);
handle_info(replaced, _StateName, StateData) ->
_Lang = StateData#state.lang,
send_element(StateData,
?SERRT_CONFLICT), %% (Lang, "Replaced by new connection")),
send_trailer(StateData),
{stop, normal, StateData#state{authenticated = replaced}};
%% Process Packets that are to be send to the user
handle_info({route, From, To, Packet}, StateName, StateData) ->
{Pass, NewAttrs, NewState} =
case Packet of
#xmlel{attrs = Attrs} when ?IS_PRESENCE(Packet) ->
case exmpp_presence:get_type(Packet) of
'probe' ->
LFrom = jlib:short_prepd_jid(From),
LBFrom = jlib:short_prepd_bare_jid(From),
NewStateData =
case ?SETS:is_element(
LFrom, StateData#state.pres_a) orelse
?SETS:is_element(
LBFrom, StateData#state.pres_a) of
true ->
StateData;
false ->
case ?SETS:is_element(
LFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LFrom,
StateData#state.pres_a),
StateData#state{pres_a = A};
false ->
case ?SETS:is_element(
LBFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LBFrom,
StateData#state.pres_a),
StateData#state{pres_a = A};
false ->
StateData
end
end
end,
process_presence_probe(From, To, NewStateData),
{false, Attrs, NewStateData};
'error' ->
LFrom = jlib:short_prepd_jid(From),
NewA = remove_element(LFrom,
StateData#state.pres_a),
{true, Attrs, StateData#state{pres_a = NewA}};
'subscribe' ->
SRes = is_privacy_allow(From, To, Packet, StateData#state.privacy_list),
{SRes, Attrs, StateData};
'subscribed' ->
SRes = is_privacy_allow(From, To, Packet, StateData#state.privacy_list),
{SRes, Attrs, StateData};
'unsubscribe' ->
SRes = is_privacy_allow(From, To, Packet, StateData#state.privacy_list),
{SRes, Attrs, StateData};
'unsubscribed' ->
SRes = is_privacy_allow(From, To, Packet, StateData#state.privacy_list),
{SRes, Attrs, StateData};
_ ->
case ejabberd_hooks:run_fold(
privacy_check_packet, StateData#state.server,
allow,
[StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, Packet},
in]) of
allow ->
LFrom = jlib:short_prepd_jid(From),
LBFrom = jlib:short_prepd_bare_jid(From),
case ?SETS:is_element(
LFrom, StateData#state.pres_a) orelse
?SETS:is_element(
LBFrom, StateData#state.pres_a) of
true ->
{true, Attrs, StateData};
false ->
case ?SETS:is_element(
LFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LFrom,
StateData#state.pres_a),
{true, Attrs,
StateData#state{pres_a = A}};
false ->
case ?SETS:is_element(
LBFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LBFrom,
StateData#state.pres_a),
{true, Attrs,
StateData#state{pres_a = A}};
false ->
{true, Attrs, StateData}
end
end
end;
deny ->
{false, Attrs, StateData}
end
end;
#xmlel{name = broadcast, attrs = Attrs} ->
?DEBUG("broadcast~n~p~n", [Packet#xmlel.children]),
case Packet#xmlel.ns of
roster_item ->
IJID = exmpp_jid:make(exmpp_xml:get_attribute(Packet, u, <<"">>),
exmpp_xml:get_attribute(Packet, s, <<"">>),
exmpp_xml:get_attribute(Packet, r, <<"">>)),
ISubscription = exmpp_xml:get_attribute(Packet, subs, <<"none">>),
{false, Attrs,
roster_change(IJID, ISubscription, StateData)};
exit ->
Reason = exmpp_xml:get_attribute_as_list(Packet, reason, "Unknown reason"),
{exit, Attrs, Reason};
privacy_list ->
PrivListName = exmpp_xml:get_attribute_as_list(Packet, list_name, "Unknown list name"),
CDataString = exmpp_xml:get_cdata_as_list(Packet),
{ok, A2, _} = erl_scan:string(CDataString),
{_, W} = erl_parse:parse_exprs(A2),
{value, PrivList, []} = erl_eval:exprs(W, []),
case ejabberd_hooks:run_fold(
privacy_updated_list, StateData#state.server,
false,
[StateData#state.privacy_list,
PrivList]) of
false ->
{false, Attrs, StateData};
NewPL ->
PrivPushEl = exmpp_server_privacy:list_push(
StateData#state.jid, PrivListName),
send_element(StateData, PrivPushEl),
{false, Attrs, StateData#state{privacy_list = NewPL}}
end;
_ ->
{false, Attrs, StateData}
end;
#xmlel{attrs = Attrs} when ?IS_IQ(Packet) ->
case exmpp_iq:is_request(Packet) of
true ->
ToNode = exmpp_jid:node(To),
ToResource = exmpp_jid:resource(To),
case exmpp_iq:get_request(Packet) of
#xmlel{ns = ?NS_VCARD} when (ToNode == <<"">>) or (ToResource == <<"">>) ->
Host = StateData#state.server,
case ets:lookup(sm_iqtable, {?NS_VCARD, Host}) of
[{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, exmpp_iq:xmlel_to_iq(Packet));
[] ->
Res = exmpp_iq:error(Packet, 'feature-not-implemented'),
ejabberd_router:route(To, From, Res)
end,
{false, Attrs, StateData};
_ ->
case ejabberd_hooks:run_fold(
privacy_check_packet, StateData#state.server,
allow,
[StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, Packet},
in]) of
allow ->
{true, Attrs, StateData};
deny ->
Res = exmpp_iq:error(Packet, 'feature-not-implemented'),
ejabberd_router:route(To, From, Res),
{false, Attrs, StateData}
end
end;
false ->
{true, Attrs, StateData}
end;
#xmlel{attrs = Attrs} when ?IS_MESSAGE(Packet) ->
case ejabberd_hooks:run_fold(
privacy_check_packet, StateData#state.server,
allow,
[StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, Packet},
in]) of
allow ->
case ejabberd_hooks:run_fold(
feature_check_packet, StateData#state.server,
allow,
[StateData#state.jid,
StateData#state.server,
StateData#state.pres_last,
{From, To, Packet},
in]) of
allow ->
{true, Attrs, StateData};
deny ->
{false, Attrs, StateData}
end;
deny ->
{false, Attrs, StateData}
end;
#xmlel{attrs = Attrs} ->
{true, Attrs, StateData}
end,
if
Pass == exit ->
%% When Pass==exit, NewState contains a string instead of a #state{}
_Lang = StateData#state.lang,
send_element(StateData, ?SERRT_CONFLICT), %% (Lang, NewState)),
send_trailer(StateData),
{stop, normal, StateData};
Pass ->
Attrs2 = exmpp_stanza:set_sender_in_attrs(NewAttrs, From),
Attrs3 = exmpp_stanza:set_recipient_in_attrs(Attrs2, To),
FixedPacket = Packet#xmlel{attrs = Attrs3},
send_element(StateData, FixedPacket),
ejabberd_hooks:run(user_receive_packet,
StateData#state.server,
[StateData#state.jid, From, To, FixedPacket]),
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
fsm_next_state(StateName, NewState);
true ->
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
fsm_next_state(StateName, NewState)
end;
handle_info({'DOWN', Monitor, _Type, _Object, _Info}, _StateName, StateData)
when Monitor == StateData#state.socket_monitor ->
{stop, normal, StateData};
handle_info(system_shutdown, StateName, StateData) ->
case StateName of
wait_for_stream ->
send_header(StateData#state.streamid, ?MYNAME, "1.0", ""),
send_element(StateData, exmpp_stream:error('system-shutdown')),
send_trailer(StateData),
ok;
_ ->
send_element(StateData, exmpp_stream:error('system-shutdown')),
send_trailer(StateData),
ok
end,
{stop, normal, StateData};
handle_info({force_update_presence, LUser}, StateName,
#state{user = LUser, server = LServer} = StateData) ->
NewStateData =
case exmpp_presence:is_presence(StateData#state.pres_last) of
true ->
PresenceEl = ejabberd_hooks:run_fold(
c2s_update_presence,
LServer,
StateData#state.pres_last,
[LUser, LServer]),
StateData2 = StateData#state{pres_last = PresenceEl},
presence_update(StateData2#state.jid,
PresenceEl,
StateData2),
StateData2;
false ->
StateData
end,
{next_state, StateName, NewStateData};
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
fsm_next_state(StateName, StateData).
%%----------------------------------------------------------------------
%% Func: print_state/1
%% Purpose: Prepare the state to be printed on error log
%% Returns: State to print
%%----------------------------------------------------------------------
print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) ->
State#state{pres_t = {pres_t, ?SETS:size(T)},
pres_f = {pres_f, ?SETS:size(F)},
pres_a = {pres_a, ?SETS:size(A)},
pres_i = {pres_i, ?SETS:size(I)}
}.
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
terminate({migrated, ClonePid}, StateName, StateData) ->
if StateName == session_established ->
?INFO_MSG("(~w) Migrating ~s to ~p on node ~p",
[StateData#state.socket,
exmpp_jid:to_binary(StateData#state.jid),
ClonePid, node(ClonePid)]),
ejabberd_sm:close_session(StateData#state.sid,
StateData#state.jid);
true ->
ok
end,
(StateData#state.sockmod):change_controller(
StateData#state.socket, ClonePid),
ok;
terminate(_Reason, StateName, StateData) ->
%%TODO: resource could be 'undefined' if terminate before bind?
case StateName of
session_established ->
case StateData#state.authenticated of
replaced ->
?INFO_MSG("(~w) Replaced session for ~s",
[StateData#state.socket,
exmpp_jid:to_binary(StateData#state.jid)]),
From = StateData#state.jid,
Packet = exmpp_presence:unavailable(),
Packet1 = exmpp_presence:set_status(Packet,
"Replaced by new connection"),
ejabberd_sm:close_session_unset_presence(
StateData#state.sid,
StateData#state.jid,
"Replaced by new connection"),
presence_broadcast(
StateData, From, StateData#state.pres_a, Packet1),
presence_broadcast(
StateData, From, StateData#state.pres_i, Packet1);
_ ->
?INFO_MSG("(~w) Close session for ~s",
[StateData#state.socket,
exmpp_jid:to_binary(StateData#state.jid)]),
EmptySet = ?SETS:new(),
case StateData of
#state{pres_last = undefined,
pres_a = EmptySet,
pres_i = EmptySet} ->
ejabberd_sm:close_session(StateData#state.sid,
StateData#state.jid);
_ ->
From = StateData#state.jid,
Packet = exmpp_presence:unavailable(),
ejabberd_sm:close_session_unset_presence(
StateData#state.sid,
StateData#state.jid,
""),
presence_broadcast(
StateData, From, StateData#state.pres_a, Packet),
presence_broadcast(
StateData, From, StateData#state.pres_i, Packet)
end
end,
bounce_messages();
_ ->
ok
end,
(StateData#state.sockmod):close(StateData#state.socket),
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
change_shaper(StateData, JID) ->
Shaper = acl:match_rule(binary_to_list(StateData#state.server),
StateData#state.shaper, JID),
(StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper).
send_text(StateData, Text) ->
?DEBUG("Send XML on stream = ~s", [Text]),
(StateData#state.sockmod):send(StateData#state.socket, Text).
send_element(StateData, #xmlel{ns = ?NS_XMPP, name = 'stream'} = El) ->
send_text(StateData, exmpp_stream:to_iolist(El));
send_element(StateData, El) when StateData#state.xml_socket ->
(StateData#state.sockmod):send_xml(StateData#state.socket,
{xmlstreamelement, El});
send_element(StateData, El) ->
send_text(StateData, exmpp_stanza:to_iolist(El)).
send_header(StateData, Server, Version, Lang)
when StateData#state.xml_socket ->
VersionAttr =
case Version of
"" -> [];
_ -> [{"version", Version}]
end,
LangAttr =
case Lang of
"" -> [];
_ -> [{"xml:lang", Lang}]
end,
Header =
{xmlstreamstart,
"stream:stream",
VersionAttr ++
LangAttr ++
[{"xmlns", "jabber:client"},
{"xmlns:stream", "http://etherx.jabber.org/streams"},
{"id", StateData#state.streamid},
{"from", Server}]},
(StateData#state.sockmod):send_xml(
StateData#state.socket, Header);
send_header(StateData, Server, Version, Lang) ->
VersionStr =
case Version of
"" -> "";
_ -> [" version='", Version, "'"]
end,
LangStr =
case Lang of
"" -> "";
_ -> [" xml:lang='", Lang, "'"]
end,
Header = io_lib:format(?STREAM_HEADER,
[StateData#state.streamid,
Server,
VersionStr,
LangStr]),
send_text(StateData, Header).
send_trailer(StateData) when StateData#state.xml_socket ->
(StateData#state.sockmod):send_xml(
StateData#state.socket,
{xmlstreamend, "stream:stream"});
send_trailer(StateData) ->
send_element(StateData, exmpp_stream:closing()).
new_id() ->
randoms:get_string().
is_auth_packet(El) when ?IS_IQ(El) ->
case exmpp_iq:xmlel_to_iq(El) of
#iq{ns = ?NS_LEGACY_AUTH, kind = 'request'} = IQ_Rec ->
Children = exmpp_xml:get_child_elements(IQ_Rec#iq.payload),
{auth, IQ_Rec#iq.id, IQ_Rec#iq.type,
get_auth_tags(Children , undefined, undefined,
undefined, undefined)};
_ ->
false
end;
is_auth_packet(_El) ->
false.
get_auth_tags([#xmlel{ns = ?NS_LEGACY_AUTH, name = Name, children = Els} | L],
U, P, D, R) ->
CData = exmpp_xml:get_cdata_from_list_as_list(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}.
%% Copied from ejabberd_socket.erl
-record(socket_state, {sockmod, socket, receiver}).
get_conn_type(StateData) ->
case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
gen_tcp -> c2s;
tls -> c2s_tls;
ejabberd_zlib ->
if is_pid(StateData#state.socket) ->
unknown;
true ->
case ejabberd_zlib:get_sockmod(
(StateData#state.socket)#socket_state.socket) of
gen_tcp -> c2s_compressed;
tls -> c2s_compressed_tls
end
end;
ejabberd_http_poll -> http_poll;
ejabberd_http_bind -> http_bind;
_ -> unknown
end.
process_presence_probe(From, To, StateData) ->
LFrom = jlib:short_prepd_jid(From),
LBFrom = jlib:short_prepd_bare_jid(From),
case StateData#state.pres_last of
undefined ->
ok;
_ ->
Cond1 = (?SETS:is_element(LFrom, StateData#state.pres_f)
orelse
((LFrom /= LBFrom) andalso
?SETS:is_element(LBFrom, StateData#state.pres_f)))
andalso (not
(?SETS:is_element(LFrom, StateData#state.pres_i)
orelse
((LFrom /= LBFrom) andalso
?SETS:is_element(LBFrom, StateData#state.pres_i)))),
if
Cond1 ->
Timestamp = StateData#state.pres_timestamp,
Packet = exmpp_xml:append_children(
StateData#state.pres_last,
%% To is the one sending the presence (the target of the probe)
[jlib:timestamp_to_xml(Timestamp, utc, To, ""),
%% TODO: Delete the next line once XEP-0091 is Obsolete
jlib:timestamp_to_xml(Timestamp)]),
case ejabberd_hooks:run_fold(
privacy_check_packet, StateData#state.server,
allow,
[StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{To, From, Packet},
out]) of
deny ->
ok;
allow ->
Pid=element(2, StateData#state.sid),
ejabberd_hooks:run(presence_probe_hook, StateData#state.server, [From, To, Pid]),
%% Don't route a presence probe to oneself
case From == To of
false ->
ejabberd_router:route(To, From, Packet);
true ->
ok
end
end;
true ->
ok
end
end.
%% User updates his presence (non-directed presence packet)
presence_update(From, Packet, StateData) ->
case exmpp_presence:get_type(Packet) of
'unavailable' ->
Status = case exmpp_presence:get_status(Packet) of
undefined -> <<>>;
S -> S
end,
Info = [{ip, StateData#state.ip}, {conn, StateData#state.conn},
{auth_module, StateData#state.auth_module}],
ejabberd_sm:unset_presence(StateData#state.sid,
StateData#state.jid,
Status,
Info),
presence_broadcast(StateData, From, StateData#state.pres_a, Packet),
presence_broadcast(StateData, From, StateData#state.pres_i, Packet),
StateData#state{pres_last = undefined,
pres_timestamp = undefined,
pres_a = ?SETS:new(),
pres_i = ?SETS:new()};
'error' ->
StateData;
'probe' ->
StateData;
'subscribe' ->
StateData;
'subscribed' ->
StateData;
'unsubscribe' ->
StateData;
'unsubscribed' ->
StateData;
_ ->
OldPriority = case StateData#state.pres_last of
undefined ->
0;
OldPresence ->
try
exmpp_presence:get_priority(OldPresence)
catch
_Exception -> 0
end
end,
NewPriority = try
exmpp_presence:get_priority(Packet)
catch
_Exception1 -> 0
end,
Timestamp = calendar:now_to_universal_time(now()),
update_priority(NewPriority, Packet, StateData),
FromUnavail = (StateData#state.pres_last == undefined),
?DEBUG("from unavail = ~p~n", [FromUnavail]),
NewState =
if
FromUnavail ->
ejabberd_hooks:run(user_available_hook,
StateData#state.server,
[StateData#state.jid]),
if NewPriority >= 0 ->
resend_offline_messages(StateData),
resend_subscription_requests(StateData);
true ->
ok
end,
presence_broadcast_first(
From, StateData#state{pres_last = Packet,
pres_timestamp = Timestamp
}, Packet);
true ->
presence_broadcast_to_trusted(StateData,
From,
StateData#state.pres_f,
StateData#state.pres_a,
Packet),
if OldPriority < 0, NewPriority >= 0 ->
resend_offline_messages(StateData);
true ->
ok
end,
StateData#state{pres_last = Packet,
pres_timestamp = Timestamp
}
end,
NewState
end.
%% User sends a directed presence packet
presence_track(From, To, Packet, StateData) ->
LTo = jlib:short_prepd_jid(To),
case exmpp_presence:get_type(Packet) of
'unavailable' ->
check_privacy_route(From, StateData, 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};
'subscribe' ->
try_check_privacy_route(subscribe, StateData#state.user, StateData#state.server,
From, StateData, exmpp_jid:bare(From), To, Packet),
StateData;
'unsubscribe' ->
try_check_privacy_route(subscribe, StateData#state.user, StateData#state.server,
From, StateData, exmpp_jid:bare(From), To, Packet),
StateData;
'subscribed' ->
ejabberd_hooks:run(roster_out_subscription,
StateData#state.server,
[StateData#state.user, StateData#state.server, To, subscribed]),
check_privacy_route(From, StateData, exmpp_jid:bare(From),
To, Packet),
StateData;
'unsubscribed' ->
ejabberd_hooks:run(roster_out_subscription,
StateData#state.server,
[StateData#state.user, StateData#state.server, To, unsubscribed]),
check_privacy_route(From, StateData, exmpp_jid:bare(From),
To, Packet),
StateData;
'error' ->
check_privacy_route(From, StateData, From, To, Packet),
StateData;
'probe' ->
check_privacy_route(From, StateData, From, To, Packet),
StateData;
_ ->
check_privacy_route(From, StateData, 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.
%%% Check ACL before allowing to send a subscription stanza
try_check_privacy_route(Type, User, Server, From, StateData, FromRoute, To, Packet) ->
JID1 = exmpp_jid:make(User, Server, undefined),
Access = gen_mod:get_module_opt(Server, mod_roster, access, all),
case acl:match_rule(Server, Access, JID1) of
deny ->
%% Silently drop this (un)subscription request
ok;
allow ->
ejabberd_hooks:run(roster_out_subscription,
Server,
[User, Server, To, Type]),
check_privacy_route(From, StateData, FromRoute,
To, Packet)
end.
check_privacy_route(From, StateData, FromRoute, To, Packet) ->
case ejabberd_hooks:run_fold(
privacy_check_packet, StateData#state.server,
allow,
[StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, Packet},
out]) of
deny ->
ok;
allow ->
ejabberd_router:route(FromRoute, To, Packet)
end.
%% Check if privacy rules allow this delivery
is_privacy_allow(From, To, Packet, PrivacyList) ->
User = exmpp_jid:prep_node(To),
Server = exmpp_jid:prep_domain(To),
allow == ejabberd_hooks:run_fold(
privacy_check_packet, Server,
allow,
[User,
Server,
PrivacyList,
{From, To, Packet},
in]).
%% Send presence when disconnecting
presence_broadcast(StateData, From, JIDSet, Packet) ->
JIDs = ?SETS:to_list(JIDSet),
JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs),
Server = StateData#state.server,
send_multiple(From, Server, JIDs2, Packet).
%% Send presence when updating presence
presence_broadcast_to_trusted(StateData, From, Trusted, JIDSet, Packet) ->
JIDs = ?SETS:to_list(JIDSet),
JIDs_trusted = [JID || JID <- JIDs, ?SETS:is_element(JID, Trusted)],
JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs_trusted),
Server = StateData#state.server,
send_multiple(From, Server, JIDs2, Packet).
%% Send presence when connecting
presence_broadcast_first(From, StateData, Packet) ->
JIDsProbe =
?SETS:fold(
fun(JID, L) -> [JID | L] end,
[],
StateData#state.pres_t),
PacketProbe = exmpp_presence:probe(),
JIDs2Probe = format_and_check_privacy(From, StateData, Packet, JIDsProbe),
Server = StateData#state.server,
send_multiple(From, Server, JIDs2Probe, PacketProbe),
{As, JIDs} =
?SETS:fold(
fun(JID, {A, JID_list}) ->
{?SETS:add_element(JID, A), JID_list++[JID]}
end,
{StateData#state.pres_a, []},
StateData#state.pres_f),
JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs),
Server = StateData#state.server,
send_multiple(From, Server, JIDs2, Packet),
StateData#state{pres_a = As}.
format_and_check_privacy(From, StateData, Packet, JIDs) ->
FJIDs = [exmpp_jid:make(JID) || JID <- JIDs],
lists:filter(
fun(FJID) ->
case ejabberd_hooks:run_fold(
privacy_check_packet, StateData#state.server,
allow,
[StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, FJID, Packet},
out]) of
deny -> false;
allow -> true
end
end,
FJIDs).
send_multiple(From, Server, JIDs, Packet) ->
ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
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:short_prepd_jid(IJID),
IsFrom = (ISubscription == both) or (ISubscription == from),
IsTo = (ISubscription == both) or (ISubscription == to),
OldIsFrom = ?SETS:is_element(LIJID, StateData#state.pres_f),
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
undefined ->
StateData#state{pres_f = FSet, pres_t = TSet};
P ->
?DEBUG("roster changed for ~p~n", [StateData#state.user]),
From = StateData#state.jid,
To = IJID,
Cond1 = IsFrom and (not OldIsFrom),
Cond2 = (not IsFrom) and OldIsFrom
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]),
case ejabberd_hooks:run_fold(
privacy_check_packet, StateData#state.server,
allow,
[StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, P},
out]) of
deny ->
ok;
allow ->
ejabberd_router:route(From, To, P)
end,
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]),
PU = exmpp_presence:unavailable(),
case ejabberd_hooks:run_fold(
privacy_check_packet, StateData#state.server,
allow,
[StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, PU},
out]) of
deny ->
ok;
allow ->
ejabberd_router:route(From, To, PU)
end,
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(Priority, Packet, StateData) ->
Info = [{ip, StateData#state.ip}, {conn, StateData#state.conn},
{auth_module, StateData#state.auth_module}],
ejabberd_sm:set_presence(StateData#state.sid,
StateData#state.jid,
Priority,
Packet,
Info).
process_privacy_iq(From, To,
#iq{type = Type, iq_ns = IQ_NS} = IQ_Rec,
StateData) ->
{Res, NewStateData} =
case Type of
get ->
R = ejabberd_hooks:run_fold(
privacy_iq_get, StateData#state.server ,
{error, ?ERR_FEATURE_NOT_IMPLEMENTED(IQ_NS)},
[From, To, IQ_Rec, StateData#state.privacy_list]),
{R, StateData};
set ->
case ejabberd_hooks:run_fold(
privacy_iq_set, StateData#state.server,
{error, ?ERR_FEATURE_NOT_IMPLEMENTED(IQ_NS)},
[From, To, IQ_Rec]) of
{result, R, NewPrivList} ->
{{result, R},
StateData#state{privacy_list = NewPrivList}};
R -> {R, StateData}
end
end,
IQRes =
case Res of
{result, []} ->
exmpp_iq:result(IQ_Rec);
{result, Result} ->
exmpp_iq:result(IQ_Rec, Result);
{error, Error} ->
exmpp_iq:error(IQ_Rec, Error)
end,
ejabberd_router:route(
To, From, exmpp_iq:iq_to_xmlel(IQRes)),
NewStateData.
resend_offline_messages(#state{user = UserB,
server = ServerB,
privacy_list = PrivList} = StateData) ->
case ejabberd_hooks:run_fold(resend_offline_messages_hook,
StateData#state.server,
[],
[UserB, ServerB]) of
Rs when is_list(Rs) ->
lists:foreach(
fun({route,
From, To, Packet}) ->
Pass = case ejabberd_hooks:run_fold(
privacy_check_packet, StateData#state.server,
allow,
[StateData#state.user,
StateData#state.server,
PrivList,
{From, To, Packet},
in]) of
allow ->
true;
deny ->
false
end,
if
Pass ->
Attrs1 = exmpp_stanza:set_sender_in_attrs(
Packet#xmlel.attrs, From),
Attrs2 = exmpp_stanza:set_recipient_in_attrs(
Attrs1, To),
FixedPacket = Packet#xmlel{attrs = Attrs2},
send_element(StateData, FixedPacket),
ejabberd_hooks:run(user_receive_packet,
StateData#state.server,
[StateData#state.jid,
From, To, FixedPacket]);
true ->
ok
end
end, Rs)
end.
resend_subscription_requests(#state{user = UserB,
server = ServerB} = StateData) ->
PendingSubscriptions = ejabberd_hooks:run_fold(
resend_subscription_requests_hook,
StateData#state.server,
[],
[UserB, ServerB]),
lists:foreach(fun(XMLPacket) ->
send_element(StateData,
XMLPacket)
end,
PendingSubscriptions).
process_unauthenticated_stanza(StateData, El) when ?IS_IQ(El) ->
case exmpp_iq:get_kind(El) of
request ->
IQ_Rec = exmpp_iq:xmlel_to_iq(El),
Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq,
StateData#state.server,
empty,
[StateData#state.server, IQ_Rec,
StateData#state.ip]),
case Res of
empty ->
% The only reasonable IQ's here are auth and register IQ's
% They contain secrets, so don't include subelements to response
ResIQ = exmpp_iq:error_without_original(El,
'service-unavailable'),
Res1 = exmpp_stanza:set_sender(ResIQ,
exmpp_jid:make(StateData#state.server)),
Res2 = exmpp_stanza:remove_recipient(Res1),
send_element(StateData, Res2);
_ ->
send_element(StateData, Res)
end;
_ ->
% Drop any stanza, which isn't an IQ request stanza
ok
end;
process_unauthenticated_stanza(_StateData,_El) ->
ok.
peerip(SockMod, Socket) ->
IP = case SockMod of
gen_tcp -> inet:peername(Socket);
_ -> SockMod:peername(Socket)
end,
case IP of
{ok, IPOK} -> IPOK;
_ -> undefined
end.
maybe_migrate(StateName, StateData) ->
case ejabberd_cluster:get_node({StateData#state.user,
StateData#state.server}) of
Node when Node == node() ->
Conn = get_conn_type(StateData),
Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, StateData#state.auth_module}],
#state{user = U, server = S, jid = JID, sid = SID} = StateData,
ejabberd_sm:open_session(SID, JID, Info),
case ejabberd_cluster:get_node_new({U, S}) of
Node ->
ok;
NewNode ->
After = ejabberd_cluster:rehash_timeout(),
migrate(self(), NewNode, After)
end,
fsm_next_state(StateName, StateData);
Node ->
fsm_migrate(StateName, StateData, Node, 0)
end.
%% fsm_next_state: Generate the next_state FSM tuple with different
%% timeout, depending on the future state
fsm_next_state(session_established, StateData) ->
{next_state, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT};
fsm_next_state(StateName, StateData) ->
{next_state, StateName, StateData, ?C2S_OPEN_TIMEOUT}.
fsm_migrate(StateName, StateData, Node, Timeout) ->
{migrate, StateData,
{Node, ?MODULE, start, [StateName, StateData]}, Timeout}.
%% fsm_reply: Generate the reply FSM tuple with different timeout,
%% depending on the future state
fsm_reply(Reply, session_established, StateData) ->
{reply, Reply, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT};
fsm_reply(Reply, StateName, StateData) ->
{reply, Reply, StateName, StateData, ?C2S_OPEN_TIMEOUT}.
%% Used by c2s blacklist plugins
is_ip_blacklisted(undefined) ->
false;
is_ip_blacklisted({IP,_Port}) ->
ejabberd_hooks:run_fold(check_bl_c2s, false, [IP]).
%% Check from attributes
%% returns invalid-from|NewElement
check_from(El, FromJID) ->
case exmpp_stanza:get_sender(El) of
undefined ->
El;
SJID when is_binary(SJID) ->
try
JIDEl = exmpp_jid:parse(SJID),
case exmpp_jid:prep_resource(JIDEl) of
undefined ->
%% Matching JID: The stanza is ok
case exmpp_jid:bare_compare(JIDEl, FromJID) of
true ->
El;
false ->
'invalid-from'
end;
_ ->
%% Matching JID: The stanza is ok
case exmpp_jid:compare(JIDEl, FromJID) of
true ->
El;
false ->
'invalid-from'
end
end
catch
_:_ ->
'invalid-from'
end
end.
fsm_limit_opts(Opts) ->
case lists:keysearch(max_fsm_queue, 1, Opts) of
{value, {_, N}} when is_integer(N) ->
[{max_queue, N}];
_ ->
case ejabberd_config:get_local_option(max_fsm_queue) of
N when is_integer(N) ->
[{max_queue, N}];
_ ->
[]
end
end.
bounce_messages() ->
receive
{route, From, To, El} ->
ejabberd_router:route(From, To, El),
bounce_messages()
after 0 ->
ok
end.