diff --git a/ChangeLog b/ChangeLog index f42d06a56..0851fcd71 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 * src/ejabberd_c2s.erl: Finish ejabberd_c2s conversion with the diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 9827775d2..31ae9454e 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -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), - ("" - "") - ). - --define(STREAM_TRAILER, ""). - --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]; _ -> [] diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index e465cfb39..d33439a6e 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -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, - "" - "" - ). - --define(STREAM_TRAILER, ""). - --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 ->