25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-30 16:36:29 +01:00

Convert to exmpp.

SVN Revision: 1383
This commit is contained in:
Jean-Sébastien Pédron 2008-06-26 15:48:19 +00:00
parent 1a311a30b5
commit 22e79490ff
3 changed files with 319 additions and 351 deletions

View File

@ -13,6 +13,8 @@
(send_element): For stanzas under the NS_JABBER_SERVER namespace, lie
to exmpp_xml by telling it that this namespace is the default one.
* src/ejabberd_s2s_in.erl, src/ejabberd_s2s_out.erl: Convert to exmpp.
2008-06-25 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
* src/ejabberd_c2s.erl: Finish ejabberd_c2s conversion with the

View File

@ -46,8 +46,9 @@
handle_info/3,
terminate/3]).
-include("exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-ifdef(SSL39).
-include_lib("ssl/include/ssl_pkix.hrl").
-define(PKIXEXPLICIT, 'OTP-PKIX').
@ -92,28 +93,10 @@
[SockData, Opts])).
-endif.
-define(STREAM_HEADER(Version),
("<?xml version='1.0'?>"
"<stream:stream "
"xmlns:stream='http://etherx.jabber.org/streams' "
"xmlns='jabber:server' "
"xmlns:db='jabber:server:dialback' "
"id='" ++ StateData#state.streamid ++ "'" ++ Version ++ ">")
).
-define(STREAM_TRAILER, "</stream:stream>").
-define(INVALID_NAMESPACE_ERR,
xml:element_to_string(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
xml:element_to_string(?SERR_HOST_UNKNOWN)).
-define(INVALID_FROM_ERR,
xml:element_to_string(?SERR_INVALID_FROM)).
-define(INVALID_XML_ERR,
xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, [?NS_JABBER_SERVER]).
-define(PREFIXED_NS, [{?NS_XMPP, "stream"}, {?NS_JABBER_DIALBACK, "db"}]).
%%%----------------------------------------------------------------------
%%% API
@ -174,13 +157,16 @@ init([{SockMod, Socket}, Opts]) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case {xml:get_attr_s("xmlns", Attrs),
xml:get_attr_s("xmlns:db", Attrs),
xml:get_attr_s("version", Attrs) == "1.0"} of
{"jabber:server", _, true} when
wait_for_stream({xmlstreamstart, Opening}, StateData) ->
case {exmpp_stream:get_default_ns(Opening),
exmpp_xml:is_ns_declared_here(Opening, ?NS_JABBER_DIALBACK),
exmpp_stream:get_version(Opening) == {1, 0}} of
{?NS_JABBER_SERVER, _, true} when
StateData#state.tls and (not StateData#state.authenticated) ->
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
Opening_Reply = exmpp_stream:opening_reply(Opening,
StateData#state.streamid),
send_element(StateData,
exmpp_stream:set_dialback_support(Opening_Reply)),
SASL =
if
StateData#state.tls_enabled ->
@ -190,10 +176,8 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case (StateData#state.sockmod):get_verify_result(
StateData#state.socket) of
0 ->
[{xmlelement, "mechanisms",
[{"xmlns", ?NS_SASL}],
[{xmlelement, "mechanism", [],
[{xmlcdata, "EXTERNAL"}]}]}];
[exmpp_server_sasl:feature(
["EXTERNAL"])];
_ ->
[]
end;
@ -207,30 +191,35 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
StateData#state.tls_enabled ->
[];
true ->
[{xmlelement, "starttls",
[{"xmlns", ?NS_TLS}], []}]
[exmpp_server_tls:feature()]
end,
send_element(StateData,
{xmlelement, "stream:features", [],
SASL ++ StartTLS}),
send_element(StateData, exmpp_stream:features(SASL ++ StartTLS)),
{next_state, wait_for_feature_request, StateData};
{"jabber:server", _, true} when
{?NS_JABBER_SERVER, _, true} when
StateData#state.authenticated ->
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
Opening_Reply = exmpp_stream:opening_reply(Opening,
StateData#state.streamid),
send_element(StateData,
{xmlelement, "stream:features", [], []}),
exmpp_stream:set_dialback_support(Opening_Reply)),
send_element(StateData, exmpp_stream:features([])),
{next_state, stream_established, StateData};
{"jabber:server", "jabber:server:dialback", _} ->
send_text(StateData, ?STREAM_HEADER("")),
{?NS_JABBER_SERVER, true, _} ->
Opening_Reply = exmpp_stream:opening_reply(Opening,
StateData#state.streamid),
send_element(StateData,
exmpp_stream:set_dialback_support(Opening_Reply)),
{next_state, stream_established, StateData};
_ ->
send_text(StateData, ?INVALID_NAMESPACE_ERR),
send_element(StateData, exmpp_stream:error('invalid-namespace')),
{stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?STREAM_HEADER("") ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
Opening_Reply = exmpp_stream:opening_reply(undefined, ?NS_JABBER_SERVER,
"", StateData#state.streamid),
send_element(StateData, Opening_Reply),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
wait_for_stream(timeout, StateData) ->
@ -241,31 +230,29 @@ wait_for_stream(closed, StateData) ->
wait_for_feature_request({xmlstreamelement, El}, StateData) ->
{xmlelement, Name, Attrs, Els} = El,
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket),
case {xml:get_attr_s("xmlns", Attrs), Name} of
{?NS_TLS, "starttls"} when TLS == true,
case El of
#xmlel{ns = ?NS_TLS, name = 'starttls'} when TLS == true,
TLSEnabled == false,
SockMod == gen_tcp ->
?DEBUG("starttls", []),
Socket = StateData#state.socket,
Proceed = exmpp_xml:document_fragment_to_list(
exmpp_server_tls:proceed(), ?DEFAULT_NS, ?PREFIXED_NS),
TLSOpts = StateData#state.tls_options,
TLSSocket = (StateData#state.sockmod):starttls(
Socket, TLSOpts,
xml:element_to_string(
{xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})),
Proceed),
{next_state, wait_for_stream,
StateData#state{socket = TLSSocket,
streamid = new_id(),
tls_enabled = true
}};
{?NS_SASL, "auth"} when TLSEnabled ->
Mech = xml:get_attr_s("mechanism", Attrs),
case Mech of
"EXTERNAL" ->
Auth = jlib:decode_base64(xml:get_cdata(Els)),
#xmlel{ns = ?NS_SASL, name = 'auth'} when TLSEnabled ->
case exmpp_server_sasl:next_step(El) of
{auth, "EXTERNAL", Auth} ->
AuthDomain = jlib:nameprep(Auth),
AuthRes =
case (StateData#state.sockmod):get_peer_certificate(
@ -300,8 +287,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
(StateData#state.sockmod):reset_stream(
StateData#state.socket),
send_element(StateData,
{xmlelement, "success",
[{"xmlns", ?NS_SASL}], []}),
exmpp_server_sasl:success()),
?DEBUG("(~w) Accepted s2s authentication for ~s",
[StateData#state.socket, AuthDomain]),
{next_state, wait_for_stream,
@ -311,16 +297,14 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
}};
true ->
send_element(StateData,
{xmlelement, "failure",
[{"xmlns", ?NS_SASL}], []}),
send_text(StateData, ?STREAM_TRAILER),
exmpp_server_sasl:failure()),
send_element(StateData,
exmpp_stream:closing()),
{stop, normal, StateData}
end;
_ ->
send_element(StateData,
{xmlelement, "failure",
[{"xmlns", ?NS_SASL}],
[{xmlelement, "invalid-mechanism", [], []}]}),
exmpp_server_sasl:failure('invalid-mechanism')),
{stop, normal, StateData}
end;
_ ->
@ -328,11 +312,12 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
end;
wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
send_text(StateData, ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
wait_for_feature_request({xmlstreamerror, _}, StateData) ->
send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
wait_for_feature_request(closed, StateData) ->
@ -345,8 +330,8 @@ stream_established({xmlstreamelement, El}, StateData) ->
case is_key_packet(El) of
{key, To, From, Id, Key} ->
?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]),
LTo = jlib:nameprep(To),
LFrom = jlib:nameprep(From),
LTo = exmpp_stringprep:nameprep(To),
LFrom = exmpp_stringprep:nameprep(From),
%% Checks if the from domain is allowed and if the to
%% domain is handled by this server:
case {ejabberd_s2s:allow_host(To, From),
@ -358,49 +343,56 @@ stream_established({xmlstreamelement, El}, StateData) ->
Key, StateData#state.streamid}),
Conns = ?DICT:store({LFrom, LTo}, wait_for_verification,
StateData#state.connections),
change_shaper(StateData, LTo, jlib:make_jid("", LFrom, "")),
change_shaper(StateData, LTo,
exmpp_jid:make_bare_jid(undefined, LFrom)),
{next_state,
stream_established,
StateData#state{connections = Conns,
timer = Timer}};
{_, false} ->
send_text(StateData, ?HOST_UNKNOWN_ERR),
send_element(StateData, exmpp_stream:error('host-unknown')),
{stop, normal, StateData};
{false, _} ->
send_text(StateData, ?INVALID_FROM_ERR),
send_element(StateData, exmpp_stream:error('invalid-from')),
{stop, normal, StateData}
end;
{verify, To, From, Id, Key} ->
?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
LTo = jlib:nameprep(To),
LFrom = jlib:nameprep(From),
Type = case ejabberd_s2s:has_key({LTo, LFrom}, Key) of
true -> "valid";
_ -> "invalid"
end,
%Type = if Key == Key1 -> "valid";
% true -> "invalid"
% end,
send_element(StateData,
{xmlelement,
"db:verify",
[{"from", To},
{"to", From},
{"id", Id},
{"type", Type}],
[]}),
LTo = exmpp_stringprep:nameprep(To),
LFrom = exmpp_stringprep:nameprep(From),
send_element(StateData, exmpp_dialback:verify_response(
El, ejabberd_s2s:has_key({LTo, LFrom}, Key))),
{next_state, stream_established, StateData#state{timer = Timer}};
_ ->
NewEl = jlib:remove_attr("xmlns", El),
{xmlelement, Name, Attrs, _Els} = NewEl,
From_s = xml:get_attr_s("from", Attrs),
From = jlib:string_to_jid(From_s),
To_s = xml:get_attr_s("to", Attrs),
To = jlib:string_to_jid(To_s),
From = case exmpp_stanza:get_sender(El) of
undefined ->
error;
F ->
try
exmpp_jid:string_to_jid(F)
catch
_Exception1 -> error
end
end,
To = case exmpp_stanza:get_recipient(El) of
undefined ->
error;
T ->
try
exmpp_jid:string_to_jid(T)
catch
_Exception2 -> error
end
end,
% XXX OLD FORMAT: El.
% XXX No namespace conversion (:server <-> :client) is done.
% This is handled by C2S and S2S send_element functions.
ElOld = exmpp_xml:xmlel_to_xmlelement(El,
?DEFAULT_NS, ?PREFIXED_NS),
if
(To /= error) and (From /= error) ->
LFrom = From#jid.lserver,
LTo = To#jid.lserver,
LFrom = From#jid.ldomain,
LTo = To#jid.ldomain,
if
StateData#state.authenticated ->
case (LFrom == StateData#state.auth_domain)
@ -409,15 +401,19 @@ stream_established({xmlstreamelement, El}, StateData) ->
LTo,
ejabberd_router:dirty_get_all_domains()) of
true ->
if ((Name == "iq") or
(Name == "message") or
(Name == "presence")) ->
Name = El#xmlel.name,
if ((Name == 'iq') or
(Name == 'message') or
(Name == 'presence')) ->
% XXX OLD FORMAT: From, To.
FromOld = exmpp_jid:to_ejabberd_jid(From),
ToOld = exmpp_jid:to_ejabberd_jid(To),
ejabberd_hooks:run(
s2s_receive_packet,
LFrom,
[From, To, NewEl]),
[FromOld, ToOld, ElOld]),
ejabberd_router:route(
From, To, NewEl);
FromOld, ToOld, ElOld);
true ->
error
end;
@ -428,15 +424,19 @@ stream_established({xmlstreamelement, El}, StateData) ->
case ?DICT:find({LFrom, LTo},
StateData#state.connections) of
{ok, established} ->
if ((Name == "iq") or
(Name == "message") or
(Name == "presence")) ->
Name = El#xmlel.name,
if ((Name == 'iq') or
(Name == 'message') or
(Name == 'presence')) ->
% XXX OLD FORMAT: From, To.
FromOld = exmpp_jid:to_ejabberd_jid(From),
ToOld = exmpp_jid:to_ejabberd_jid(To),
ejabberd_hooks:run(
s2s_receive_packet,
LFrom,
[From, To, NewEl]),
[FromOld, ToOld, ElOld]),
ejabberd_router:route(
From, To, NewEl);
FromOld, ToOld, ElOld);
true ->
error
end;
@ -447,35 +447,24 @@ stream_established({xmlstreamelement, El}, StateData) ->
true ->
error
end,
ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, El}]),
ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, ElOld}]),
{next_state, stream_established, StateData#state{timer = Timer}}
end;
stream_established({valid, From, To}, StateData) ->
send_element(StateData,
{xmlelement,
"db:result",
[{"from", To},
{"to", From},
{"type", "valid"}],
[]}),
LFrom = jlib:nameprep(From),
LTo = jlib:nameprep(To),
send_element(StateData, exmpp_dialback:validate(From, To)),
LFrom = exmpp_stringprep:nameprep(From),
LTo = exmpp_stringprep:nameprep(To),
NSD = StateData#state{
connections = ?DICT:store({LFrom, LTo}, established,
StateData#state.connections)},
{next_state, stream_established, NSD};
stream_established({invalid, From, To}, StateData) ->
send_element(StateData,
{xmlelement,
"db:result",
[{"from", To},
{"to", From},
{"type", "invalid"}],
[]}),
LFrom = jlib:nameprep(From),
LTo = jlib:nameprep(To),
Valid = exmpp_dialback:validate(From, To),
send_element(StateData, exmpp_stanza:set_type(Valid, "invalid")),
LFrom = exmpp_stringprep:nameprep(From),
LTo = exmpp_stringprep:nameprep(To),
NSD = StateData#state{
connections = ?DICT:erase({LFrom, LTo},
StateData#state.connections)},
@ -485,8 +474,8 @@ stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
stream_established(timeout, StateData) ->
@ -570,12 +559,21 @@ terminate(Reason, _StateName, StateData) ->
send_text(StateData, Text) ->
(StateData#state.sockmod):send(StateData#state.socket, Text).
send_element(StateData, #xmlel{ns = ?NS_XMPP, name = 'stream'} = El) ->
send_text(StateData, exmpp_xml:document_to_list(El));
send_element(StateData, #xmlel{ns = ?NS_JABBER_CLIENT} = El) ->
send_text(StateData, exmpp_xml:document_fragment_to_list(El,
[?NS_JABBER_CLIENT], ?PREFIXED_NS));
send_element(StateData, El) ->
send_text(StateData, xml:element_to_string(El)).
send_text(StateData, exmpp_xml:document_fragment_to_list(El,
?DEFAULT_NS, ?PREFIXED_NS)).
change_shaper(StateData, Host, JID) ->
Shaper = acl:match_rule(Host, StateData#state.shaper, JID),
% XXX OLD FORMAT: JIDOld is an old #jid.
JIDOld = exmpp_jid:to_ejabberd_jid(JID),
Shaper = acl:match_rule(Host, StateData#state.shaper, JIDOld),
(StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper).
@ -592,18 +590,20 @@ cancel_timer(Timer) ->
end.
is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:result" ->
is_key_packet(#xmlel{ns = ?NS_JABBER_DIALBACK, name = 'result',
attrs = Attrs} = El) ->
{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" ->
exmpp_stanza:get_recipient_from_attrs(Attrs),
exmpp_stanza:get_sender_from_attrs(Attrs),
exmpp_stanza:get_id_from_attrs(Attrs),
exmpp_xml:get_cdata_as_list(El)};
is_key_packet(#xmlel{ns = ?NS_JABBER_DIALBACK, name = 'verify',
attrs = Attrs} = El) ->
{verify,
xml:get_attr_s("to", Attrs),
xml:get_attr_s("from", Attrs),
xml:get_attr_s("id", Attrs),
xml:get_cdata(Els)};
exmpp_stanza:get_recipient_from_attrs(Attrs),
exmpp_stanza:get_sender_from_attrs(Attrs),
exmpp_stanza:get_id_from_attrs(Attrs),
exmpp_xml:get_cdata_as_list(El)};
is_key_packet(_) ->
false.
@ -625,10 +625,10 @@ get_cert_domains(Cert) ->
end,
if
D /= error ->
case jlib:string_to_jid(D) of
#jid{luser = "",
lserver = LD,
lresource = ""} ->
case exmpp_jid:string_to_jid(D) of
#jid{lnode = undefined,
ldomain = LD,
lresource = undefined} ->
[LD];
_ ->
[]
@ -662,8 +662,8 @@ get_cert_domains(Cert) ->
{ok, D} when is_binary(D) ->
case jlib:string_to_jid(
binary_to_list(D)) of
#jid{luser = "",
lserver = LD,
#jid{lnode = "",
ldomain = LD,
lresource = ""} ->
case idna:domain_utf8_to_ascii(LD) of
false ->
@ -678,10 +678,10 @@ get_cert_domains(Cert) ->
[]
end;
({dNSName, D}) when is_list(D) ->
case jlib:string_to_jid(D) of
#jid{luser = "",
lserver = LD,
lresource = ""} ->
case exmpp_jid:string_to_jid(D) of
#jid{lnode = undefined,
ldomain = LD,
lresource = undefined} ->
[LD];
_ ->
[]

View File

@ -54,8 +54,9 @@
code_change/4,
test_get_addr_port/1]).
-include("exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(state, {socket,
streamid,
@ -72,7 +73,7 @@
new = false, verify = false,
timer}).
%%-define(DBGFSM, true).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
@ -98,25 +99,10 @@
%% Specified in miliseconds. Default value is 5 minutes.
-define(MAX_RETRY_DELAY, 300000).
-define(STREAM_HEADER,
"<?xml version='1.0'?>"
"<stream:stream "
"xmlns:stream='http://etherx.jabber.org/streams' "
"xmlns='jabber:server' "
"xmlns:db='jabber:server:dialback' "
"to='~s'~s>"
).
-define(STREAM_TRAILER, "</stream:stream>").
-define(INVALID_NAMESPACE_ERR,
xml:element_to_string(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
xml:element_to_string(?SERR_HOST_UNKNOWN)).
-define(INVALID_XML_ERR,
xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, [?NS_JABBER_SERVER]).
-define(PREFIXED_NS, [{?NS_XMPP, "stream"}, {?NS_JABBER_DIALBACK, "db"}]).
%%%----------------------------------------------------------------------
%%% API
@ -209,16 +195,19 @@ open_socket(init, StateData) ->
{ok, Socket} ->
Version = if
StateData#state.use_v10 ->
" version='1.0'";
"1.0";
true ->
""
end,
NewStateData = StateData#state{socket = Socket,
tls_enabled = false,
streamid = new_id()},
send_text(NewStateData, io_lib:format(?STREAM_HEADER,
[StateData#state.server,
Version])),
Opening = exmpp_stream:opening(
StateData#state.server,
?NS_JABBER_SERVER,
Version),
send_element(NewStateData,
exmpp_stream:set_dialback_support(Opening)),
{next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
{error, _Reason} ->
?INFO_MSG("s2s connection: ~s -> ~s (remote server not found)",
@ -272,27 +261,27 @@ open_socket1(Addr, Port) ->
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case {xml:get_attr_s("xmlns", Attrs),
xml:get_attr_s("xmlns:db", Attrs),
xml:get_attr_s("version", Attrs) == "1.0"} of
{"jabber:server", "jabber:server:dialback", false} ->
wait_for_stream({xmlstreamstart, Opening}, StateData) ->
case {exmpp_stream:get_default_ns(Opening),
exmpp_xml:is_ns_declared_here(Opening, ?NS_JABBER_DIALBACK),
exmpp_stream:get_version(Opening) == {1, 0}} of
{?NS_JABBER_SERVER, true, false} ->
send_db_request(StateData);
{"jabber:server", "jabber:server:dialback", true} when
{?NS_JABBER_SERVER, true, true} when
StateData#state.use_v10 ->
{next_state, wait_for_features, StateData, ?FSMTIMEOUT};
{"jabber:server", "", true} when StateData#state.use_v10 ->
{?NS_JABBER_SERVER, false, true} when StateData#state.use_v10 ->
{next_state, wait_for_features, StateData#state{db_enabled = false}, ?FSMTIMEOUT};
_ ->
send_text(StateData, ?INVALID_NAMESPACE_ERR),
send_element(StateData, exmpp_stream:error('invalid-namespace')),
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid namespace)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid xml)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
@ -376,8 +365,8 @@ wait_for_validation({xmlstreamend, _Name}, StateData) ->
wait_for_validation({xmlstreamerror, _}, StateData) ->
?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamerror)",
[StateData#state.myname, StateData#state.server]),
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
wait_for_validation(timeout, #state{verify = {VPid, VKey, SID}} = StateData)
@ -401,41 +390,37 @@ wait_for_validation(closed, StateData) ->
wait_for_features({xmlstreamelement, El}, StateData) ->
case El of
{xmlelement, "stream:features", _Attrs, Els} ->
#xmlel{ns = ?NS_XMPP, name = 'features'} = Features ->
{SASLEXT, StartTLS, StartTLSRequired} =
lists:foldl(
fun({xmlelement, "mechanisms", Attrs1, Els1} = _El1,
fun(#xmlel{ns = ?NS_SASL, name = 'mechanisms'},
{_SEXT, STLS, STLSReq} = Acc) ->
case xml:get_attr_s("xmlns", Attrs1) of
?NS_SASL ->
NewSEXT =
lists:any(
fun({xmlelement, "mechanism", _, Els2}) ->
case xml:get_cdata(Els2) of
"EXTERNAL" -> true;
_ -> false
end;
(_) -> false
end, Els1),
{NewSEXT, STLS, STLSReq};
_ ->
try
Mechs = exmpp_client_sasl:announced_mechanisms(
El),
NewSEXT = lists:member("EXTERNAL", Mechs),
{NewSEXT, STLS, STLSReq}
catch
_Exception ->
Acc
end;
({xmlelement, "starttls", Attrs1, _Els1} = El1,
(#xmlel{ns = ?NS_TLS, name ='starttls'},
{SEXT, _STLS, _STLSReq} = Acc) ->
case xml:get_attr_s("xmlns", Attrs1) of
?NS_TLS ->
Req = case xml:get_subtag(El1, "required") of
{xmlelement, _, _, _} -> true;
false -> false
end,
{SEXT, true, Req};
_ ->
try
Support = exmpp_client_tls:announced_support(
El),
case Support of
none -> Acc;
optional -> {SEXT, true, false};
required -> {SEXT, true, true}
end
catch
_Exception ->
Acc
end;
(_, Acc) ->
Acc
end, {false, false, false}, Els),
end, {false, false, false}, Features#xmlel.children),
if
(not SASLEXT) and (not StartTLS) and
StateData#state.authenticated ->
@ -450,19 +435,14 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
SASLEXT and StateData#state.try_auth and
(StateData#state.new /= false) ->
send_element(StateData,
{xmlelement, "auth",
[{"xmlns", ?NS_SASL},
{"mechanism", "EXTERNAL"}],
[{xmlcdata,
jlib:encode_base64(
StateData#state.myname)}]}),
exmpp_client_sasl:selected_mechanism("EXTERNAL",
StateData#state.myname)),
{next_state, wait_for_auth_result,
StateData#state{try_auth = false}, ?FSMTIMEOUT};
StartTLS and StateData#state.tls and
(not StateData#state.tls_enabled) ->
send_element(StateData,
{xmlelement, "starttls",
[{"xmlns", ?NS_TLS}], []}),
exmpp_client_tls:starttls()),
{next_state, wait_for_starttls_proceed, StateData,
?FSMTIMEOUT};
StartTLSRequired and (not StateData#state.tls) ->
@ -483,9 +463,8 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
use_v10 = false}, ?FSMTIMEOUT}
end;
_ ->
send_text(StateData,
xml:element_to_string(?SERR_BAD_FORMAT) ++
?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('bad-format')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
@ -496,8 +475,8 @@ wait_for_features({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_features({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("wait for features: xmlstreamerror", []),
{stop, normal, StateData};
@ -512,48 +491,29 @@ wait_for_features(closed, StateData) ->
wait_for_auth_result({xmlstreamelement, El}, StateData) ->
case El of
{xmlelement, "success", Attrs, _Els} ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_SASL ->
?DEBUG("auth: ~p", [{StateData#state.myname,
StateData#state.server}]),
ejabberd_socket:reset_stream(StateData#state.socket),
send_text(StateData,
io_lib:format(?STREAM_HEADER,
[StateData#state.server,
" version='1.0'"])),
{next_state, wait_for_stream,
StateData#state{streamid = new_id(),
authenticated = true
}, ?FSMTIMEOUT};
_ ->
send_text(StateData,
xml:element_to_string(?SERR_BAD_FORMAT) ++
?STREAM_TRAILER),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
end;
{xmlelement, "failure", Attrs, _Els} ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_SASL ->
?DEBUG("restarted: ~p", [{StateData#state.myname,
StateData#state.server}]),
ejabberd_socket:close(StateData#state.socket),
{next_state, reopen_socket,
StateData#state{socket = undefined}, ?FSMTIMEOUT};
_ ->
send_text(StateData,
xml:element_to_string(?SERR_BAD_FORMAT) ++
?STREAM_TRAILER),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
end;
#xmlel{ns = ?NS_SASL, name = 'success'} ->
?DEBUG("auth: ~p", [{StateData#state.myname,
StateData#state.server}]),
ejabberd_socket:reset_stream(StateData#state.socket),
Opening = exmpp_stream:opening(
StateData#state.server,
?NS_JABBER_SERVER,
"1.0"),
send_element(StateData,
exmpp_stream:set_dialback_support(Opening)),
{next_state, wait_for_stream,
StateData#state{streamid = new_id(),
authenticated = true
}, ?FSMTIMEOUT};
#xmlel{ns = ?NS_SASL, name = 'failure'} ->
?DEBUG("restarted: ~p", [{StateData#state.myname,
StateData#state.server}]),
ejabberd_socket:close(StateData#state.socket),
{next_state, reopen_socket,
StateData#state{socket = undefined}, ?FSMTIMEOUT};
_ ->
send_text(StateData,
xml:element_to_string(?SERR_BAD_FORMAT) ++
?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('bad-format')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
@ -564,8 +524,8 @@ wait_for_auth_result({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_auth_result({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("wait for auth result: xmlstreamerror", []),
{stop, normal, StateData};
@ -580,42 +540,36 @@ wait_for_auth_result(closed, StateData) ->
wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
case El of
{xmlelement, "proceed", Attrs, _Els} ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_TLS ->
?DEBUG("starttls: ~p", [{StateData#state.myname,
StateData#state.server}]),
Socket = StateData#state.socket,
TLSOpts = case ejabberd_config:get_local_option(
{domain_certfile,
StateData#state.server}) of
undefined ->
StateData#state.tls_options;
CertFile ->
[{certfile, CertFile} |
lists:keydelete(
certfile, 1,
StateData#state.tls_options)]
end,
TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
NewStateData = StateData#state{socket = TLSSocket,
streamid = new_id(),
tls_enabled = true
},
send_text(NewStateData,
io_lib:format(?STREAM_HEADER,
[StateData#state.server,
" version='1.0'"])),
{next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
_ ->
send_text(StateData,
xml:element_to_string(?SERR_BAD_FORMAT) ++
?STREAM_TRAILER),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
end;
#xmlel{ns = ?NS_TLS, name = 'proceed'} ->
?DEBUG("starttls: ~p", [{StateData#state.myname,
StateData#state.server}]),
Socket = StateData#state.socket,
TLSOpts = case ejabberd_config:get_local_option(
{domain_certfile,
StateData#state.server}) of
undefined ->
StateData#state.tls_options;
CertFile ->
[{certfile, CertFile} |
lists:keydelete(
certfile, 1,
StateData#state.tls_options)]
end,
TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
NewStateData = StateData#state{socket = TLSSocket,
streamid = new_id(),
tls_enabled = true
},
Opening = exmpp_stream:opening(
StateData#state.server,
?NS_JABBER_SERVER,
"1.0"),
send_element(NewStateData,
exmpp_stream:set_dialback_support(Opening)),
{next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
_ ->
send_element(StateData, exmpp_stream:error('bad-format')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
@ -626,8 +580,8 @@ wait_for_starttls_proceed({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_starttls_proceed({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("wait for starttls proceed: xmlstreamerror", []),
{stop, normal, StateData};
@ -690,8 +644,8 @@ stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("stream established: ~s -> ~s (xmlstreamerror)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
@ -759,7 +713,10 @@ handle_info({send_text, Text}, StateName, StateData) ->
{next_state, StateName, StateData#state{timer = Timer},
get_timeout_interval(StateName)};
handle_info({send_element, El}, StateName, StateData) ->
handle_info({send_element, ElOld}, StateName, StateData) ->
% XXX OLD FORMAT: El.
El = exmpp_xml:xmlelement_to_xmlel(ElOld,
[?NS_JABBER_CLIENT], ?PREFIXED_NS),
case StateName of
stream_established ->
cancel_timer(StateData#state.timer),
@ -769,7 +726,7 @@ handle_info({send_element, El}, StateName, StateData) ->
%% In this state we bounce all message: We are waiting before
%% trying to reconnect
wait_before_retry ->
bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_element(El, 'remote-server-not-found'),
{next_state, StateName, StateData};
_ ->
Q = queue:in(El, StateData#state.queue),
@ -811,8 +768,8 @@ terminate(Reason, StateName, StateData) ->
{StateData#state.myname, StateData#state.server}, self(), Key)
end,
%% bounce queue manage by process and Erlang message queue
bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_queue(StateData#state.queue, 'remote-server-not-found'),
bounce_messages('remote-server-not-found'),
case StateData#state.socket of
undefined ->
ok;
@ -828,8 +785,14 @@ terminate(Reason, StateName, StateData) ->
send_text(StateData, Text) ->
ejabberd_socket:send(StateData#state.socket, Text).
send_element(StateData, #xmlel{ns = ?NS_XMPP, name = 'stream'} = El) ->
send_text(StateData, exmpp_xml:document_to_list(El));
send_element(StateData, #xmlel{ns = ?NS_JABBER_CLIENT} = El) ->
send_text(StateData, exmpp_xml:document_fragment_to_list(El,
[?NS_JABBER_CLIENT], ?PREFIXED_NS));
send_element(StateData, El) ->
send_text(StateData, xml:element_to_string(El)).
send_text(StateData, exmpp_xml:document_fragment_to_list(El,
?DEFAULT_NS, ?PREFIXED_NS)).
send_queue(StateData, Q) ->
case queue:out(Q) of
@ -840,24 +803,31 @@ send_queue(StateData, Q) ->
ok
end.
%% Bounce a single message (xmlelement)
bounce_element(El, Error) ->
{xmlelement, _Name, Attrs, _SubTags} = El,
case xml:get_attr_s("type", Attrs) of
%% Bounce a single message (xmlel)
bounce_element(El, Condition) ->
case exmpp_stanza:get_type(El) of
"error" -> ok;
"result" -> ok;
_ ->
Err = jlib:make_error_reply(El, Error),
From = jlib:string_to_jid(xml:get_tag_attr_s("from", El)),
To = jlib:string_to_jid(xml:get_tag_attr_s("to", El)),
ejabberd_router:route(To, From, Err)
Error = exmpp_stanza:error(El#xmlel.ns, Condition),
Err = exmpp_stanza:reply_with_error(El, Error),
From = exmpp_jid:string_to_jid(exmpp_stanza:get_sender(El)),
To = exmpp_jid:string_to_jid(exmpp_stanza:get_recipient(El)),
% XXX OLD FORMAT: From, To, Err.
% XXX No namespace conversion (:server <-> :client) is done.
% This is handled by C2S and S2S send_element functions.
ErrOld = exmpp_xml:xmlel_to_xmlelement(Err,
[?NS_JABBER_CLIENT], ?PREFIXED_NS),
FromOld = exmpp_jid:to_ejabberd_jid(From),
ToOld = exmpp_jid:to_ejabberd_jid(To),
ejabberd_router:route(ToOld, FromOld, ErrOld)
end.
bounce_queue(Q, Error) ->
bounce_queue(Q, Condition) ->
case queue:out(Q) of
{{value, El}, Q1} ->
bounce_element(El, Error),
bounce_queue(Q1, Error);
bounce_element(El, Condition),
bounce_queue(Q1, Condition);
{empty, _} ->
ok
end.
@ -874,11 +844,14 @@ cancel_timer(Timer) ->
ok
end.
bounce_messages(Error) ->
bounce_messages(Condition) ->
receive
{send_element, El} ->
bounce_element(El, Error),
bounce_messages(Error)
{send_element, ElOld} ->
% XXX OLD FORMAT: El.
El = exmpp_xml:xmlelement_to_xmlel(ElOld,
[?NS_JABBER_CLIENT], ?PREFIXED_NS),
bounce_element(El, Condition),
bounce_messages(Condition)
after 0 ->
ok
end.
@ -902,40 +875,33 @@ send_db_request(StateData) ->
false ->
ok;
Key1 ->
send_element(StateData,
{xmlelement,
"db:result",
[{"from", StateData#state.myname},
{"to", Server}],
[{xmlcdata, Key1}]})
send_element(StateData, exmpp_dialback:key(
StateData#state.myname, Server, Key1))
end,
case StateData#state.verify of
false ->
ok;
{_Pid, Key2, SID} ->
send_element(StateData,
{xmlelement,
"db:verify",
[{"from", StateData#state.myname},
{"to", StateData#state.server},
{"id", SID}],
[{xmlcdata, Key2}]})
send_element(StateData, exmpp_dialback:verify_request(
StateData#state.myname, StateData#state.server, SID, Key2))
end,
{next_state, wait_for_validation, StateData#state{new = New}, ?FSMTIMEOUT*6}.
is_verify_res({xmlelement, Name, Attrs, _Els}) when Name == "db:result" ->
is_verify_res(#xmlel{ns = ?NS_JABBER_DIALBACK, name = 'result',
attrs = Attrs}) ->
{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" ->
exmpp_stanza:get_recipient_from_attrs(Attrs),
exmpp_stanza:get_sender_from_attrs(Attrs),
exmpp_stanza:get_id_from_attrs(Attrs),
exmpp_stanza:get_type_from_attrs(Attrs)};
is_verify_res(#xmlel{ns = ?NS_JABBER_DIALBACK, name = 'verify',
attrs = Attrs}) ->
{verify,
xml:get_attr_s("to", Attrs),
xml:get_attr_s("from", Attrs),
xml:get_attr_s("id", Attrs),
xml:get_attr_s("type", Attrs)};
exmpp_stanza:get_recipient_from_attrs(Attrs),
exmpp_stanza:get_sender_from_attrs(Attrs),
exmpp_stanza:get_id_from_attrs(Attrs),
exmpp_stanza:get_type_from_attrs(Attrs)};
is_verify_res(_) ->
false.
@ -1032,8 +998,8 @@ get_timeout_interval(StateName) ->
%% function that want to wait for a reconnect delay before stopping.
wait_before_reconnect(StateData) ->
%% bounce queue manage by process and Erlang message queue
bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_queue(StateData#state.queue, 'remote-server-not-found'),
bounce_messages('remote-server-not-found'),
cancel_timer(StateData#state.timer),
Delay = case StateData#state.delay_to_retry of
undefined_delay ->