diff options
author | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2016-07-27 10:45:08 +0300 |
---|---|---|
committer | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2016-07-27 10:45:08 +0300 |
commit | c409ed2f2c09ae79a22745e2a253023787017893 (patch) | |
tree | 2a26aa019b06f37c39811d7e8fa9b1d77cc0b133 | |
parent | Get rid of "jlib.hrl" dependency in some files (diff) |
Rewrite S2S and ejabberd_service code to use XML generator
-rw-r--r-- | include/ejabberd.hrl | 2 | ||||
-rw-r--r-- | include/ns.hrl | 4 | ||||
-rw-r--r-- | include/xmpp_codec.hrl | 38 | ||||
-rw-r--r-- | src/ejabberd_c2s.erl | 191 | ||||
-rw-r--r-- | src/ejabberd_http_bind.erl | 2 | ||||
-rw-r--r-- | src/ejabberd_router.erl | 21 | ||||
-rw-r--r-- | src/ejabberd_s2s.erl | 46 | ||||
-rw-r--r-- | src/ejabberd_s2s_in.erl | 653 | ||||
-rw-r--r-- | src/ejabberd_s2s_out.erl | 924 | ||||
-rw-r--r-- | src/ejabberd_service.erl | 379 | ||||
-rw-r--r-- | src/xmpp.erl | 13 | ||||
-rw-r--r-- | src/xmpp_codec.erl | 531 | ||||
-rw-r--r-- | tools/xmpp_codec.spec | 60 |
13 files changed, 1599 insertions, 1265 deletions
diff --git a/include/ejabberd.hrl b/include/ejabberd.hrl index 6316d7813..a97474d2b 100644 --- a/include/ejabberd.hrl +++ b/include/ejabberd.hrl @@ -39,7 +39,7 @@ -define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>). --define(S2STIMEOUT, 600000). +-define(S2STIMEOUT, timer:minutes(10)). %%-define(DBGFSM, true). diff --git a/include/ns.hrl b/include/ns.hrl index a150746e7..b30161565 100644 --- a/include/ns.hrl +++ b/include/ns.hrl @@ -18,6 +18,10 @@ %%% %%%---------------------------------------------------------------------- +-define(NS_COMPONENT, <<"jabber:component:accept">>). +-define(NS_SERVER, <<"jabber:server">>). +-define(NS_SERVER_DIALBACK, <<"jabber:server:dialback">>). +-define(NS_CLIENT, <<"jabber:client">>). -define(NS_DISCO_ITEMS, <<"http://jabber.org/protocol/disco#items">>). -define(NS_DISCO_INFO, diff --git a/include/xmpp_codec.hrl b/include/xmpp_codec.hrl index b14c0d11f..43bb6b098 100644 --- a/include/xmpp_codec.hrl +++ b/include/xmpp_codec.hrl @@ -104,6 +104,16 @@ xmlns :: binary()}). -type sm_a() :: #sm_a{}. +-record(stream_start, {from :: any(), + to :: any(), + id = <<>> :: binary(), + version = <<>> :: binary(), + xmlns :: binary(), + stream_xmlns = <<>> :: binary(), + db_xmlns = <<>> :: binary(), + lang = <<>> :: binary()}). +-type stream_start() :: #stream_start{}. + -record(muc_subscribe, {nick :: binary(), events = [] :: [binary()]}). -type muc_subscribe() :: #muc_subscribe{}. @@ -138,6 +148,9 @@ -record(sasl_challenge, {text :: any()}). -type sasl_challenge() :: #sasl_challenge{}. +-record(handshake, {data = <<>> :: binary()}). +-type handshake() :: #handshake{}. + -record(gone, {uri :: binary()}). -type gone() :: #gone{}. @@ -672,6 +685,21 @@ text :: #text{}}). -type error() :: #error{}. +-record(db_verify, {from :: any(), + to :: any(), + id :: binary(), + type :: 'error' | 'invalid' | 'valid', + key = <<>> :: binary(), + error :: #error{}}). +-type db_verify() :: #db_verify{}. + +-record(db_result, {from :: any(), + to :: any(), + type :: 'error' | 'invalid' | 'valid', + key = <<>> :: binary(), + error :: #error{}}). +-type db_result() :: #db_result{}. + -record(presence, {id :: binary(), type = available :: 'available' | 'error' | 'probe' | 'subscribe' | 'subscribed' | 'unavailable' | 'unsubscribe' | 'unsubscribed', lang :: binary(), @@ -772,12 +800,12 @@ utc :: any()}). -type time() :: #time{}. --type xmpp_element() :: compression() | +-type xmpp_element() :: muc_admin() | + compression() | pubsub_subscription() | xdata_option() | version() | pubsub_affiliation() | - muc_admin() | mam_fin() | sm_a() | carbons_sent() | @@ -790,8 +818,10 @@ compressed() | block_list() | rsm_set() | + db_result() | 'see-other-host'() | hint() | + stream_start() | stanza_id() | starttls_proceed() | client_id() | @@ -831,6 +861,7 @@ pubsub() | muc_owner() | muc_actor() | + vcard_name() | adhoc_note() | rosterver_feature() | muc_invite() | @@ -842,12 +873,12 @@ sm_enable() | starttls_failure() | sasl_challenge() | + handshake() | x_conference() | private() | compress_failure() | sasl_failure() | bookmark_storage() | - vcard_name() | muc_decline() | sasl_auth() | p1_push() | @@ -891,6 +922,7 @@ csi() | roster_query() | mam_query() | + db_verify() | bookmark_url() | vcard_email() | vcard_label() | diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 8d217a354..1ae9a7c29 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -320,42 +320,46 @@ get_subscribed(FsmRef) -> (?GEN_FSM):sync_send_all_state_event(FsmRef, get_subscribed, 1000). -wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> - DefaultLang = ?MYLANG, - case fxml:get_attr_s(<<"xmlns:stream">>, Attrs) of - ?NS_STREAM -> - Server = - case StateData#state.server of - <<"">> -> - jid:nameprep(fxml:get_attr_s(<<"to">>, Attrs)); - S -> S - end, - Lang = case fxml:get_attr_s(<<"xml:lang">>, Attrs) of - Lang1 when byte_size(Lang1) =< 35 -> - %% As stated in BCP47, 4.4.1: - %% Protocols or specifications that - %% specify limited buffer sizes for - %% language tags MUST allow for - %% language tags of at least 35 characters. - Lang1; - _ -> - %% Do not store long language tag to - %% avoid possible DoS/flood attacks - <<"">> - end, - StreamVersion = case fxml:get_attr_s(<<"version">>, Attrs) of - <<"1.0">> -> - <<"1.0">>; - _ -> - <<"">> - end, +wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> + try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of + #stream_start{xmlns = NS_CLIENT, stream_xmlns = NS_STREAM, lang = Lang} + when NS_CLIENT /= ?NS_CLIENT; NS_STREAM /= ?NS_STREAM -> + send_header(StateData, ?MYNAME, <<"">>, Lang), + send_element(StateData, xmpp:serr_invalid_namespace()), + {stop, normal, StateData}; + #stream_start{lang = Lang} when byte_size(Lang) > 35 -> + %% As stated in BCP47, 4.4.1: + %% Protocols or specifications that specify limited buffer sizes for + %% language tags MUST allow for language tags of at least 35 characters. + %% Do not store long language tag to avoid possible DoS/flood attacks + send_header(StateData, ?MYNAME, <<"">>, ?MYLANG), + Txt = <<"Too long value of 'xml:lang' attribute">>, + send_element(StateData, + xmpp:serr_policy_violation(Txt, ?MYLANG)), + {stop, normal, StateData}; + #stream_start{to = undefined, lang = Lang} -> + Txt = <<"Missing 'to' attribute">>, + send_header(StateData, ?MYNAME, <<"">>, Lang), + send_element(StateData, + xmpp:serr_improper_addressing(Txt, Lang)), + {stop, normal, StateData}; + #stream_start{to = #jid{lserver = To}, lang = Lang, + version = Version} -> + Server = case StateData#state.server of + <<"">> -> To; + S -> S + end, + StreamVersion = case Version of + <<"1.0">> -> <<"1.0">>; + _ -> <<"">> + end, IsBlacklistedIP = is_ip_blacklisted(StateData#state.ip, Lang), case lists:member(Server, ?MYHOSTS) of true when IsBlacklistedIP == false -> change_shaper(StateData, jid:make(<<"">>, Server, <<"">>)), case StreamVersion of <<"1.0">> -> - send_header(StateData, Server, <<"1.0">>, DefaultLang), + send_header(StateData, Server, <<"1.0">>, ?MYLANG), case StateData#state.authenticated of false -> TLS = StateData#state.tls, @@ -458,7 +462,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> end end; _ -> - send_header(StateData, Server, <<"">>, DefaultLang), + send_header(StateData, Server, <<"">>, ?MYLANG), if not StateData#state.tls_enabled and StateData#state.tls_required -> send_element( @@ -477,17 +481,18 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> {true, LogReason, ReasonT} = IsBlacklistedIP, ?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s", [jlib:ip_to_list(IP), LogReason]), - send_header(StateData, Server, StreamVersion, DefaultLang), + send_header(StateData, Server, StreamVersion, ?MYLANG), send_element(StateData, xmpp:serr_policy_violation(ReasonT, Lang)), {stop, normal, StateData}; _ -> - send_header(StateData, ?MYNAME, StreamVersion, DefaultLang), + send_header(StateData, ?MYNAME, StreamVersion, ?MYLANG), send_element(StateData, xmpp:serr_host_unknown()), {stop, normal, StateData} - end; - _ -> - send_header(StateData, ?MYNAME, <<"">>, DefaultLang), - send_element(StateData, xmpp:serr_invalid_namespace()), + end + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + send_header(StateData, ?MYNAME, <<"">>, ?MYLANG), + send_element(StateData, xmpp:serr_not_well_formed(Txt, ?MYLANG)), {stop, normal, StateData} end; wait_for_stream(timeout, StateData) -> @@ -854,38 +859,36 @@ resource_conflict_action(U, S, R) -> {accept_resource, Rnew} end. --spec decode_subels(stanza()) -> stanza(). -decode_subels(#iq{sub_els = [El], type = T} = IQ) when T == set; T == get -> - NewEl = case xmpp:get_ns(El) of - ?NS_BIND when T == set -> xmpp:decode(El); - ?NS_AUTH -> xmpp:decode(El); - ?NS_PRIVACY -> xmpp:decode(El); - ?NS_BLOCKING -> xmpp:decode(El); - _ -> El - end, - IQ#iq{sub_els = [NewEl]}; -decode_subels(Pkt) -> - Pkt. - --spec decode_element(xmlel(), state_name(), state()) -> fsm_next(). +-spec decode_element(xmlel(), state_name(), state()) -> fsm_transition(). decode_element(#xmlel{} = El, StateName, StateData) -> - try - Pkt0 = xmpp:decode(El, [ignore_els]), - Pkt = decode_subels(Pkt0), - ?MODULE:StateName(Pkt, StateData) + try case xmpp:decode(El, [ignore_els]) of + #iq{sub_els = [_], type = T} = Pkt when T == set; T == get -> + NewPkt = xmpp:decode_els( + Pkt, + fun(SubEl) when StateName == session_established -> + case xmpp:get_ns(SubEl) of + ?NS_PRIVACY -> true; + ?NS_BLOCKING -> true; + _ -> false + end; + (SubEl) -> + xmpp_codec:is_known_tag(SubEl) + end), + ?MODULE:StateName(NewPkt, StateData); + Pkt -> + ?MODULE:StateName(Pkt, StateData) + end catch error:{xmpp_codec, Why} -> - Type = xmpp:get_type(El), NS = xmpp:get_ns(El), case xmpp:is_stanza(El) of - true when Type /= <<"result">>, Type /= <<"error">> -> + true -> Lang = xmpp:get_lang(El), Txt = xmpp:format_error(Why), - Err = xmpp:make_error(El, xmpp:err_bad_request(Txt, Lang)), - send_element(StateData, Err); - _ when NS == ?NS_STREAM_MGMT_2; NS == ?NS_STREAM_MGMT_3 -> + send_error(StateData, El, xmpp:err_bad_request(Txt, Lang)); + false when NS == ?NS_STREAM_MGMT_2; NS == ?NS_STREAM_MGMT_3 -> Err = #sm_failed{reason = 'bad-request', xmlns = NS}, send_element(StateData, Err); - _ -> + false -> ok end, fsm_next_state(StateName, StateData) @@ -951,13 +954,7 @@ wait_for_bind(stop, StateData) -> wait_for_bind(Pkt, StateData) -> case xmpp:is_stanza(Pkt) of true -> - Type = xmpp:get_type(Pkt), - if Type /= error, Type /= result -> - Err = xmpp:make_error(Pkt, xmpp:err_not_acceptable()), - send_element(StateData, Err); - true -> - ok - end; + send_error(StateData, Pkt, xmpp:err_not_acceptable()); false -> ok end, @@ -1046,7 +1043,7 @@ session_established(closed, StateData) -> {stop, normal, StateData}; session_established(stop, StateData) -> {stop, normal, StateData}; -session_established(Pkt, StateData) -> +session_established(Pkt, StateData) when ?is_stanza(Pkt) -> FromJID = StateData#state.jid, case check_from(Pkt, FromJID) of 'invalid-from' -> @@ -1055,11 +1052,13 @@ session_established(Pkt, StateData) -> _ -> NewStateData = update_num_stanzas_in(StateData, Pkt), session_established2(Pkt, NewStateData) - end. + end; +session_established(_Pkt, StateData) -> + fsm_next_state(session_established, StateData). -spec session_established2(xmpp_element(), state()) -> fsm_next(). %% Process packets sent by user (coming from user on c2s XMPP connection) -session_established2(Pkt, StateData) when ?is_stanza(Pkt) -> +session_established2(Pkt, StateData) -> User = StateData#state.user, Server = StateData#state.server, FromJID = StateData#state.jid, @@ -1116,11 +1115,7 @@ session_established2(Pkt, StateData) when ?is_stanza(Pkt) -> end, ejabberd_hooks:run(c2s_loop_debug, [{xmlstreamelement, Pkt}]), - fsm_next_state(session_established, NewState); -session_established2(Pkt, StateData) -> - ejabberd_hooks:run(c2s_loop_debug, - [{xmlstreamelement, Pkt}]), - fsm_next_state(session_established, StateData). + fsm_next_state(session_established, NewState). wait_for_resume({xmlstreamelement, _El} = Event, StateData) -> Result = session_established(Event, StateData), @@ -1573,6 +1568,16 @@ send_element(StateData, #xmlel{} = El) -> send_element(StateData, Pkt) -> send_element(StateData, xmpp:encode(Pkt)). +-spec send_error(state(), xmlel() | stanza(), error()) -> ok. +send_error(StateData, Stanza, Error) -> + Type = xmpp:get_type(Stanza), + if Type == error; Type == result; + Type == <<"error">>; Type == <<"result">> -> + ok; + true -> + send_element(StateData, xmpp:make_error(Stanza, Error)) + end. + -spec send_stanza(state(), xmpp_element()) -> state(). send_stanza(StateData, Stanza) when StateData#state.csi_state == inactive -> csi_filter_stanza(StateData, Stanza); @@ -2136,28 +2141,24 @@ is_ip_blacklisted({IP, _Port}, Lang) -> %% Check from attributes %% returns invalid-from|NewElement +-spec check_from(stanza(), jid()) -> 'invalid-from' | stanza(). check_from(Pkt, FromJID) -> - case xmpp:is_stanza(Pkt) of - false -> + JID = xmpp:get_from(Pkt), + case JID of + undefined -> Pkt; - true -> - JID = xmpp:get_from(Pkt), - case JID of - undefined -> + #jid{} -> + if + (JID#jid.luser == FromJID#jid.luser) and + (JID#jid.lserver == FromJID#jid.lserver) and + (JID#jid.lresource == FromJID#jid.lresource) -> Pkt; - #jid{} -> - if - (JID#jid.luser == FromJID#jid.luser) and - (JID#jid.lserver == FromJID#jid.lserver) and - (JID#jid.lresource == FromJID#jid.lresource) -> - Pkt; - (JID#jid.luser == FromJID#jid.luser) and - (JID#jid.lserver == FromJID#jid.lserver) and - (JID#jid.lresource == <<"">>) -> - Pkt; - true -> - 'invalid-from' - end + (JID#jid.luser == FromJID#jid.luser) and + (JID#jid.lserver == FromJID#jid.lserver) and + (JID#jid.lresource == <<"">>) -> + Pkt; + true -> + 'invalid-from' end end. diff --git a/src/ejabberd_http_bind.erl b/src/ejabberd_http_bind.erl index ea8cd792f..6fa38110c 100644 --- a/src/ejabberd_http_bind.erl +++ b/src/ejabberd_http_bind.erl @@ -125,8 +125,6 @@ %% Wait 100ms before continue processing, to allow the client provide more related stanzas. -define(BOSH_VERSION, <<"1.8">>). --define(NS_CLIENT, <<"jabber:client">>). - -define(NS_BOSH, <<"urn:xmpp:xbosh">>). -define(NS_HTTP_BIND, diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index 83ffd932b..d65edc6e3 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -389,7 +389,13 @@ do_route(OrigFrom, OrigTo, OrigPacket) -> {From, To, Packet} -> LDstDomain = To#jid.lserver, case mnesia:dirty_read(route, LDstDomain) of - [] -> ejabberd_s2s:route(From, To, Packet); + [] -> + try xmpp:decode(Packet, [ignore_els]) of + Pkt -> + ejabberd_s2s:route(From, To, Pkt) + catch _:{xmpp_codec, Why} -> + log_decoding_error(From, To, Packet, Why) + end; [R] -> do_route(From, To, Packet, R); Rs -> @@ -425,15 +431,18 @@ do_route(From, To, Packet, #route{local_hint = LocalHint, Pid ! {route, From, To, Pkt} end catch error:{xmpp_codec, Why} -> - ?ERROR_MSG("failed to decode xml element ~p when " - "routing from ~s to ~s: ~s", - [Packet, jid:to_string(From), jid:to_string(To), - xmpp:format_error(Why)]), - drop + log_decoding_error(From, To, Packet, Why) end; do_route(_From, _To, _Packet, _Route) -> drop. +-spec log_decoding_error(jid(), jid(), xmlel() | xmpp_element(), term()) -> ok. +log_decoding_error(From, To, Packet, Reason) -> + ?ERROR_MSG("failed to decode xml element ~p when " + "routing from ~s to ~s: ~s", + [Packet, jid:to_string(From), jid:to_string(To), + xmpp:format_error(Reason)]). + -spec get_component_number(binary()) -> pos_integer() | undefined. get_component_number(LDomain) -> ejabberd_config:get_option( diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index 19de64adb..e585257e8 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -55,7 +55,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("ejabberd_commands.hrl"). @@ -89,7 +89,7 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). --spec route(jid(), jid(), xmlel()) -> ok. +-spec route(jid(), jid(), xmpp_element()) -> ok. route(From, To, Packet) -> case catch do_route(From, To, Packet) of @@ -222,6 +222,7 @@ check_peer_certificate(SockMod, Sock, Peer) -> {error, <<"Cannot get peer certificate">>} end. +-spec make_key({binary(), binary()}, binary()) -> binary(). make_key({From, To}, StreamID) -> Secret = ejabberd_config:get_option(shared_key, fun(V) -> V end), p1_sha:to_hexlist( @@ -275,7 +276,7 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- - +-spec clean_table_from_bad_node(node()) -> any(). clean_table_from_bad_node(Node) -> F = fun() -> Es = mnesia:select( @@ -289,6 +290,7 @@ clean_table_from_bad_node(Node) -> end, mnesia:async_dirty(F). +-spec do_route(jid(), jid(), stanza()) -> ok | false. do_route(From, To, Packet) -> ?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket " "~P~n", @@ -296,28 +298,16 @@ do_route(From, To, Packet) -> case find_connection(From, To) of {atomic, Pid} when is_pid(Pid) -> ?DEBUG("sending to process ~p~n", [Pid]), - #xmlel{name = Name, attrs = Attrs, children = Els} = - Packet, - NewAttrs = - jlib:replace_from_to_attrs(jid:to_string(From), - jid:to_string(To), Attrs), #jid{lserver = MyServer} = From, ejabberd_hooks:run(s2s_send_packet, MyServer, [From, To, Packet]), - send_element(Pid, - #xmlel{name = Name, attrs = NewAttrs, children = Els}), + send_element(Pid, xmpp:set_from_to(Packet, From, To)), ok; {aborted, _Reason} -> - case fxml:get_tag_attr_s(<<"type">>, Packet) of - <<"error">> -> ok; - <<"result">> -> ok; - _ -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"No s2s connection found">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)), - ejabberd_router:route(To, From, Err) - end, + Lang = xmpp:get_lang(Packet), + Txt = <<"No s2s connection found">>, + Err = xmpp:err_service_unavailable(Txt, Lang), + ejabberd_router:route_error(To, From, Packet, Err), false end. @@ -367,9 +357,11 @@ find_connection(From, To) -> end end. +-spec choose_connection(jid(), [#s2s{}]) -> pid(). choose_connection(From, Connections) -> choose_pid(From, [C#s2s.pid || C <- Connections]). +-spec choose_pid(jid(), [pid()]) -> pid(). choose_pid(From, Pids) -> Pids1 = case [P || P <- Pids, node(P) == node()] of [] -> Pids; @@ -417,22 +409,21 @@ new_connection(MyServer, Server, From, FromTo, end, TRes. +-spec max_s2s_connections_number({binary(), binary()}) -> integer(). max_s2s_connections_number({From, To}) -> - case acl:match_rule(From, max_s2s_connections, - jid:make(<<"">>, To, <<"">>)) - of + case acl:match_rule(From, max_s2s_connections, jid:make(To)) of Max when is_integer(Max) -> Max; _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER end. +-spec max_s2s_connections_number_per_node({binary(), binary()}) -> integer(). max_s2s_connections_number_per_node({From, To}) -> - case acl:match_rule(From, max_s2s_connections_per_node, - jid:make(<<"">>, To, <<"">>)) - of + case acl:match_rule(From, max_s2s_connections_per_node, jid:make(To)) of Max when is_integer(Max) -> Max; _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE end. +-spec needed_connections_number([#s2s{}], integer(), integer()) -> integer(). needed_connections_number(Ls, MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode) -> LocalLs = [L || L <- Ls, node(L#s2s.pid) == node()], @@ -444,6 +435,7 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber, %% Description: Return true if the destination must be considered as a %% service. %% -------------------------------------------------------------------- +-spec is_service(jid(), jid()) -> boolean(). is_service(From, To) -> LFromDomain = From#jid.lserver, case ejabberd_config:get_option( @@ -541,7 +533,7 @@ allow_host1(MyHost, S2SHost) -> s2s_access, fun(A) -> A end, all), - JID = jid:make(<<"">>, S2SHost, <<"">>), + JID = jid:make(S2SHost), case acl:match_rule(MyHost, Rule, JID) of deny -> false; allow -> diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index d8d0a400a..04b961b3d 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -42,7 +42,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -define(DICT, dict). @@ -62,40 +62,19 @@ connections = (?DICT):new() :: ?TDICT, timer = make_ref() :: reference()}). -%-define(DBGFSM, true). +-type state_name() :: wait_for_stream | wait_for_features | stream_established. +-type state() :: #state{}. +-type fsm_next() :: {next_state, state_name(), state()}. +-type fsm_stop() :: {stop, normal, state()}. +-type fsm_transition() :: fsm_stop() | fsm_next(). +%%-define(DBGFSM, true). -ifdef(DBGFSM). - -define(FSMOPTS, [{debug, [trace]}]). - -else. - -define(FSMOPTS, []). - -endif. --define(STREAM_HEADER(Version), - <<"<?xml version='1.0'?><stream:stream " - "xmlns:stream='http://etherx.jabber.org/stream" - "s' xmlns='jabber:server' xmlns:db='jabber:ser" - "ver:dialback' id='", - (StateData#state.streamid)/binary, "'", Version/binary, - ">">>). - --define(STREAM_TRAILER, <<"</stream:stream>">>). - --define(INVALID_NAMESPACE_ERR, - fxml:element_to_binary(?SERR_INVALID_NAMESPACE)). - --define(HOST_UNKNOWN_ERR, - fxml:element_to_binary(?SERR_HOST_UNKNOWN)). - --define(INVALID_FROM_ERR, - fxml:element_to_binary(?SERR_INVALID_FROM)). - --define(INVALID_XML_ERR, - fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)). - start(SockData, Opts) -> supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]). @@ -185,319 +164,252 @@ init([{SockMod, Socket}, Opts]) -> %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- - -wait_for_stream({xmlstreamstart, _Name, Attrs}, - StateData) -> - case {fxml:get_attr_s(<<"xmlns">>, Attrs), - fxml:get_attr_s(<<"xmlns:db">>, Attrs), - fxml:get_attr_s(<<"to">>, Attrs), - fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>} - of - {<<"jabber:server">>, _, Server, true} - when StateData#state.tls and - not StateData#state.authenticated -> - send_text(StateData, - ?STREAM_HEADER(<<" version='1.0'">>)), - Auth = if StateData#state.tls_enabled -> - case jid:nameprep(fxml:get_attr_s(<<"from">>, Attrs)) of - From when From /= <<"">>, From /= error -> - {Result, Message} = - ejabberd_s2s:check_peer_certificate(StateData#state.sockmod, - StateData#state.socket, - From), - {Result, From, Message}; - _ -> - {error, <<"(unknown)">>, - <<"Got no valid 'from' attribute">>} - end; - true -> - {no_verify, <<"(unknown)">>, - <<"TLS not (yet) enabled">>} - end, - StartTLS = if StateData#state.tls_enabled -> []; - not StateData#state.tls_enabled and +wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> + try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of + #stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM} + when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM -> + send_header(StateData, <<" version='1.0'">>), + send_element(StateData, xmpp:serr_invalid_namespace()), + {stop, normal, StateData}; + #stream_start{to = #jid{lserver = Server}, + from = #jid{lserver = From}, + version = <<"1.0">>} + when StateData#state.tls and not StateData#state.authenticated -> + send_header(StateData, <<" version='1.0'">>), + Auth = if StateData#state.tls_enabled -> + {Result, Message} = + ejabberd_s2s:check_peer_certificate( + StateData#state.sockmod, + StateData#state.socket, + From), + {Result, From, Message}; + true -> + {no_verify, <<"(unknown)">>, <<"TLS not (yet) enabled">>} + end, + StartTLS = if StateData#state.tls_enabled -> []; + not StateData#state.tls_enabled and not StateData#state.tls_required -> - [#xmlel{name = <<"starttls">>, - attrs = [{<<"xmlns">>, ?NS_TLS}], - children = []}]; - not StateData#state.tls_enabled and + [#starttls{required = false}]; + not StateData#state.tls_enabled and StateData#state.tls_required -> - [#xmlel{name = <<"starttls">>, - attrs = [{<<"xmlns">>, ?NS_TLS}], - children = - [#xmlel{name = <<"required">>, - attrs = [], children = []}]}] - end, - case Auth of - {error, RemoteServer, CertError} - when StateData#state.tls_certverify -> - ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)", - [StateData#state.server, RemoteServer, CertError]), - send_text(StateData, - <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>, - CertError)))/binary, - (?STREAM_TRAILER)/binary>>), - {stop, normal, StateData}; - {VerifyResult, RemoteServer, Msg} -> - {SASL, NewStateData} = case VerifyResult of - ok -> - {[#xmlel{name = <<"mechanisms">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = - [#xmlel{name = <<"mechanism">>, - attrs = [], - children = - [{xmlcdata, - <<"EXTERNAL">>}]}]}], - StateData#state{auth_domain = RemoteServer}}; - error -> - ?DEBUG("Won't accept certificate of ~s: ~s", - [RemoteServer, Msg]), - {[], StateData}; - no_verify -> - {[], StateData} - end, - send_element(NewStateData, - #xmlel{name = <<"stream:features">>, attrs = [], - children = - SASL ++ - StartTLS ++ - ejabberd_hooks:run_fold(s2s_stream_features, - Server, [], - [Server])}), - {next_state, wait_for_feature_request, - NewStateData#state{server = Server}} - end; - {<<"jabber:server">>, _, Server, true} - when StateData#state.authenticated -> - send_text(StateData, - ?STREAM_HEADER(<<" version='1.0'">>)), - send_element(StateData, - #xmlel{name = <<"stream:features">>, attrs = [], - children = - ejabberd_hooks:run_fold(s2s_stream_features, - Server, [], - [Server])}), - {next_state, stream_established, StateData}; - {<<"jabber:server">>, <<"jabber:server:dialback">>, - _Server, _} when - (StateData#state.tls_required and StateData#state.tls_enabled) - or (not StateData#state.tls_required) -> - send_text(StateData, ?STREAM_HEADER(<<"">>)), - {next_state, stream_established, StateData}; - _ -> - send_text(StateData, ?INVALID_NAMESPACE_ERR), - {stop, normal, StateData} + [#starttls{required = true}] + end, + case Auth of + {error, RemoteServer, CertError} + when StateData#state.tls_certverify -> + ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)", + [StateData#state.server, RemoteServer, CertError]), + send_element(StateData, + xmpp:serr_policy_violation(CertError, ?MYLANG)), + {stop, normal, StateData}; + {VerifyResult, RemoteServer, Msg} -> + {SASL, NewStateData} = + case VerifyResult of + ok -> + {[#sasl_mechanisms{list = [<<"EXTERNAL">>]}], + StateData#state{auth_domain = RemoteServer}}; + error -> + ?DEBUG("Won't accept certificate of ~s: ~s", + [RemoteServer, Msg]), + {[], StateData}; + no_verify -> + {[], StateData} + end, + send_element(NewStateData, + #stream_features{ + sub_els = SASL ++ StartTLS ++ + ejabberd_hooks:run_fold( + s2s_stream_features, Server, [], + [Server])}), + {next_state, wait_for_feature_request, + NewStateData#state{server = Server}} + end; + #stream_start{to = #jid{lserver = Server}, + version = <<"1.0">>} when StateData#state.authenticated -> + send_header(StateData, <<" version='1.0'">>), + send_element(StateData, + #stream_features{ + sub_els = ejabberd_hooks:run_fold( + s2s_stream_features, Server, [], + [Server])}), + {next_state, stream_established, StateData}; + #stream_start{db_xmlns = ?NS_SERVER_DIALBACK} + when (StateData#state.tls_required and StateData#state.tls_enabled) + or (not StateData#state.tls_required) -> + send_header(StateData, <<"">>), + {next_state, stream_established, StateData}; + #stream_start{} -> + send_header(StateData, <<" version='1.0'">>), + send_element(StateData, xmpp:serr_undefined_condition()), + {stop, normal, StateData} + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + send_header(StateData, <<" version='1.0'">>), + send_element(StateData, xmpp:serr_not_well_formed(Txt, ?MYLANG)), + {stop, normal, StateData} end; wait_for_stream({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?STREAM_HEADER(<<"">>))/binary, - (?INVALID_XML_ERR)/binary, (?STREAM_TRAILER)/binary>>), + send_header(StateData, <<"">>), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_stream(timeout, StateData) -> + send_header(StateData, <<"">>), + send_element(StateData, xmpp:serr_connection_timeout()), {stop, normal, StateData}; wait_for_stream(closed, StateData) -> {stop, normal, StateData}. -wait_for_feature_request({xmlstreamelement, El}, - StateData) -> - #xmlel{name = Name, attrs = Attrs} = El, - TLS = StateData#state.tls, - TLSEnabled = StateData#state.tls_enabled, - SockMod = - (StateData#state.sockmod):get_sockmod(StateData#state.socket), - case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of - {?NS_TLS, <<"starttls">>} - when TLS == true, TLSEnabled == false, - SockMod == gen_tcp -> - ?DEBUG("starttls", []), - Socket = StateData#state.socket, - TLSOpts1 = case - ejabberd_config:get_option( - {domain_certfile, StateData#state.server}, - fun iolist_to_binary/1) of - undefined -> StateData#state.tls_options; - CertFile -> - [{certfile, CertFile} | lists:keydelete(certfile, 1, - StateData#state.tls_options)] - end, - TLSOpts = case ejabberd_config:get_option( - {s2s_tls_compression, StateData#state.server}, - fun(true) -> true; - (false) -> false - end, false) of - true -> lists:delete(compression_none, TLSOpts1); - false -> [compression_none | TLSOpts1] - end, - TLSSocket = (StateData#state.sockmod):starttls(Socket, - TLSOpts, - fxml:element_to_binary(#xmlel{name - = - <<"proceed">>, - attrs - = - [{<<"xmlns">>, - ?NS_TLS}], - children - = - []})), - {next_state, wait_for_stream, - StateData#state{socket = TLSSocket, streamid = new_id(), - tls_enabled = true, tls_options = TLSOpts}}; - {?NS_SASL, <<"auth">>} when TLSEnabled -> - Mech = fxml:get_attr_s(<<"mechanism">>, Attrs), - case Mech of - <<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> -> - AuthDomain = StateData#state.auth_domain, - AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>, - AuthDomain), - if AllowRemoteHost -> - (StateData#state.sockmod):reset_stream(StateData#state.socket), - send_element(StateData, - #xmlel{name = <<"success">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = []}), - ?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)", - [AuthDomain, StateData#state.tls_enabled]), - change_shaper(StateData, <<"">>, - jid:make(<<"">>, AuthDomain, <<"">>)), - {next_state, wait_for_stream, - StateData#state{streamid = new_id(), - authenticated = true}}; - true -> - send_element(StateData, - #xmlel{name = <<"failure">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = []}), - send_text(StateData, ?STREAM_TRAILER), - {stop, normal, StateData} - end; - _ -> - send_element(StateData, - #xmlel{name = <<"failure">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = - [#xmlel{name = <<"invalid-mechanism">>, - attrs = [], children = []}]}), - {stop, normal, StateData} - end; - _ -> - stream_established({xmlstreamelement, El}, StateData) +wait_for_feature_request({xmlstreamelement, El}, StateData) -> + decode_element(El, wait_for_feature_request, StateData); +wait_for_feature_request(#starttls{}, + #state{tls = true, tls_enabled = false} = StateData) -> + case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of + gen_tcp -> + ?DEBUG("starttls", []), + Socket = StateData#state.socket, + TLSOpts1 = case + ejabberd_config:get_option( + {domain_certfile, StateData#state.server}, + fun iolist_to_binary/1) of + undefined -> StateData#state.tls_options; + CertFile -> + lists:keystore(certfile, 1, + StateData#state.tls_options, + {certfile, CertFile}) + end, + TLSOpts = case ejabberd_config:get_option( + {s2s_tls_compression, StateData#state.server}, + fun(true) -> true; + (false) -> false + end, false) of + true -> lists:delete(compression_none, TLSOpts1); + false -> [compression_none | TLSOpts1] + end, + TLSSocket = (StateData#state.sockmod):starttls( + Socket, TLSOpts, + fxml:element_to_binary(#starttls_proceed{})), + {next_state, wait_for_stream, + StateData#state{socket = TLSSocket, streamid = new_id(), + tls_enabled = true, tls_options = TLSOpts}}; + _ -> + Txt = <<"Unsupported TLS transport">>, + send_element(StateData, xmpp:serr_policy_violation(Txt, ?MYLANG)), + {stop, normal, StateData} + end; +wait_for_feature_request(#sasl_auth{mechanism = Mech}, + #state{tls_enabled = true} = StateData) -> + case Mech of + <<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> -> + AuthDomain = StateData#state.auth_domain, + AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>, AuthDomain), + if AllowRemoteHost -> + (StateData#state.sockmod):reset_stream(StateData#state.socket), + send_element(StateData, #sasl_success{}), + ?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)", + [AuthDomain, StateData#state.tls_enabled]), + change_shaper(StateData, <<"">>, jid:make(AuthDomain)), + {next_state, wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true}}; + true -> + send_element(StateData, #sasl_failure{}), + {stop, normal, StateData} + end; + _ -> + send_element(StateData, #sasl_failure{reason = 'invalid-mechanism'}), + {stop, normal, StateData} end; -wait_for_feature_request({xmlstreamend, _Name}, - StateData) -> - send_text(StateData, ?STREAM_TRAILER), +wait_for_feature_request({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; -wait_for_feature_request({xmlstreamerror, _}, - StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), +wait_for_feature_request({xmlstreamerror, _}, StateData) -> + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_feature_request(closed, StateData) -> - {stop, normal, StateData}. + {stop, normal, StateData}; +wait_for_feature_request(_Pkt, #state{tls_required = TLSRequired, + tls_enabled = TLSEnabled} = StateData) + when TLSRequired and not TLSEnabled -> + Txt = <<"Use of STARTTLS required">>, + send_element(StateData, xmpp:serr_policy_violation(Txt, ?MYLANG)), + {stop, normal, StateData}; +wait_for_feature_request(El, StateData) -> + stream_established({xmlstreamelement, El}, StateData). stream_established({xmlstreamelement, El}, StateData) -> cancel_timer(StateData#state.timer), Timer = erlang:start_timer(?S2STIMEOUT, self(), []), - case is_key_packet(El) of - {key, To, From, Id, Key} -> - ?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]), - LTo = jid:nameprep(To), - LFrom = jid:nameprep(From), - case {ejabberd_s2s:allow_host(LTo, LFrom), - lists:member(LTo, - ejabberd_router:dirty_get_all_domains())} - of - {true, true} -> - ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom), - ejabberd_s2s_out:start(LTo, LFrom, - {verify, self(), Key, - StateData#state.streamid}), - Conns = (?DICT):store({LFrom, LTo}, - wait_for_verification, - StateData#state.connections), - change_shaper(StateData, LTo, - jid:make(<<"">>, LFrom, <<"">>)), - {next_state, stream_established, - StateData#state{connections = Conns, timer = Timer}}; - {_, false} -> - send_text(StateData, ?HOST_UNKNOWN_ERR), - {stop, normal, StateData}; - {false, _} -> - send_text(StateData, ?INVALID_FROM_ERR), - {stop, normal, StateData} - end; - {verify, To, From, Id, Key} -> - ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]), - LTo = jid:nameprep(To), - LFrom = jid:nameprep(From), - Type = case ejabberd_s2s:make_key({LTo, LFrom}, Id) of - Key -> <<"valid">>; - _ -> <<"invalid">> - end, - send_element(StateData, - #xmlel{name = <<"db:verify">>, - attrs = - [{<<"from">>, To}, {<<"to">>, From}, - {<<"id">>, Id}, {<<"type">>, Type}], - children = []}), - {next_state, stream_established, - StateData#state{timer = Timer}}; - _ -> - NewEl = jlib:remove_attr(<<"xmlns">>, El), - #xmlel{name = Name, attrs = Attrs} = NewEl, - From_s = fxml:get_attr_s(<<"from">>, Attrs), - From = jid:from_string(From_s), - To_s = fxml:get_attr_s(<<"to">>, Attrs), - To = jid:from_string(To_s), - if (To /= error) and (From /= error) -> - LFrom = From#jid.lserver, - LTo = To#jid.lserver, - if StateData#state.authenticated -> - case LFrom == StateData#state.auth_domain andalso - lists:member(LTo, - ejabberd_router:dirty_get_all_domains()) - of - true -> - if (Name == <<"iq">>) or (Name == <<"message">>) - or (Name == <<"presence">>) -> - ejabberd_hooks:run(s2s_receive_packet, LTo, - [From, To, NewEl]), - ejabberd_router:route(From, To, NewEl); - true -> error - end; - false -> error - end; - true -> - case (?DICT):find({LFrom, LTo}, - StateData#state.connections) - of - {ok, established} -> - if (Name == <<"iq">>) or (Name == <<"message">>) - or (Name == <<"presence">>) -> - ejabberd_hooks:run(s2s_receive_packet, LTo, - [From, To, NewEl]), - ejabberd_router:route(From, To, NewEl); - true -> error - end; - _ -> error - end - end; - true -> error - end, - ejabberd_hooks:run(s2s_loop_debug, - [{xmlstreamelement, El}]), - {next_state, stream_established, - StateData#state{timer = Timer}} + decode_element(El, stream_established, StateData#state{timer = Timer}); +stream_established(#db_result{to = To, from = From, key = Key}, + StateData) -> + ?DEBUG("GET KEY: ~p", [{To, From, Key}]), + LTo = To#jid.lserver, + LFrom = From#jid.lserver, + case {ejabberd_s2s:allow_host(LTo, LFrom), + lists:member(LTo, ejabberd_router:dirty_get_all_domains())} of + {true, true} -> + ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom), + ejabberd_s2s_out:start(LTo, LFrom, + {verify, self(), Key, + StateData#state.streamid}), + Conns = (?DICT):store({LFrom, LTo}, + wait_for_verification, + StateData#state.connections), + change_shaper(StateData, LTo, jid:make(LFrom)), + {next_state, stream_established, + StateData#state{connections = Conns}}; + {_, false} -> + send_element(StateData, xmpp:serr_host_unknown()), + {stop, normal, StateData}; + {false, _} -> + send_element(StateData, xmpp:serr_invalid_from()), + {stop, normal, StateData} end; +stream_established(#db_verify{to = To, from = From, id = Id, key = Key}, + StateData) -> + ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]), + LTo = jid:nameprep(To), + LFrom = jid:nameprep(From), + Type = case ejabberd_s2s:make_key({LTo, LFrom}, Id) of + Key -> valid; + _ -> invalid + end, + send_element(StateData, + #db_verify{from = To, to = From, id = Id, type = Type}), + {next_state, stream_established, StateData}; +stream_established(Pkt, StateData) when ?is_stanza(Pkt) -> + From = xmpp:get_from(Pkt), + To = xmpp:get_to(Pkt), + if To /= undefined, From /= undefined -> + LFrom = From#jid.lserver, + LTo = To#jid.lserver, + if StateData#state.authenticated -> + case LFrom == StateData#state.auth_domain andalso + lists:member(LTo, ejabberd_router:dirty_get_all_domains()) of + true -> + ejabberd_hooks:run(s2s_receive_packet, LTo, + [From, To, Pkt]), + ejabberd_router:route(From, To, Pkt); + false -> + send_error(StateData, Pkt, xmpp:err_not_authorized()) + end; + true -> + case (?DICT):find({LFrom, LTo}, StateData#state.connections) of + {ok, established} -> + ejabberd_hooks:run(s2s_receive_packet, LTo, + [From, To, Pkt]), + ejabberd_router:route(From, To, Pkt); + _ -> + send_error(StateData, Pkt, xmpp:err_not_authorized()) + end + end; + true -> + send_error(StateData, Pkt, xmpp:err_jid_malformed()) + end, + ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]), + {next_state, stream_established, StateData}; stream_established({valid, From, To}, StateData) -> send_element(StateData, - #xmlel{name = <<"db:result">>, - attrs = - [{<<"from">>, To}, {<<"to">>, From}, - {<<"type">>, <<"valid">>}], - children = []}), + #db_result{from = To, to = From, type = valid}), ?INFO_MSG("Accepted s2s dialback authentication for ~s (TLS=~p)", [From, StateData#state.tls_enabled]), LFrom = jid:nameprep(From), @@ -508,11 +420,7 @@ stream_established({valid, From, To}, StateData) -> {next_state, stream_established, NSD}; stream_established({invalid, From, To}, StateData) -> send_element(StateData, - #xmlel{name = <<"db:result">>, - attrs = - [{<<"from">>, To}, {<<"to">>, From}, - {<<"type">>, <<"invalid">>}], - children = []}), + #db_result{from = To, to = From, type = invalid}), LFrom = jid:nameprep(From), LTo = jid:nameprep(To), NSD = StateData#state{connections = @@ -522,14 +430,16 @@ stream_established({invalid, From, To}, StateData) -> stream_established({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; stream_established({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; stream_established(timeout, StateData) -> + send_element(StateData, xmpp:serr_connection_timeout()), {stop, normal, StateData}; stream_established(closed, StateData) -> - {stop, normal, StateData}. + {stop, normal, StateData}; +stream_established(Pkt, StateData) -> + ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]), + {next_state, stream_established, StateData}. %%---------------------------------------------------------------------- %% Func: StateName/3 @@ -589,8 +499,14 @@ code_change(_OldVsn, StateName, StateData, _Extra) -> handle_info({send_text, Text}, StateName, StateData) -> send_text(StateData, Text), {next_state, StateName, StateData}; -handle_info({timeout, Timer, _}, _StateName, +handle_info({timeout, Timer, _}, StateName, #state{timer = Timer} = StateData) -> + if StateName == wait_for_stream -> + send_header(StateData, <<"">>); + true -> + ok + end, + send_element(StateData, xmpp:serr_connection_timeout()), {stop, normal, StateData}; handle_info(_, StateName, StateData) -> {next_state, StateName, StateData}. @@ -603,6 +519,7 @@ terminate(Reason, _StateName, StateData) -> || Host <- get_external_hosts(StateData)]; _ -> ok end, + catch send_trailer(StateData), (StateData#state.sockmod):close(StateData#state.socket), ok. @@ -621,39 +538,69 @@ print_state(State) -> State. %%% Internal functions %%%---------------------------------------------------------------------- +-spec send_text(state(), iodata()) -> ok. send_text(StateData, Text) -> (StateData#state.sockmod):send(StateData#state.socket, Text). +-spec send_element(state(), xmpp_element()) -> ok. send_element(StateData, El) -> - send_text(StateData, fxml:element_to_binary(El)). + El1 = fix_ns(xmpp:encode(El)), + send_text(StateData, fxml:element_to_binary(El1)). + +-spec send_error(state(), xmlel() | stanza(), error()) -> ok. +send_error(StateData, Stanza, Error) -> + Type = xmpp:get_type(Stanza), + if Type == error; Type == result; + Type == <<"error">>; Type == <<"result">> -> + ok; + true -> + send_element(StateData, xmpp:make_error(Stanza, Error)) + end. + +-spec send_trailer(state()) -> ok. +send_trailer(StateData) -> + send_text(StateData, <<"</stream:stream>">>). +-spec send_header(state(), binary()) -> ok. +send_header(StateData, Version) -> + send_text(StateData, + <<"<?xml version='1.0'?><stream:stream " + "xmlns:stream='http://etherx.jabber.org/stream" + "s' xmlns='jabber:server' xmlns:db='jabber:ser" + "ver:dialback' id='", + (StateData#state.streamid)/binary, "'", Version/binary, + ">">>). + +-spec change_shaper(state(), binary(), jid()) -> ok. change_shaper(StateData, Host, JID) -> Shaper = acl:match_rule(Host, StateData#state.shaper, JID), (StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper). +-spec fix_ns(xmlel()) -> xmlel(). +fix_ns(#xmlel{name = Name} = El) when Name == <<"message">>; + Name == <<"iq">>; + Name == <<"presence">>; + Name == <<"db:verify">>, + Name == <<"db:result">> -> + Attrs = lists:filter( + fun({<<"xmlns">>, _}) -> false; + (_) -> true + end, El#xmlel.attrs), + El#xmlel{attrs = Attrs}; +fix_ns(El) -> + El. + +-spec new_id() -> binary(). new_id() -> randoms:get_string(). +-spec cancel_timer(reference()) -> ok. cancel_timer(Timer) -> erlang:cancel_timer(Timer), receive {timeout, Timer, _} -> ok after 0 -> ok end. -is_key_packet(#xmlel{name = Name, attrs = Attrs, - children = Els}) - when Name == <<"db:result">> -> - {key, fxml:get_attr_s(<<"to">>, Attrs), - fxml:get_attr_s(<<"from">>, Attrs), - fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)}; -is_key_packet(#xmlel{name = Name, attrs = Attrs, - children = Els}) - when Name == <<"db:verify">> -> - {verify, fxml:get_attr_s(<<"to">>, Attrs), - fxml:get_attr_s(<<"from">>, Attrs), - fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)}; -is_key_packet(_) -> false. - fsm_limit_opts(Opts) -> case lists:keysearch(max_fsm_queue, 1, Opts) of {value, {_, N}} when is_integer(N) -> [{max_queue, N}]; @@ -666,6 +613,22 @@ fsm_limit_opts(Opts) -> end end. +-spec decode_element(xmlel(), state_name(), state()) -> fsm_transition(). +decode_element(#xmlel{} = El, StateName, StateData) -> + try xmpp:decode(El) of + Pkt -> ?MODULE:StateName(Pkt, StateData) + catch error:{xmpp_codec, Why} -> + case xmpp:is_stanza(El) of + true -> + Lang = xmpp:get_lang(El), + Txt = xmpp:format_error(Why), + send_error(StateData, El, xmpp:err_bad_request(Txt, Lang)); + false -> + ok + end, + {next_state, StateName, StateData} + end. + opt_type(domain_certfile) -> fun iolist_to_binary/1; opt_type(max_fsm_queue) -> fun (I) when is_integer(I), I > 0 -> I end; diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index a30f2f438..024e51e7a 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -50,8 +50,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). +-include("xmpp.hrl"). -record(state, {socket :: ejabberd_socket:socket_state(), @@ -75,6 +74,17 @@ bridge :: {atom(), atom()}, timer = make_ref() :: reference()}). +-type state_name() :: open_socket | wait_for_stream | + wait_for_validation | wait_for_features | + wait_for_auth_result | wait_for_starttls_proceed | + relay_to_bridge | reopen_socket | wait_before_retry | + stream_established. +-type state() :: #state{}. +-type fsm_stop() :: {stop, normal, state()}. +-type fsm_next() :: {next_state, state_name(), state(), non_neg_integer()} | + {next_state, state_name(), state()}. +-type fsm_transition() :: fsm_stop() | fsm_next(). + %%-define(DBGFSM, true). -ifdef(DBGFSM). @@ -102,17 +112,6 @@ "s' xmlns='jabber:server' xmlns:db='jabber:ser" "ver:dialback' from='~s' to='~s'~s>">>). --define(STREAM_TRAILER, <<"</stream:stream>">>). - --define(INVALID_NAMESPACE_ERR, - fxml:element_to_binary(?SERR_INVALID_NAMESPACE)). - --define(HOST_UNKNOWN_ERR, - fxml:element_to_binary(?SERR_HOST_UNKNOWN)). - --define(INVALID_XML_ERR, - fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)). - -define(SOCKET_DEFAULT_RESULT, {error, badarg}). %%%---------------------------------------------------------------------- @@ -236,10 +235,7 @@ open_socket(init, StateData) -> NewStateData = StateData#state{socket = Socket, tls_enabled = false, streamid = new_id()}, - send_text(NewStateData, - io_lib:format(?STREAM_HEADER, - [StateData#state.myname, - StateData#state.server, Version])), + send_header(NewStateData, Version), {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT}; {error, _Reason} -> @@ -259,18 +255,8 @@ open_socket(init, StateData) -> _ -> wait_before_reconnect(StateData) end end; -open_socket(closed, StateData) -> - ?INFO_MSG("s2s connection: ~s -> ~s (stopped in " - "open socket)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -open_socket(timeout, StateData) -> - ?INFO_MSG("s2s connection: ~s -> ~s (timeout in " - "open socket)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -open_socket(_, StateData) -> - {next_state, open_socket, StateData}. +open_socket(Event, StateData) -> + handle_unexpected_event(Event, open_socket, StateData). open_socket1({_, _, _, _} = Addr, Port) -> open_socket2(inet, Addr, Port); @@ -309,466 +295,215 @@ open_socket2(Type, Addr, Port) -> %%---------------------------------------------------------------------- -wait_for_stream({xmlstreamstart, _Name, Attrs}, - StateData) -> - {CertCheckRes, CertCheckMsg, StateData0} = - if StateData#state.tls_certverify, StateData#state.tls_enabled -> - {Res, Msg} = - ejabberd_s2s:check_peer_certificate(ejabberd_socket, - StateData#state.socket, - StateData#state.server), - ?DEBUG("Certificate verification result for ~s: ~s", - [StateData#state.server, Msg]), - {Res, Msg, StateData#state{tls_certverify = false}}; +wait_for_stream({xmlstreamstart, Name, Attrs}, StateData0) -> + {CertCheckRes, CertCheckMsg, StateData} = + if StateData0#state.tls_certverify, StateData0#state.tls_enabled -> + {Res, Msg} = + ejabberd_s2s:check_peer_certificate(ejabberd_socket, + StateData0#state.socket, + StateData0#state.server), + ?DEBUG("Certificate verification result for ~s: ~s", + [StateData0#state.server, Msg]), + {Res, Msg, StateData0#state{tls_certverify = false}}; true -> - {no_verify, <<"Not verified">>, StateData} + {no_verify, <<"Not verified">>, StateData0} end, - RemoteStreamID = fxml:get_attr_s(<<"id">>, Attrs), - NewStateData = StateData0#state{remote_streamid = RemoteStreamID}, - case {fxml:get_attr_s(<<"xmlns">>, Attrs), - fxml:get_attr_s(<<"xmlns:db">>, Attrs), - fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>} - of - _ when CertCheckRes == error -> - send_text(NewStateData, - <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>, - CertCheckMsg)))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)", - [NewStateData#state.myname, - NewStateData#state.server, - CertCheckMsg]), - {stop, normal, NewStateData}; - {<<"jabber:server">>, <<"jabber:server:dialback">>, - false} -> - send_db_request(NewStateData); - {<<"jabber:server">>, <<"jabber:server:dialback">>, - true} - when NewStateData#state.use_v10 -> - {next_state, wait_for_features, NewStateData, ?FSMTIMEOUT}; - %% Clause added to handle Tigase's workaround for an old ejabberd bug: - {<<"jabber:server">>, <<"jabber:server:dialback">>, - true} - when not NewStateData#state.use_v10 -> - send_db_request(NewStateData); - {<<"jabber:server">>, <<"">>, true} - when NewStateData#state.use_v10 -> - {next_state, wait_for_features, - NewStateData#state{db_enabled = false}, ?FSMTIMEOUT}; - {NSProvided, DB, _} -> - send_text(NewStateData, ?INVALID_NAMESPACE_ERR), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " - "namespace).~nNamespace provided: ~p~nNamespac" - "e expected: \"jabber:server\"~nxmlns:db " - "provided: ~p~nAll attributes: ~p", - [NewStateData#state.myname, NewStateData#state.server, - NSProvided, DB, Attrs]), - {stop, normal, NewStateData} + try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of + _ when CertCheckRes == error -> + send_element(StateData, + xmpp:serr_policy_violation(CertCheckMsg, ?MYLANG)), + ?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)", + [StateData#state.myname, StateData#state.server, + CertCheckMsg]), + {stop, normal, StateData}; + #stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM} + when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM -> + send_header(StateData, <<" version='1.0'">>), + send_element(StateData, xmpp:serr_invalid_namespace()), + {stop, normal, StateData}; + #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID, + version = V} when V /= <<"1.0">> -> + send_db_request(StateData#state{remote_streamid = ID}); + #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID} + when StateData#state.use_v10 -> + {next_state, wait_for_features, + StateData#state{remote_streamid = ID}, ?FSMTIMEOUT}; + #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID} + when not StateData#state.use_v10 -> + %% Handle Tigase's workaround for an old ejabberd bug: + send_db_request(StateData#state{remote_streamid = ID}); + #stream_start{id = ID} when StateData#state.use_v10 -> + {next_state, wait_for_features, + StateData#state{db_enabled = false, remote_streamid = ID}, + ?FSMTIMEOUT}; + #stream_start{} -> + send_header(StateData, <<"">>), + send_element(StateData, xmpp:serr_invalid_namespace()), + {stop, normal, StateData} + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + send_header(StateData, <<" version='1.0'">>), + send_element(StateData, xmpp:serr_not_well_formed(Txt, ?MYLANG)), + {stop, normal, StateData} end; -wait_for_stream({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " - "xml)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -wait_for_stream({xmlstreamend, _Name}, StateData) -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (xmlstreamend)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -wait_for_stream(timeout, StateData) -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (timeout " - "in wait_for_stream)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -wait_for_stream(closed, StateData) -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (close " - "in wait_for_stream)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}. - -wait_for_validation({xmlstreamelement, El}, +wait_for_stream(Event, StateData) -> + handle_unexpected_event(Event, wait_for_stream, StateData). + +wait_for_validation({xmlstreamelement, El}, StateData) -> + decode_element(El, wait_for_validation, StateData); +wait_for_validation(#db_result{to = To, from = From, type = Type}, StateData) -> + ?DEBUG("recv result: ~p", [{From, To, Type}]), + case {Type, StateData#state.tls_enabled, StateData#state.tls_required} of + {valid, Enabled, Required} when (Enabled == true) or (Required == false) -> + send_queue(StateData, StateData#state.queue), + ?INFO_MSG("Connection established: ~s -> ~s with " + "TLS=~p", + [StateData#state.myname, StateData#state.server, + StateData#state.tls_enabled]), + ejabberd_hooks:run(s2s_connect_hook, + [StateData#state.myname, + StateData#state.server]), + {next_state, stream_established, StateData#state{queue = queue:new()}}; + {valid, Enabled, Required} when (Enabled == false) and (Required == true) -> + ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS " + "is required but unavailable)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData}; + _ -> + ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " + "dialback key result)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData} + end; +wait_for_validation(#db_verify{to = To, from = From, id = Id, type = Type}, StateData) -> - case is_verify_res(El) of - {result, To, From, Id, Type} -> - ?DEBUG("recv result: ~p", [{From, To, Id, Type}]), - case {Type, StateData#state.tls_enabled, - StateData#state.tls_required} - of - {<<"valid">>, Enabled, Required} - when (Enabled == true) or (Required == false) -> - send_queue(StateData, StateData#state.queue), - ?INFO_MSG("Connection established: ~s -> ~s with " - "TLS=~p", - [StateData#state.myname, StateData#state.server, - StateData#state.tls_enabled]), - ejabberd_hooks:run(s2s_connect_hook, - [StateData#state.myname, - StateData#state.server]), - {next_state, stream_established, - StateData#state{queue = queue:new()}}; - {<<"valid">>, Enabled, Required} - when (Enabled == false) and (Required == true) -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS " - "is required but unavailable)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; - _ -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " - "dialback key)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - {verify, To, From, Id, Type} -> - ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]), - case StateData#state.verify of - false -> - NextState = wait_for_validation, - {next_state, NextState, StateData, - get_timeout_interval(NextState)}; - {Pid, _Key, _SID} -> - case Type of - <<"valid">> -> - p1_fsm:send_event(Pid, - {valid, StateData#state.server, - StateData#state.myname}); - _ -> - p1_fsm:send_event(Pid, - {invalid, StateData#state.server, - StateData#state.myname}) - end, - if StateData#state.verify == false -> - {stop, normal, StateData}; - true -> - NextState = wait_for_validation, - {next_state, NextState, StateData, - get_timeout_interval(NextState)} - end - end; - _ -> - {next_state, wait_for_validation, StateData, - (?FSMTIMEOUT) * 3} + ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]), + case StateData#state.verify of + false -> + NextState = wait_for_validation, + {next_state, NextState, StateData, get_timeout_interval(NextState)}; + {Pid, _Key, _SID} -> + case Type of + valid -> + p1_fsm:send_event(Pid, + {valid, StateData#state.server, + StateData#state.myname}); + _ -> + p1_fsm:send_event(Pid, + {invalid, StateData#state.server, + StateData#state.myname}) + end, + if StateData#state.verify == false -> + {stop, normal, StateData}; + true -> + NextState = wait_for_validation, + {next_state, NextState, StateData, get_timeout_interval(NextState)} + end end; -wait_for_validation({xmlstreamend, _Name}, StateData) -> - ?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamend)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, 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)/binary, - (?STREAM_TRAILER)/binary>>), - {stop, normal, StateData}; wait_for_validation(timeout, #state{verify = {VPid, VKey, SID}} = StateData) - when is_pid(VPid) and is_binary(VKey) and - is_binary(SID) -> - ?DEBUG("wait_for_validation: ~s -> ~s (timeout " - "in verify connection)", + when is_pid(VPid) and is_binary(VKey) and is_binary(SID) -> + ?DEBUG("wait_for_validation: ~s -> ~s (timeout in verify connection)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; -wait_for_validation(timeout, StateData) -> - ?INFO_MSG("wait_for_validation: ~s -> ~s (connect " - "timeout)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -wait_for_validation(closed, StateData) -> - ?INFO_MSG("wait for validation: ~s -> ~s (closed)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}. +wait_for_validation(Event, StateData) -> + handle_unexpected_event(Event, wait_for_validation, StateData). wait_for_features({xmlstreamelement, El}, StateData) -> - case El of - #xmlel{name = <<"stream:features">>, children = Els} -> - {SASLEXT, StartTLS, StartTLSRequired} = lists:foldl(fun - (#xmlel{name = - <<"mechanisms">>, - attrs = - Attrs1, - children - = - Els1} = - _El1, - {_SEXT, STLS, - STLSReq} = - Acc) -> - case - fxml:get_attr_s(<<"xmlns">>, - Attrs1) - of - ?NS_SASL -> - NewSEXT = - lists:any(fun - (#xmlel{name - = - <<"mechanism">>, - children - = - Els2}) -> - case - fxml:get_cdata(Els2) - of - <<"EXTERNAL">> -> - true; - _ -> - false - end; - (_) -> - false - end, - Els1), - {NewSEXT, - STLS, - STLSReq}; - _ -> Acc - end; - (#xmlel{name = - <<"starttls">>, - attrs = - Attrs1} = - El1, - {SEXT, _STLS, - _STLSReq} = - Acc) -> - case - fxml:get_attr_s(<<"xmlns">>, - Attrs1) - of - ?NS_TLS -> - Req = - case - fxml:get_subtag(El1, - <<"required">>) - of - #xmlel{} -> - true; - false -> - false - end, - {SEXT, - true, - Req}; - _ -> Acc - end; - (_, Acc) -> Acc - end, - {false, false, - false}, - Els), - if not SASLEXT and not StartTLS and - StateData#state.authenticated -> - send_queue(StateData, StateData#state.queue), - ?INFO_MSG("Connection established: ~s -> ~s with " - "SASL EXTERNAL and TLS=~p", - [StateData#state.myname, StateData#state.server, - StateData#state.tls_enabled]), - ejabberd_hooks:run(s2s_connect_hook, - [StateData#state.myname, - StateData#state.server]), - {next_state, stream_established, - StateData#state{queue = queue:new()}}; - SASLEXT and StateData#state.try_auth and - (StateData#state.new /= false) and - (StateData#state.tls_enabled or - not StateData#state.tls_required) -> - send_element(StateData, - #xmlel{name = <<"auth">>, - attrs = - [{<<"xmlns">>, ?NS_SASL}, - {<<"mechanism">>, <<"EXTERNAL">>}], - children = - [{xmlcdata, - jlib:encode_base64(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, - #xmlel{name = <<"starttls">>, - attrs = [{<<"xmlns">>, ?NS_TLS}], - children = []}), - {next_state, wait_for_starttls_proceed, StateData, - ?FSMTIMEOUT}; - StartTLSRequired and not StateData#state.tls -> - ?DEBUG("restarted: ~p", - [{StateData#state.myname, StateData#state.server}]), - ejabberd_socket:close(StateData#state.socket), - {next_state, reopen_socket, - StateData#state{socket = undefined, use_v10 = false}, - ?FSMTIMEOUT}; - StateData#state.db_enabled -> - send_db_request(StateData); - true -> - ?DEBUG("restarted: ~p", - [{StateData#state.myname, StateData#state.server}]), - ejabberd_socket:close(StateData#state.socket), - {next_state, reopen_socket, - StateData#state{socket = undefined, use_v10 = false}, - ?FSMTIMEOUT} - end; - _ -> - send_text(StateData, - <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} + decode_element(El, wait_for_features, StateData); +wait_for_features(#stream_features{sub_els = Els}, StateData) -> + {SASLEXT, StartTLS, StartTLSRequired} = + lists:foldl( + fun(#sasl_mechanisms{list = Mechs}, {_, STLS, STLSReq}) -> + {lists:member(<<"EXTERNAL">>, Mechs), STLS, STLSReq}; + (#starttls{required = Required}, {SEXT, _, _}) -> + {SEXT, true, Required}; + (_, Acc) -> + Acc + end, {false, false, false}, Els), + if not SASLEXT and not StartTLS and StateData#state.authenticated -> + send_queue(StateData, StateData#state.queue), + ?INFO_MSG("Connection established: ~s -> ~s with " + "SASL EXTERNAL and TLS=~p", + [StateData#state.myname, StateData#state.server, + StateData#state.tls_enabled]), + ejabberd_hooks:run(s2s_connect_hook, + [StateData#state.myname, + StateData#state.server]), + {next_state, stream_established, + StateData#state{queue = queue:new()}}; + SASLEXT and StateData#state.try_auth and + (StateData#state.new /= false) and + (StateData#state.tls_enabled or + not StateData#state.tls_required) -> + send_element(StateData, + #sasl_auth{mechanism = <<"EXTERNAL">>, + text = 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, #starttls{}), + {next_state, wait_for_starttls_proceed, StateData, ?FSMTIMEOUT}; + StartTLSRequired and not StateData#state.tls -> + ?DEBUG("restarted: ~p", + [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:close(StateData#state.socket), + {next_state, reopen_socket, + StateData#state{socket = undefined, use_v10 = false}, + ?FSMTIMEOUT}; + StateData#state.db_enabled -> + send_db_request(StateData); + true -> + ?DEBUG("restarted: ~p", + [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:close(StateData#state.socket), + {next_state, reopen_socket, + StateData#state{socket = undefined, use_v10 = false}, ?FSMTIMEOUT} end; -wait_for_features({xmlstreamend, _Name}, StateData) -> - ?INFO_MSG("wait_for_features: xmlstreamend", []), - {stop, normal, StateData}; -wait_for_features({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("wait for features: xmlstreamerror", []), - {stop, normal, StateData}; -wait_for_features(timeout, StateData) -> - ?INFO_MSG("wait for features: timeout", []), - {stop, normal, StateData}; -wait_for_features(closed, StateData) -> - ?INFO_MSG("wait for features: closed", []), - {stop, normal, StateData}. - -wait_for_auth_result({xmlstreamelement, El}, - StateData) -> - case El of - #xmlel{name = <<"success">>, attrs = Attrs} -> - case fxml: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.myname, - StateData#state.server, - <<" version='1.0'">>])), - {next_state, wait_for_stream, - StateData#state{streamid = new_id(), - authenticated = true}, - ?FSMTIMEOUT}; - _ -> - send_text(StateData, - <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - #xmlel{name = <<"failure">>, attrs = Attrs} -> - case fxml: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, - <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - _ -> - send_text(StateData, - <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; -wait_for_auth_result({xmlstreamend, _Name}, - StateData) -> - ?INFO_MSG("wait for auth result: xmlstreamend", []), - {stop, normal, StateData}; -wait_for_auth_result({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("wait for auth result: xmlstreamerror", []), - {stop, normal, StateData}; -wait_for_auth_result(timeout, StateData) -> - ?INFO_MSG("wait for auth result: timeout", []), - {stop, normal, StateData}; -wait_for_auth_result(closed, StateData) -> - ?INFO_MSG("wait for auth result: closed", []), - {stop, normal, StateData}. - -wait_for_starttls_proceed({xmlstreamelement, El}, - StateData) -> - case El of - #xmlel{name = <<"proceed">>, attrs = Attrs} -> - case fxml: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_option( - {domain_certfile, StateData#state.myname}, - fun iolist_to_binary/1) - 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, - tls_options = TLSOpts}, - send_text(NewStateData, - io_lib:format(?STREAM_HEADER, - [NewStateData#state.myname, - NewStateData#state.server, - <<" version='1.0'">>])), - {next_state, wait_for_stream, NewStateData, - ?FSMTIMEOUT}; - _ -> - send_text(StateData, - <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - _ -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; -wait_for_starttls_proceed({xmlstreamend, _Name}, - StateData) -> - ?INFO_MSG("wait for starttls proceed: xmlstreamend", - []), - {stop, normal, StateData}; -wait_for_starttls_proceed({xmlstreamerror, _}, - StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("wait for starttls proceed: xmlstreamerror", - []), - {stop, normal, StateData}; -wait_for_starttls_proceed(timeout, StateData) -> - ?INFO_MSG("wait for starttls proceed: timeout", []), - {stop, normal, StateData}; -wait_for_starttls_proceed(closed, StateData) -> - ?INFO_MSG("wait for starttls proceed: closed", []), - {stop, normal, StateData}. +wait_for_features(Event, StateData) -> + handle_unexpected_event(Event, wait_for_features, StateData). + +wait_for_auth_result({xmlstreamelement, El}, StateData) -> + decode_element(El, wait_for_auth_result, StateData); +wait_for_auth_result(#sasl_success{}, StateData) -> + ?DEBUG("auth: ~p", [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:reset_stream(StateData#state.socket), + send_header(StateData, <<" version='1.0'">>), + {next_state, wait_for_stream, + StateData#state{streamid = new_id(), authenticated = true}, + ?FSMTIMEOUT}; +wait_for_auth_result(#sasl_failure{}, StateData) -> + ?DEBUG("restarted: ~p", [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:close(StateData#state.socket), + {next_state, reopen_socket, + StateData#state{socket = undefined}, ?FSMTIMEOUT}; +wait_for_auth_result(Event, StateData) -> + handle_unexpected_event(Event, wait_for_auth_result, StateData). + +wait_for_starttls_proceed({xmlstreamelement, El}, StateData) -> + decode_element(El, wait_for_starttls_proceed, StateData); +wait_for_starttls_proceed(#starttls_proceed{}, StateData) -> + ?DEBUG("starttls: ~p", [{StateData#state.myname, StateData#state.server}]), + Socket = StateData#state.socket, + TLSOpts = case ejabberd_config:get_option( + {domain_certfile, StateData#state.myname}, + fun iolist_to_binary/1) 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, + tls_options = TLSOpts}, + send_header(NewStateData, <<" version='1.0'">>), + {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT}; +wait_for_starttls_proceed(Event, StateData) -> + handle_unexpected_event(Event, wait_for_starttls_proceed, StateData). reopen_socket({xmlstreamelement, _El}, StateData) -> {next_state, reopen_socket, StateData, ?FSMTIMEOUT}; @@ -797,47 +532,69 @@ relay_to_bridge(_Event, StateData) -> {next_state, relay_to_bridge, StateData}. stream_established({xmlstreamelement, El}, StateData) -> - ?DEBUG("s2S stream established", []), - case is_verify_res(El) of - {verify, VTo, VFrom, VId, VType} -> - ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]), - case StateData#state.verify of - {VPid, _VKey, _SID} -> - case VType of - <<"valid">> -> - p1_fsm:send_event(VPid, - {valid, StateData#state.server, - StateData#state.myname}); - _ -> - p1_fsm:send_event(VPid, - {invalid, StateData#state.server, - StateData#state.myname}) - end; - _ -> ok - end; - _ -> ok + decode_element(El, stream_established, StateData); +stream_established(#db_verify{to = VTo, from = VFrom, id = VId, type = VType}, + StateData) -> + ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]), + case StateData#state.verify of + {VPid, _VKey, _SID} -> + case VType of + valid -> + p1_fsm:send_event(VPid, + {valid, StateData#state.server, + StateData#state.myname}); + _ -> + p1_fsm:send_event(VPid, + {invalid, StateData#state.server, + StateData#state.myname}) + end; + _ -> ok end, {next_state, stream_established, StateData}; -stream_established({xmlstreamend, _Name}, StateData) -> - ?INFO_MSG("Connection closed in stream established: " - "~s -> ~s (xmlstreamend)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -stream_established({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("stream established: ~s -> ~s (xmlstreamerror)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -stream_established(timeout, StateData) -> - ?INFO_MSG("stream established: ~s -> ~s (timeout)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -stream_established(closed, StateData) -> - ?INFO_MSG("stream established: ~s -> ~s (closed)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}. +stream_established(Event, StateData) -> + handle_unexpected_event(Event, stream_established, StateData). + +-spec handle_unexpected_event(term(), state_name(), state()) -> fsm_transition(). +handle_unexpected_event(Event, StateName, StateData) -> + case Event of + {xmlstreamerror, _} -> + send_element(StateData, xmpp:serr_not_well_formed()), + ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: " + "got invalid XML from peer", + [StateData#state.myname, StateData#state.server, + StateName]), + {stop, normal, StateData}; + {xmlstreamend, _} -> + ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: " + "XML stream closed by peer", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData}; + timeout -> + send_element(StateData, xmpp:serr_connection_timeout()), + ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: " + "timed out during establishing an XML stream", + [StateData#state.myname, StateData#state.server, + StateName]), + {stop, normal, StateData}; + closed -> + ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: " + "connection socket closed", + [StateData#state.myname, StateData#state.server, + StateName]), + {stop, normal, StateData}; + Pkt when StateName == wait_for_stream; + StateName == wait_for_features; + StateName == wait_for_auth_result; + StateName == wait_for_starttls_proceed -> + send_element(StateData, xmpp:serr_bad_format()), + ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: " + "got unexpected event ~p", + [StateData#state.myname, StateData#state.server, + StateName, Pkt]), + {stop, normal, StateData}; + _ -> + {next_state, StateName, StateData, get_timeout_interval(StateName)} + end. %%---------------------------------------------------------------------- %% Func: StateName/3 @@ -917,7 +674,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, xmpp:err_remote_server_not_found()), {next_state, StateName, StateData}; relay_to_bridge -> {Mod, Fun} = StateData#state.bridge, @@ -926,7 +683,7 @@ handle_info({send_element, El}, StateName, StateData) -> {'EXIT', Reason} -> ?ERROR_MSG("Error while relaying to bridge: ~p", [Reason]), - bounce_element(El, ?ERR_INTERNAL_SERVER_ERROR), + bounce_element(El, xmpp:err_internal_server_error()), wait_before_reconnect(StateData); _ -> {next_state, StateName, StateData} end; @@ -966,12 +723,13 @@ terminate(Reason, StateName, StateData) -> StateData#state.server}, self()) end, - bounce_queue(StateData#state.queue, - ?ERR_REMOTE_SERVER_NOT_FOUND), - bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND), + bounce_queue(StateData#state.queue, xmpp:err_remote_server_not_found()), + bounce_messages(xmpp:err_remote_server_not_found()), case StateData#state.socket of undefined -> ok; - _Socket -> ejabberd_socket:close(StateData#state.socket) + _Socket -> + catch send_trailer(StateData), + ejabberd_socket:close(StateData#state.socket) end, ok. @@ -981,12 +739,30 @@ print_state(State) -> State. %%% Internal functions %%%---------------------------------------------------------------------- +-spec send_text(state(), iodata()) -> ok. send_text(StateData, Text) -> ejabberd_socket:send(StateData#state.socket, Text). +-spec send_element(state(), xmpp_element()) -> ok. send_element(StateData, El) -> - send_text(StateData, fxml:element_to_binary(El)). - + El1 = fix_ns(xmpp:encode(El)), + send_text(StateData, fxml:element_to_binary(El1)). + +-spec send_header(state(), binary()) -> ok. +send_header(StateData, Version) -> + Txt = io_lib:format( + "<?xml version='1.0'?><stream:stream " + "xmlns:stream='http://etherx.jabber.org/stream" + "s' xmlns='jabber:server' xmlns:db='jabber:ser" + "ver:dialback' from='~s' to='~s'~s>", + [StateData#state.myname, StateData#state.server, Version]), + send_text(StateData, Txt). + +-spec send_trailer(state()) -> ok. +send_trailer(StateData) -> + send_text(StateData, <<"</stream:stream>">>). + +-spec send_queue(state(), queue:queue()) -> ok. send_queue(StateData, Q) -> case queue:out(Q) of {{value, El}, Q1} -> @@ -994,21 +770,28 @@ send_queue(StateData, Q) -> {empty, _Q1} -> ok end. +-spec fix_ns(xmlel()) -> xmlel(). +fix_ns(#xmlel{name = Name} = El) when Name == <<"message">>; + Name == <<"iq">>; + Name == <<"presence">>; + Name == <<"db:verify">>, + Name == <<"db:result">> -> + Attrs = lists:filter( + fun({<<"xmlns">>, _}) -> false; + (_) -> true + end, El#xmlel.attrs), + El#xmlel{attrs = Attrs}; +fix_ns(El) -> + El. + %% Bounce a single message (xmlelement) +-spec bounce_element(stanza(), error()) -> ok. bounce_element(El, Error) -> - #xmlel{attrs = Attrs} = El, - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - <<"result">> -> ok; - _ -> - Err = jlib:make_error_reply(El, Error), - From = jid:from_string(fxml:get_tag_attr_s(<<"from">>, - El)), - To = jid:from_string(fxml:get_tag_attr_s(<<"to">>, - El)), - ejabberd_router:route(To, From, Err) - end. + From = xmpp:get_from(El), + To = xmpp:get_to(El), + ejabberd_router:route_error(To, From, El, Error). +-spec bounce_queue(queue:queue(), error()) -> ok. bounce_queue(Q, Error) -> case queue:out(Q) of {{value, El}, Q1} -> @@ -1016,12 +799,15 @@ bounce_queue(Q, Error) -> {empty, _} -> ok end. +-spec new_id() -> binary(). new_id() -> randoms:get_string(). +-spec cancel_timer(reference()) -> ok. cancel_timer(Timer) -> erlang:cancel_timer(Timer), receive {timeout, Timer, _} -> ok after 0 -> ok end. +-spec bounce_messages(error()) -> ok. bounce_messages(Error) -> receive {send_element, El} -> @@ -1029,6 +815,7 @@ bounce_messages(Error) -> after 0 -> ok end. +-spec send_db_request(state()) -> fsm_transition(). send_db_request(StateData) -> Server = StateData#state.server, New = case StateData#state.new of @@ -1045,22 +832,18 @@ send_db_request(StateData) -> {StateData#state.myname, Server}, StateData#state.remote_streamid), send_element(StateData, - #xmlel{name = <<"db:result">>, - attrs = - [{<<"from">>, StateData#state.myname}, - {<<"to">>, Server}], - children = [{xmlcdata, Key1}]}) + #db_result{from = jid:make(StateData#state.myname), + to = jid:make(Server), + key = Key1}) end, case StateData#state.verify of false -> ok; {_Pid, Key2, SID} -> send_element(StateData, - #xmlel{name = <<"db:verify">>, - attrs = - [{<<"from">>, StateData#state.myname}, - {<<"to">>, StateData#state.server}, - {<<"id">>, SID}], - children = [{xmlcdata, Key2}]}) + #db_verify{from = jid:make(StateData#state.myname), + to = StateData#state.server, + id = SID, + key = Key2}) end, {next_state, wait_for_validation, NewStateData, (?FSMTIMEOUT) * 6} @@ -1068,20 +851,6 @@ send_db_request(StateData) -> _:_ -> {stop, normal, NewStateData} end. -is_verify_res(#xmlel{name = Name, attrs = Attrs}) - when Name == <<"db:result">> -> - {result, fxml:get_attr_s(<<"to">>, Attrs), - fxml:get_attr_s(<<"from">>, Attrs), - fxml:get_attr_s(<<"id">>, Attrs), - fxml:get_attr_s(<<"type">>, Attrs)}; -is_verify_res(#xmlel{name = Name, attrs = Attrs}) - when Name == <<"db:verify">> -> - {verify, fxml:get_attr_s(<<"to">>, Attrs), - fxml:get_attr_s(<<"from">>, Attrs), - fxml:get_attr_s(<<"id">>, Attrs), - fxml:get_attr_s(<<"type">>, Attrs)}; -is_verify_res(_) -> false. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% SRV support @@ -1190,12 +959,14 @@ get_addrs(Host, Family) -> [] end. +-spec outgoing_s2s_port() -> pos_integer(). outgoing_s2s_port() -> ejabberd_config:get_option( outgoing_s2s_port, fun(I) when is_integer(I), I > 0, I =< 65536 -> I end, 5269). +-spec outgoing_s2s_families() -> [ipv4 | ipv6]. outgoing_s2s_families() -> ejabberd_config:get_option( outgoing_s2s_families, @@ -1207,6 +978,7 @@ outgoing_s2s_families() -> Families end, [ipv4, ipv6]). +-spec outgoing_s2s_timeout() -> pos_integer(). outgoing_s2s_timeout() -> ejabberd_config:get_option( outgoing_s2s_timeout, @@ -1256,21 +1028,24 @@ log_s2s_out(_, Myname, Server, Tls) -> %% Calculate timeout depending on which state we are in: %% Can return integer > 0 | infinity +-spec get_timeout_interval(state_name()) -> pos_integer() | infinity. get_timeout_interval(StateName) -> case StateName of %% Validation implies dialback: Networking can take longer: wait_for_validation -> (?FSMTIMEOUT) * 6; %% When stream is established, we only rely on S2S Timeout timer: stream_established -> infinity; + relay_to_bridge -> infinity; + open_socket -> infinity; _ -> ?FSMTIMEOUT end. %% This function is intended to be called at the end of a state %% function that want to wait for a reconnect delay before stopping. +-spec wait_before_reconnect(state()) -> fsm_next(). wait_before_reconnect(StateData) -> - bounce_queue(StateData#state.queue, - ?ERR_REMOTE_SERVER_NOT_FOUND), - bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND), + bounce_queue(StateData#state.queue, xmpp:err_remote_server_not_found()), + bounce_messages(xmpp:err_remote_server_not_found()), cancel_timer(StateData#state.timer), Delay = case StateData#state.delay_to_retry of undefined_delay -> @@ -1282,6 +1057,7 @@ wait_before_reconnect(StateData) -> StateData#state{timer = Timer, delay_to_retry = Delay, queue = queue:new()}}. +-spec get_max_retry_delay() -> pos_integer(). get_max_retry_delay() -> case ejabberd_config:get_option( s2s_max_retry_delay, @@ -1291,6 +1067,7 @@ get_max_retry_delay() -> end. %% Terminate s2s_out connections that are in state wait_before_retry +-spec terminate_if_waiting_delay(ljid(), ljid()) -> ok. terminate_if_waiting_delay(From, To) -> FromTo = {From, To}, Pids = ejabberd_s2s:get_connections_pids(FromTo), @@ -1299,6 +1076,7 @@ terminate_if_waiting_delay(From, To) -> end, Pids). +-spec fsm_limit_opts() -> [{max_queue, pos_integer()}]. fsm_limit_opts() -> case ejabberd_config:get_option( max_fsm_queue, @@ -1307,6 +1085,24 @@ fsm_limit_opts() -> N -> [{max_queue, N}] end. +-spec decode_element(xmlel(), state_name(), state()) -> fsm_next(). +decode_element(#xmlel{} = El, StateName, StateData) -> + try xmpp:decode(El) of + Pkt -> ?MODULE:StateName(Pkt, StateData) + catch error:{xmpp_codec, Why} -> + Type = xmpp:get_type(El), + case xmpp:is_stanza(El) of + true when Type /= <<"result">>, Type /= <<"error">> -> + Lang = xmpp:get_lang(El), + Txt = xmpp:format_error(Why), + Err = xmpp:make_error(El, xmpp:err_bad_request(Txt, Lang)), + send_element(StateData, Err); + false -> + ok + end, + {next_state, StateName, StateData, get_timeout_interval(StateName)} + end. + opt_type(domain_certfile) -> fun iolist_to_binary/1; opt_type(max_fsm_queue) -> fun (I) when is_integer(I), I > 0 -> I end; diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 465fb587a..432253e09 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -46,8 +46,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). +-include("xmpp.hrl"). -record(state, {socket :: ejabberd_socket:socket_state(), @@ -58,44 +57,19 @@ access :: atom(), check_from = true :: boolean()}). -%-define(DBGFSM, true). +-type state_name() :: wait_for_stream | wait_for_handshake | stream_established. +-type state() :: #state{}. +-type fsm_next() :: {next_state, state_name(), state()}. +-type fsm_stop() :: {stop, normal, state()}. +-type fsm_transition() :: fsm_stop() | fsm_next(). +%-define(DBGFSM, true). -ifdef(DBGFSM). - -define(FSMOPTS, [{debug, [trace]}]). - -else. - -define(FSMOPTS, []). - -endif. --define(STREAM_HEADER, - <<"<?xml version='1.0'?><stream:stream " - "xmlns:stream='http://etherx.jabber.org/stream" - "s' xmlns='jabber:component:accept' id='~s' " - "from='~s'>">>). - --define(STREAM_TRAILER, <<"</stream:stream>">>). - --define(INVALID_HEADER_ERR, - <<"<stream:stream xmlns:stream='http://etherx.ja" - "bber.org/streams'><stream:error>Invalid " - "Stream Header</stream:error></stream:stream>">>). - --define(INVALID_HANDSHAKE_ERR, - <<"<stream:error><not-authorized xmlns='urn:ietf" - ":params:xml:ns:xmpp-streams'/><text " - "xmlns='urn:ietf:params:xml:ns:xmpp-streams' " - "xml:lang='en'>Invalid Handshake</text></strea" - "m:error></stream:stream>">>). - --define(INVALID_XML_ERR, - fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)). - --define(INVALID_NS_ERR, - fxml:element_to_binary(?SERR_INVALID_NAMESPACE)). - %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- @@ -112,14 +86,6 @@ socket_type() -> xml_stream. %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- init([{SockMod, Socket}, Opts]) -> ?INFO_MSG("(~w) External service connected", [Socket]), Access = case lists:keysearch(access, 1, Opts) of @@ -157,177 +123,127 @@ init([{SockMod, Socket}, Opts]) -> streamid = new_id(), host_opts = HostOpts, access = Access, check_from = CheckFrom}}. -%%---------------------------------------------------------------------- -%% Func: StateName/2 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- - -wait_for_stream({xmlstreamstart, _Name, Attrs}, - StateData) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - <<"jabber:component:accept">> -> - To = fxml:get_attr_s(<<"to">>, Attrs), - Host = jid:nameprep(To), - if Host == error -> - Header = io_lib:format(?STREAM_HEADER, - [<<"none">>, ?MYNAME]), - send_text(StateData, - <<(list_to_binary(Header))/binary, - (?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - {stop, normal, StateData}; - true -> - Header = io_lib:format(?STREAM_HEADER, - [StateData#state.streamid, fxml:crypt(To)]), - send_text(StateData, Header), - HostOpts = case dict:is_key(Host, StateData#state.host_opts) of - true -> - StateData#state.host_opts; - false -> - case dict:find(global, StateData#state.host_opts) of - {ok, GlobalPass} -> - dict:from_list([{Host, GlobalPass}]); - error -> - StateData#state.host_opts - end - end, - {next_state, wait_for_handshake, - StateData#state{host = Host, host_opts = HostOpts}} - end; - _ -> - send_text(StateData, ?INVALID_HEADER_ERR), - {stop, normal, StateData} +wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> + try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of + #stream_start{xmlns = ?NS_COMPONENT, to = To} when is_record(To, jid) -> + Host = To#jid.lserver, + send_header(StateData, To), + HostOpts = case dict:is_key(Host, StateData#state.host_opts) of + true -> + StateData#state.host_opts; + false -> + case dict:find(global, StateData#state.host_opts) of + {ok, GlobalPass} -> + dict:from_list([{Host, GlobalPass}]); + error -> + StateData#state.host_opts + end + end, + {next_state, wait_for_handshake, + StateData#state{host = Host, host_opts = HostOpts}}; + #stream_start{xmlns = ?NS_COMPONENT} -> + send_header(StateData, ?MYNAME), + send_element(StateData, xmpp:serr_improper_addressing()), + {stop, normal, StateData}; + #stream_start{} -> + send_header(StateData, ?MYNAME), + send_element(StateData, xmpp:serr_invalid_namespace()), + {stop, normal, StateData} + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + send_header(StateData, ?MYNAME), + send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)), + {stop, normal, StateData} end; wait_for_stream({xmlstreamerror, _}, StateData) -> - Header = io_lib:format(?STREAM_HEADER, - [<<"none">>, ?MYNAME]), - send_text(StateData, - <<(list_to_binary(Header))/binary, (?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), + send_header(StateData, ?MYNAME), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_stream(closed, StateData) -> {stop, normal, StateData}. wait_for_handshake({xmlstreamelement, El}, StateData) -> - #xmlel{name = Name, children = Els} = El, - case {Name, fxml:get_cdata(Els)} of - {<<"handshake">>, Digest} -> - case dict:find(StateData#state.host, StateData#state.host_opts) of - {ok, Password} -> - case p1_sha:sha(<<(StateData#state.streamid)/binary, - Password/binary>>) of - Digest -> - send_text(StateData, <<"<handshake/>">>), - lists:foreach( - fun (H) -> - ejabberd_router:register_route(H, ?MYNAME), - ?INFO_MSG("Route registered for service ~p~n", - [H]) - end, dict:fetch_keys(StateData#state.host_opts)), - {next_state, stream_established, StateData}; - _ -> - send_text(StateData, ?INVALID_HANDSHAKE_ERR), - {stop, normal, StateData} - end; - _ -> - send_text(StateData, ?INVALID_HANDSHAKE_ERR), - {stop, normal, StateData} - end; - _ -> {next_state, wait_for_handshake, StateData} + decode_element(El, wait_for_handshake, StateData); +wait_for_handshake(#handshake{data = Digest}, StateData) -> + case dict:find(StateData#state.host, StateData#state.host_opts) of + {ok, Password} -> + case p1_sha:sha(<<(StateData#state.streamid)/binary, + Password/binary>>) of + Digest -> + send_element(StateData, #handshake{}), + lists:foreach( + fun (H) -> + ejabberd_router:register_route(H, ?MYNAME), + ?INFO_MSG("Route registered for service ~p~n", + [H]) + end, dict:fetch_keys(StateData#state.host_opts)), + {next_state, stream_established, StateData}; + _ -> + send_element(StateData, xmpp:serr_not_authorized()), + {stop, normal, StateData} + end; + _ -> + send_element(StateData, xmpp:serr_not_authorized()), + {stop, normal, StateData} end; wait_for_handshake({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; wait_for_handshake({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_handshake(closed, StateData) -> - {stop, normal, StateData}. + {stop, normal, StateData}; +wait_for_handshake(_Pkt, StateData) -> + {next_state, wait_for_handshake, StateData}. stream_established({xmlstreamelement, El}, StateData) -> - NewEl = jlib:remove_attr(<<"xmlns">>, El), - #xmlel{name = Name, attrs = Attrs} = NewEl, - From = fxml:get_attr_s(<<"from">>, Attrs), - FromJID = case StateData#state.check_from of - %% If the admin does not want to check the from field - %% when accept packets from any address. - %% In this case, the component can send packet of - %% behalf of the server users. - false -> jid:from_string(From); - %% The default is the standard behaviour in XEP-0114 - _ -> - FromJID1 = jid:from_string(From), - case FromJID1 of - #jid{lserver = Server} -> - case dict:is_key(Server, StateData#state.host_opts) of - true -> FromJID1; - false -> error - end; - _ -> error - end - end, - To = fxml:get_attr_s(<<"to">>, Attrs), - ToJID = case To of - <<"">> -> error; - _ -> jid:from_string(To) - end, - if ((Name == <<"iq">>) or (Name == <<"message">>) or - (Name == <<"presence">>)) - and (ToJID /= error) - and (FromJID /= error) -> - ejabberd_router:route(FromJID, ToJID, NewEl); + decode_element(El, stream_established, StateData); +stream_established(El, StateData) when ?is_stanza(El) -> + From = xmpp:get_from(El), + To = xmpp:get_to(El), + Lang = xmpp:get_lang(El), + if From == undefined orelse To == undefined -> + send_error(StateData, El, xmpp:err_jid_malformed()); true -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El), - Txt = <<"Incorrect stanza name or from/to JID">>, - Err = jlib:make_error_reply(NewEl, ?ERRT_BAD_REQUEST(Lang, Txt)), - send_element(StateData, Err), - error + FromJID = case StateData#state.check_from of + false -> + %% If the admin does not want to check the from field + %% when accept packets from any address. + %% In this case, the component can send packet of + %% behalf of the server users. + From; + _ -> + %% The default is the standard behaviour in XEP-0114 + case From of + #jid{lserver = Server} -> + case dict:is_key(Server, StateData#state.host_opts) of + true -> From; + false -> error + end; + _ -> error + end + end, + if FromJID /= error -> + ejabberd_router:route(FromJID, To, El); + true -> + Txt = <<"Incorrect value of 'from' or 'to' attribute">>, + send_error(StateData, El, xmpp:err_not_allowed(Txt, Lang)) + end end, {next_state, stream_established, StateData}; stream_established({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; stream_established({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; stream_established(closed, StateData) -> - {stop, normal, StateData}. - -%%---------------------------------------------------------------------- -%% Func: StateName/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -%state_name(Event, From, StateData) -> -% Reply = ok, -% {reply, Reply, state_name, StateData}. + {stop, normal, StateData}; +stream_established(_Event, StateData) -> + {next_state, stream_established, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- handle_sync_event(_Event, _From, StateName, StateData) -> Reply = ok, {reply, Reply, StateName, StateData}. @@ -335,12 +251,6 @@ handle_sync_event(_Event, _From, StateName, 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) -> send_text(StateData, Text), {next_state, StateName, StateData}; @@ -349,34 +259,20 @@ handle_info({send_element, El}, StateName, StateData) -> {next_state, StateName, StateData}; handle_info({route, From, To, Packet}, StateName, StateData) -> - case acl:match_rule(global, StateData#state.access, - From) - of + case acl:match_rule(global, StateData#state.access, From) of allow -> - #xmlel{name = Name, attrs = Attrs, children = Els} = - Packet, - Attrs2 = - jlib:replace_from_to_attrs(jid:to_string(From), - jid:to_string(To), Attrs), - Text = fxml:element_to_binary(#xmlel{name = Name, - attrs = Attrs2, children = Els}), - send_text(StateData, Text); - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)), - ejabberd_router:route_error(To, From, Err, Packet) + Pkt = xmpp:set_from_to(Packet, From, To), + send_element(StateData, Pkt); + deny -> + Lang = xmpp:get_lang(Packet), + Err = xmpp:err_not_allowed(<<"Denied by ACL">>, Lang), + ejabberd_router:route_error(To, From, Packet, Err) end, {next_state, StateName, StateData}; handle_info(Info, StateName, StateData) -> ?ERROR_MSG("Unexpected info: ~p", [Info]), {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- terminate(Reason, StateName, StateData) -> ?INFO_MSG("terminated: ~p", [Reason]), case StateName of @@ -387,6 +283,7 @@ terminate(Reason, StateName, StateData) -> dict:fetch_keys(StateData#state.host_opts)); _ -> ok end, + catch send_trailer(StateData), (StateData#state.sockmod):close(StateData#state.socket), ok. @@ -401,13 +298,69 @@ print_state(State) -> State. %%% Internal functions %%%---------------------------------------------------------------------- +-spec send_text(state(), iodata()) -> ok. send_text(StateData, Text) -> (StateData#state.sockmod):send(StateData#state.socket, Text). +-spec send_element(state(), xmpp_element()) -> ok. send_element(StateData, El) -> - send_text(StateData, fxml:element_to_binary(El)). + El1 = fix_ns(xmpp:encode(El)), + send_text(StateData, fxml:element_to_binary(El1)). + +-spec send_error(state(), xmlel() | stanza(), error()) -> ok. +send_error(StateData, Stanza, Error) -> + Type = xmpp:get_type(Stanza), + if Type == error; Type == result; + Type == <<"error">>; Type == <<"result">> -> + ok; + true -> + send_element(StateData, xmpp:make_error(Stanza, Error)) + end. + +-spec send_header(state(), binary()) -> ok. +send_header(StateData, Host) -> + send_text(StateData, + io_lib:format( + <<"<?xml version='1.0'?><stream:stream " + "xmlns:stream='http://etherx.jabber.org/stream" + "s' xmlns='jabber:component:accept' id='~s' " + "from='~s'>">>, + [StateData#state.streamid, fxml:crypt(Host)])). + +-spec send_trailer(state()) -> ok. +send_trailer(StateData) -> + send_text(StateData, <<"</stream:stream>">>). + +-spec fix_ns(xmlel()) -> xmlel(). +fix_ns(#xmlel{name = Name} = El) when Name == <<"message">>; + Name == <<"iq">>; + Name == <<"presence">> -> + Attrs = lists:filter( + fun({<<"xmlns">>, _}) -> false; + (_) -> true + end, El#xmlel.attrs), + El#xmlel{attrs = Attrs}; +fix_ns(El) -> + El. + +-spec decode_element(xmlel(), state_name(), state()) -> fsm_transition(). +decode_element(#xmlel{} = El, StateName, StateData) -> + try xmpp:decode(El, [ignore_els]) of + Pkt -> ?MODULE:StateName(Pkt, StateData) + catch error:{xmpp_codec, Why} -> + case xmpp:is_stanza(El) of + true -> + Lang = xmpp:get_lang(El), + Txt = xmpp:format_error(Why), + send_error(StateData, El, xmpp:err_bad_request(Txt, Lang)); + false -> + ok + end, + {next_state, StateName, StateData} + end. +-spec new_id() -> binary(). new_id() -> randoms:get_string(). transform_listen_option({hosts, Hosts, O}, Opts) -> diff --git a/src/xmpp.erl b/src/xmpp.erl index 369fb90c5..5b7e3d1cc 100644 --- a/src/xmpp.erl +++ b/src/xmpp.erl @@ -71,10 +71,6 @@ serr_unsupported_stanza_type/0, serr_unsupported_stanza_type/2, serr_unsupported_version/0, serr_unsupported_version/2]). --ifndef(NS_CLIENT). --define(NS_CLIENT, <<"jabber:client">>). --endif. - -include("xmpp.hrl"). %%%=================================================================== @@ -246,9 +242,14 @@ get_name(Pkt) -> decode(El) -> decode(El, []). --spec decode(xmlel() | xmpp_element(), [proplists:property()]) -> +-spec decode(xmlel() | xmpp_element(), + [proplists:property()] | + fun((xmlel() | xmpp_element()) -> boolean())) -> {ok, xmpp_element()} | {error, any()}. -decode(#xmlel{} = El, Opts) -> +decode(#xmlel{} = El, MatchFun) when is_function(MatchFun) -> + Pkt = xmpp_codec:decode(add_ns(El), [ignore_els]), + decode_els(Pkt, MatchFun); +decode(#xmlel{} = El, Opts) when is_list(Opts) -> xmpp_codec:decode(add_ns(El), Opts); decode(Pkt, _Opts) -> Pkt. diff --git a/src/xmpp_codec.erl b/src/xmpp_codec.erl index c8a4f002f..7eb06b11e 100644 --- a/src/xmpp_codec.erl +++ b/src/xmpp_codec.erl @@ -15,6 +15,21 @@ decode(_el) -> decode(_el, []). decode({xmlel, _name, _attrs, _} = _el, Opts) -> IgnoreEls = proplists:get_bool(ignore_els, Opts), case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"stream:stream">>, <<"jabber:client">>} -> + decode_stream_start(<<"jabber:client">>, IgnoreEls, + _el); + {<<"stream:stream">>, <<"jabber:server">>} -> + decode_stream_start(<<"jabber:server">>, IgnoreEls, + _el); + {<<"stream:stream">>, <<"jabber:component:accept">>} -> + decode_stream_start(<<"jabber:component:accept">>, + IgnoreEls, _el); + {<<"handshake">>, <<"jabber:client">>} -> + decode_handshake(<<"jabber:client">>, IgnoreEls, _el); + {<<"db:verify">>, <<"jabber:client">>} -> + decode_db_verify(<<"jabber:client">>, IgnoreEls, _el); + {<<"db:result">>, <<"jabber:client">>} -> + decode_db_result(<<"jabber:client">>, IgnoreEls, _el); {<<"command">>, <<"http://jabber.org/protocol/commands">>} -> decode_adhoc_command(<<"http://jabber.org/protocol/commands">>, @@ -1278,6 +1293,13 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> is_known_tag({xmlel, _name, _attrs, _} = _el) -> case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"stream:stream">>, <<"jabber:client">>} -> true; + {<<"stream:stream">>, <<"jabber:server">>} -> true; + {<<"stream:stream">>, <<"jabber:component:accept">>} -> + true; + {<<"handshake">>, <<"jabber:client">>} -> true; + {<<"db:verify">>, <<"jabber:client">>} -> true; + {<<"db:result">>, <<"jabber:client">>} -> true; {<<"command">>, <<"http://jabber.org/protocol/commands">>} -> true; @@ -2538,7 +2560,19 @@ encode({adhoc_command, _, _, _, _, _, _, _, _} = Command) -> encode_adhoc_command(Command, [{<<"xmlns">>, - <<"http://jabber.org/protocol/commands">>}]). + <<"http://jabber.org/protocol/commands">>}]); +encode({db_result, _, _, _, _, _} = Db_result) -> + encode_db_result(Db_result, + [{<<"xmlns">>, <<"jabber:client">>}]); +encode({db_verify, _, _, _, _, _, _} = Db_verify) -> + encode_db_verify(Db_verify, + [{<<"xmlns">>, <<"jabber:client">>}]); +encode({handshake, _} = Handshake) -> + encode_handshake(Handshake, + [{<<"xmlns">>, <<"jabber:client">>}]); +encode({stream_start, _, _, _, _, _, _, _, _} = + Stream_stream) -> + encode_stream_start(Stream_stream, []). get_name({last, _, _}) -> <<"query">>; get_name({version, _, _, _}) -> <<"query">>; @@ -2720,7 +2754,13 @@ get_name({client_id, _}) -> <<"client-id">>; get_name({adhoc_actions, _, _, _, _}) -> <<"actions">>; get_name({adhoc_note, _, _}) -> <<"note">>; get_name({adhoc_command, _, _, _, _, _, _, _, _}) -> - <<"command">>. + <<"command">>; +get_name({db_result, _, _, _, _, _}) -> <<"db:result">>; +get_name({db_verify, _, _, _, _, _, _}) -> + <<"db:verify">>; +get_name({handshake, _}) -> <<"handshake">>; +get_name({stream_start, _, _, _, _, _, _, _, _}) -> + <<"stream:stream">>. get_ns({last, _, _}) -> <<"jabber:iq:last">>; get_ns({version, _, _, _}) -> <<"jabber:iq:version">>; @@ -2974,7 +3014,14 @@ get_ns({adhoc_actions, _, _, _, _}) -> get_ns({adhoc_note, _, _}) -> <<"http://jabber.org/protocol/commands">>; get_ns({adhoc_command, _, _, _, _, _, _, _, _}) -> - <<"http://jabber.org/protocol/commands">>. + <<"http://jabber.org/protocol/commands">>; +get_ns({db_result, _, _, _, _, _}) -> + <<"jabber:client">>; +get_ns({db_verify, _, _, _, _, _, _}) -> + <<"jabber:client">>; +get_ns({handshake, _}) -> <<"jabber:client">>; +get_ns({stream_start, _, _, _, _, Xmlns, _, _, _}) -> + Xmlns. dec_int(Val) -> dec_int(Val, infinity, infinity). @@ -3210,6 +3257,12 @@ pp(adhoc_note, 2) -> [type, data]; pp(adhoc_command, 8) -> [node, action, sid, status, lang, actions, notes, xdata]; +pp(db_result, 5) -> [from, to, type, key, error]; +pp(db_verify, 6) -> [from, to, id, type, key, error]; +pp(handshake, 1) -> [data]; +pp(stream_start, 8) -> + [from, to, id, version, xmlns, stream_xmlns, db_xmlns, + lang]; pp(_, _) -> no. join([], _Sep) -> <<>>; @@ -3256,6 +3309,478 @@ dec_tzo(Val) -> M = jlib:binary_to_integer(M1), if H >= -12, H =< 12, M >= 0, M < 60 -> {H, M} end. +decode_stream_start(__TopXMLNS, __IgnoreEls, + {xmlel, <<"stream:stream">>, _attrs, _els}) -> + {From, To, Xmlns, Stream_xmlns, Db_xmlns, Lang, Version, + Id} = + decode_stream_start_attrs(__TopXMLNS, _attrs, undefined, + undefined, undefined, undefined, undefined, + undefined, undefined, undefined), + {stream_start, From, To, Id, Version, Xmlns, + Stream_xmlns, Db_xmlns, Lang}. + +decode_stream_start_attrs(__TopXMLNS, + [{<<"from">>, _val} | _attrs], _From, To, Xmlns, + Stream_xmlns, Db_xmlns, Lang, Version, Id) -> + decode_stream_start_attrs(__TopXMLNS, _attrs, _val, To, + Xmlns, Stream_xmlns, Db_xmlns, Lang, Version, Id); +decode_stream_start_attrs(__TopXMLNS, + [{<<"to">>, _val} | _attrs], From, _To, Xmlns, + Stream_xmlns, Db_xmlns, Lang, Version, Id) -> + decode_stream_start_attrs(__TopXMLNS, _attrs, From, + _val, Xmlns, Stream_xmlns, Db_xmlns, Lang, + Version, Id); +decode_stream_start_attrs(__TopXMLNS, + [{<<"xmlns">>, _val} | _attrs], From, To, _Xmlns, + Stream_xmlns, Db_xmlns, Lang, Version, Id) -> + decode_stream_start_attrs(__TopXMLNS, _attrs, From, To, + _val, Stream_xmlns, Db_xmlns, Lang, Version, Id); +decode_stream_start_attrs(__TopXMLNS, + [{<<"xmlns:stream">>, _val} | _attrs], From, To, + Xmlns, _Stream_xmlns, Db_xmlns, Lang, Version, Id) -> + decode_stream_start_attrs(__TopXMLNS, _attrs, From, To, + Xmlns, _val, Db_xmlns, Lang, Version, Id); +decode_stream_start_attrs(__TopXMLNS, + [{<<"xmlns:db">>, _val} | _attrs], From, To, Xmlns, + Stream_xmlns, _Db_xmlns, Lang, Version, Id) -> + decode_stream_start_attrs(__TopXMLNS, _attrs, From, To, + Xmlns, Stream_xmlns, _val, Lang, Version, Id); +decode_stream_start_attrs(__TopXMLNS, + [{<<"xml:lang">>, _val} | _attrs], From, To, Xmlns, + Stream_xmlns, Db_xmlns, _Lang, Version, Id) -> + decode_stream_start_attrs(__TopXMLNS, _attrs, From, To, + Xmlns, Stream_xmlns, Db_xmlns, _val, Version, Id); +decode_stream_start_attrs(__TopXMLNS, + [{<<"version">>, _val} | _attrs], From, To, Xmlns, + Stream_xmlns, Db_xmlns, Lang, _Version, Id) -> + decode_stream_start_attrs(__TopXMLNS, _attrs, From, To, + Xmlns, Stream_xmlns, Db_xmlns, Lang, _val, Id); +decode_stream_start_attrs(__TopXMLNS, + [{<<"id">>, _val} | _attrs], From, To, Xmlns, + Stream_xmlns, Db_xmlns, Lang, Version, _Id) -> + decode_stream_start_attrs(__TopXMLNS, _attrs, From, To, + Xmlns, Stream_xmlns, Db_xmlns, Lang, Version, + _val); +decode_stream_start_attrs(__TopXMLNS, [_ | _attrs], + From, To, Xmlns, Stream_xmlns, Db_xmlns, Lang, + Version, Id) -> + decode_stream_start_attrs(__TopXMLNS, _attrs, From, To, + Xmlns, Stream_xmlns, Db_xmlns, Lang, Version, Id); +decode_stream_start_attrs(__TopXMLNS, [], From, To, + Xmlns, Stream_xmlns, Db_xmlns, Lang, Version, Id) -> + {decode_stream_start_attr_from(__TopXMLNS, From), + decode_stream_start_attr_to(__TopXMLNS, To), + decode_stream_start_attr_xmlns(__TopXMLNS, Xmlns), + 'decode_stream_start_attr_xmlns:stream'(__TopXMLNS, + Stream_xmlns), + 'decode_stream_start_attr_xmlns:db'(__TopXMLNS, + Db_xmlns), + 'decode_stream_start_attr_xml:lang'(__TopXMLNS, Lang), + decode_stream_start_attr_version(__TopXMLNS, Version), + decode_stream_start_attr_id(__TopXMLNS, Id)}. + +encode_stream_start({stream_start, From, To, Id, + Version, Xmlns, Stream_xmlns, Db_xmlns, Lang}, + _xmlns_attrs) -> + _els = [], + _attrs = encode_stream_start_attr_id(Id, + encode_stream_start_attr_version(Version, + 'encode_stream_start_attr_xml:lang'(Lang, + 'encode_stream_start_attr_xmlns:db'(Db_xmlns, + 'encode_stream_start_attr_xmlns:stream'(Stream_xmlns, + encode_stream_start_attr_xmlns(Xmlns, + encode_stream_start_attr_to(To, + encode_stream_start_attr_from(From, + _xmlns_attrs)))))))), + {xmlel, <<"stream:stream">>, _attrs, _els}. + +decode_stream_start_attr_from(__TopXMLNS, undefined) -> + undefined; +decode_stream_start_attr_from(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"from">>, <<"stream:stream">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_stream_start_attr_from(undefined, _acc) -> _acc; +encode_stream_start_attr_from(_val, _acc) -> + [{<<"from">>, enc_jid(_val)} | _acc]. + +decode_stream_start_attr_to(__TopXMLNS, undefined) -> + undefined; +decode_stream_start_attr_to(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"to">>, <<"stream:stream">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_stream_start_attr_to(undefined, _acc) -> _acc; +encode_stream_start_attr_to(_val, _acc) -> + [{<<"to">>, enc_jid(_val)} | _acc]. + +decode_stream_start_attr_xmlns(__TopXMLNS, undefined) -> + undefined; +decode_stream_start_attr_xmlns(__TopXMLNS, _val) -> + _val. + +encode_stream_start_attr_xmlns(undefined, _acc) -> _acc; +encode_stream_start_attr_xmlns(_val, _acc) -> + [{<<"xmlns">>, _val} | _acc]. + +'decode_stream_start_attr_xmlns:stream'(__TopXMLNS, + undefined) -> + <<>>; +'decode_stream_start_attr_xmlns:stream'(__TopXMLNS, + _val) -> + _val. + +'encode_stream_start_attr_xmlns:stream'(<<>>, _acc) -> + _acc; +'encode_stream_start_attr_xmlns:stream'(_val, _acc) -> + [{<<"xmlns:stream">>, _val} | _acc]. + +'decode_stream_start_attr_xmlns:db'(__TopXMLNS, + undefined) -> + <<>>; +'decode_stream_start_attr_xmlns:db'(__TopXMLNS, _val) -> + _val. + +'encode_stream_start_attr_xmlns:db'(<<>>, _acc) -> _acc; +'encode_stream_start_attr_xmlns:db'(_val, _acc) -> + [{<<"xmlns:db">>, _val} | _acc]. + +'decode_stream_start_attr_xml:lang'(__TopXMLNS, + undefined) -> + <<>>; +'decode_stream_start_attr_xml:lang'(__TopXMLNS, _val) -> + _val. + +'encode_stream_start_attr_xml:lang'(<<>>, _acc) -> _acc; +'encode_stream_start_attr_xml:lang'(_val, _acc) -> + [{<<"xml:lang">>, _val} | _acc]. + +decode_stream_start_attr_version(__TopXMLNS, + undefined) -> + <<>>; +decode_stream_start_attr_version(__TopXMLNS, _val) -> + _val. + +encode_stream_start_attr_version(<<>>, _acc) -> _acc; +encode_stream_start_attr_version(_val, _acc) -> + [{<<"version">>, _val} | _acc]. + +decode_stream_start_attr_id(__TopXMLNS, undefined) -> + <<>>; +decode_stream_start_attr_id(__TopXMLNS, _val) -> _val. + +encode_stream_start_attr_id(<<>>, _acc) -> _acc; +encode_stream_start_attr_id(_val, _acc) -> + [{<<"id">>, _val} | _acc]. + +decode_handshake(__TopXMLNS, __IgnoreEls, + {xmlel, <<"handshake">>, _attrs, _els}) -> + Data = decode_handshake_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + {handshake, Data}. + +decode_handshake_els(__TopXMLNS, __IgnoreEls, [], + Data) -> + decode_handshake_cdata(__TopXMLNS, Data); +decode_handshake_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Data) -> + decode_handshake_els(__TopXMLNS, __IgnoreEls, _els, + <<Data/binary, _data/binary>>); +decode_handshake_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Data) -> + decode_handshake_els(__TopXMLNS, __IgnoreEls, _els, + Data). + +encode_handshake({handshake, Data}, _xmlns_attrs) -> + _els = encode_handshake_cdata(Data, []), + _attrs = _xmlns_attrs, + {xmlel, <<"handshake">>, _attrs, _els}. + +decode_handshake_cdata(__TopXMLNS, <<>>) -> <<>>; +decode_handshake_cdata(__TopXMLNS, _val) -> _val. + +encode_handshake_cdata(<<>>, _acc) -> _acc; +encode_handshake_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + +decode_db_verify(__TopXMLNS, __IgnoreEls, + {xmlel, <<"db:verify">>, _attrs, _els}) -> + {Key, Error} = decode_db_verify_els(__TopXMLNS, + __IgnoreEls, _els, <<>>, undefined), + {From, To, Id, Type} = + decode_db_verify_attrs(__TopXMLNS, _attrs, undefined, + undefined, undefined, undefined), + {db_verify, From, To, Id, Type, Key, Error}. + +decode_db_verify_els(__TopXMLNS, __IgnoreEls, [], Key, + Error) -> + {decode_db_verify_cdata(__TopXMLNS, Key), Error}; +decode_db_verify_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Key, Error) -> + decode_db_verify_els(__TopXMLNS, __IgnoreEls, _els, + <<Key/binary, _data/binary>>, Error); +decode_db_verify_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"error">>, _attrs, _} = _el | _els], Key, + Error) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> when __TopXMLNS == <<"jabber:client">> -> + decode_db_verify_els(__TopXMLNS, __IgnoreEls, _els, Key, + decode_error(__TopXMLNS, __IgnoreEls, _el)); + <<"jabber:client">> -> + decode_db_verify_els(__TopXMLNS, __IgnoreEls, _els, Key, + decode_error(<<"jabber:client">>, __IgnoreEls, + _el)); + _ -> + decode_db_verify_els(__TopXMLNS, __IgnoreEls, _els, Key, + Error) + end; +decode_db_verify_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Key, Error) -> + decode_db_verify_els(__TopXMLNS, __IgnoreEls, _els, Key, + Error). + +decode_db_verify_attrs(__TopXMLNS, + [{<<"from">>, _val} | _attrs], _From, To, Id, Type) -> + decode_db_verify_attrs(__TopXMLNS, _attrs, _val, To, Id, + Type); +decode_db_verify_attrs(__TopXMLNS, + [{<<"to">>, _val} | _attrs], From, _To, Id, Type) -> + decode_db_verify_attrs(__TopXMLNS, _attrs, From, _val, + Id, Type); +decode_db_verify_attrs(__TopXMLNS, + [{<<"id">>, _val} | _attrs], From, To, _Id, Type) -> + decode_db_verify_attrs(__TopXMLNS, _attrs, From, To, + _val, Type); +decode_db_verify_attrs(__TopXMLNS, + [{<<"type">>, _val} | _attrs], From, To, Id, _Type) -> + decode_db_verify_attrs(__TopXMLNS, _attrs, From, To, Id, + _val); +decode_db_verify_attrs(__TopXMLNS, [_ | _attrs], From, + To, Id, Type) -> + decode_db_verify_attrs(__TopXMLNS, _attrs, From, To, Id, + Type); +decode_db_verify_attrs(__TopXMLNS, [], From, To, Id, + Type) -> + {decode_db_verify_attr_from(__TopXMLNS, From), + decode_db_verify_attr_to(__TopXMLNS, To), + decode_db_verify_attr_id(__TopXMLNS, Id), + decode_db_verify_attr_type(__TopXMLNS, Type)}. + +encode_db_verify({db_verify, From, To, Id, Type, Key, + Error}, + _xmlns_attrs) -> + _els = lists:reverse(encode_db_verify_cdata(Key, + 'encode_db_verify_$error'(Error, + []))), + _attrs = encode_db_verify_attr_type(Type, + encode_db_verify_attr_id(Id, + encode_db_verify_attr_to(To, + encode_db_verify_attr_from(From, + _xmlns_attrs)))), + {xmlel, <<"db:verify">>, _attrs, _els}. + +'encode_db_verify_$error'(undefined, _acc) -> _acc; +'encode_db_verify_$error'(Error, _acc) -> + [encode_error(Error, []) | _acc]. + +decode_db_verify_attr_from(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"from">>, <<"db:verify">>, + __TopXMLNS}}); +decode_db_verify_attr_from(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"from">>, <<"db:verify">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_db_verify_attr_from(_val, _acc) -> + [{<<"from">>, enc_jid(_val)} | _acc]. + +decode_db_verify_attr_to(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"to">>, <<"db:verify">>, __TopXMLNS}}); +decode_db_verify_attr_to(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"to">>, <<"db:verify">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_db_verify_attr_to(_val, _acc) -> + [{<<"to">>, enc_jid(_val)} | _acc]. + +decode_db_verify_attr_id(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"id">>, <<"db:verify">>, __TopXMLNS}}); +decode_db_verify_attr_id(__TopXMLNS, _val) -> _val. + +encode_db_verify_attr_id(_val, _acc) -> + [{<<"id">>, _val} | _acc]. + +decode_db_verify_attr_type(__TopXMLNS, undefined) -> + undefined; +decode_db_verify_attr_type(__TopXMLNS, _val) -> + case catch dec_enum(_val, [valid, invalid, error]) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"type">>, <<"db:verify">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_db_verify_attr_type(undefined, _acc) -> _acc; +encode_db_verify_attr_type(_val, _acc) -> + [{<<"type">>, enc_enum(_val)} | _acc]. + +decode_db_verify_cdata(__TopXMLNS, <<>>) -> <<>>; +decode_db_verify_cdata(__TopXMLNS, _val) -> _val. + +encode_db_verify_cdata(<<>>, _acc) -> _acc; +encode_db_verify_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + +decode_db_result(__TopXMLNS, __IgnoreEls, + {xmlel, <<"db:result">>, _attrs, _els}) -> + {Key, Error} = decode_db_result_els(__TopXMLNS, + __IgnoreEls, _els, <<>>, undefined), + {From, To, Type} = decode_db_result_attrs(__TopXMLNS, + _attrs, undefined, undefined, + undefined), + {db_result, From, To, Type, Key, Error}. + +decode_db_result_els(__TopXMLNS, __IgnoreEls, [], Key, + Error) -> + {decode_db_result_cdata(__TopXMLNS, Key), Error}; +decode_db_result_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Key, Error) -> + decode_db_result_els(__TopXMLNS, __IgnoreEls, _els, + <<Key/binary, _data/binary>>, Error); +decode_db_result_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"error">>, _attrs, _} = _el | _els], Key, + Error) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> when __TopXMLNS == <<"jabber:client">> -> + decode_db_result_els(__TopXMLNS, __IgnoreEls, _els, Key, + decode_error(__TopXMLNS, __IgnoreEls, _el)); + <<"jabber:client">> -> + decode_db_result_els(__TopXMLNS, __IgnoreEls, _els, Key, + decode_error(<<"jabber:client">>, __IgnoreEls, + _el)); + _ -> + decode_db_result_els(__TopXMLNS, __IgnoreEls, _els, Key, + Error) + end; +decode_db_result_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Key, Error) -> + decode_db_result_els(__TopXMLNS, __IgnoreEls, _els, Key, + Error). + +decode_db_result_attrs(__TopXMLNS, + [{<<"from">>, _val} | _attrs], _From, To, Type) -> + decode_db_result_attrs(__TopXMLNS, _attrs, _val, To, + Type); +decode_db_result_attrs(__TopXMLNS, + [{<<"to">>, _val} | _attrs], From, _To, Type) -> + decode_db_result_attrs(__TopXMLNS, _attrs, From, _val, + Type); +decode_db_result_attrs(__TopXMLNS, + [{<<"type">>, _val} | _attrs], From, To, _Type) -> + decode_db_result_attrs(__TopXMLNS, _attrs, From, To, + _val); +decode_db_result_attrs(__TopXMLNS, [_ | _attrs], From, + To, Type) -> + decode_db_result_attrs(__TopXMLNS, _attrs, From, To, + Type); +decode_db_result_attrs(__TopXMLNS, [], From, To, + Type) -> + {decode_db_result_attr_from(__TopXMLNS, From), + decode_db_result_attr_to(__TopXMLNS, To), + decode_db_result_attr_type(__TopXMLNS, Type)}. + +encode_db_result({db_result, From, To, Type, Key, + Error}, + _xmlns_attrs) -> + _els = lists:reverse(encode_db_result_cdata(Key, + 'encode_db_result_$error'(Error, + []))), + _attrs = encode_db_result_attr_type(Type, + encode_db_result_attr_to(To, + encode_db_result_attr_from(From, + _xmlns_attrs))), + {xmlel, <<"db:result">>, _attrs, _els}. + +'encode_db_result_$error'(undefined, _acc) -> _acc; +'encode_db_result_$error'(Error, _acc) -> + [encode_error(Error, []) | _acc]. + +decode_db_result_attr_from(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"from">>, <<"db:result">>, + __TopXMLNS}}); +decode_db_result_attr_from(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"from">>, <<"db:result">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_db_result_attr_from(_val, _acc) -> + [{<<"from">>, enc_jid(_val)} | _acc]. + +decode_db_result_attr_to(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"to">>, <<"db:result">>, __TopXMLNS}}); +decode_db_result_attr_to(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"to">>, <<"db:result">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_db_result_attr_to(_val, _acc) -> + [{<<"to">>, enc_jid(_val)} | _acc]. + +decode_db_result_attr_type(__TopXMLNS, undefined) -> + undefined; +decode_db_result_attr_type(__TopXMLNS, _val) -> + case catch dec_enum(_val, [valid, invalid, error]) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"type">>, <<"db:result">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_db_result_attr_type(undefined, _acc) -> _acc; +encode_db_result_attr_type(_val, _acc) -> + [{<<"type">>, enc_enum(_val)} | _acc]. + +decode_db_result_cdata(__TopXMLNS, <<>>) -> <<>>; +decode_db_result_cdata(__TopXMLNS, _val) -> _val. + +encode_db_result_cdata(<<>>, _acc) -> _acc; +encode_db_result_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + decode_adhoc_command(__TopXMLNS, __IgnoreEls, {xmlel, <<"command">>, _attrs, _els}) -> {Xdata, Notes, Actions} = diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec index 0e0145f72..7503eab10 100644 --- a/tools/xmpp_codec.spec +++ b/tools/xmpp_codec.spec @@ -2877,6 +2877,66 @@ #ref{name = xdata, min = 0, max = 1}, #ref{name = adhoc_command_notes, label = '$notes'}]}). +-xml(db_result, + #elem{name = <<"db:result">>, + xmlns = <<"jabber:client">>, + result = {db_result, '$from', '$to', '$type', '$key', '$error'}, + refs = [#ref{name = error, min = 0, max = 1}], + cdata = #cdata{default = <<"">>, label = '$key'}, + attrs = [#attr{name = <<"from">>, required = true, + dec = {dec_jid, []}, enc = {enc_jid, []}}, + #attr{name = <<"to">>, required = true, + dec = {dec_jid, []}, enc = {enc_jid, []}}, + #attr{name = <<"type">>, + dec = {dec_enum, [[valid, invalid, error]]}, + enc = {enc_enum, []}}]}). + +-xml(db_verify, + #elem{name = <<"db:verify">>, + xmlns = <<"jabber:client">>, + result = {db_verify, '$from', '$to', '$id', '$type', '$key', '$error'}, + refs = [#ref{name = error, min = 0, max = 1}], + cdata = #cdata{default = <<"">>, label = '$key'}, + attrs = [#attr{name = <<"from">>, required = true, + dec = {dec_jid, []}, enc = {enc_jid, []}}, + #attr{name = <<"to">>, required = true, + dec = {dec_jid, []}, enc = {enc_jid, []}}, + #attr{name = <<"id">>, required = true}, + #attr{name = <<"type">>, + dec = {dec_enum, [[valid, invalid, error]]}, + enc = {enc_enum, []}}]}). + +-xml(handshake, + #elem{name = <<"handshake">>, + xmlns = <<"jabber:client">>, + result = {handshake, '$data'}, + cdata = #cdata{default = <<"">>, label = '$data'}}). + +-xml(stream_start, + #elem{name = <<"stream:stream">>, + xmlns = [<<"jabber:client">>, <<"jabber:server">>, + <<"jabber:component:accept">>], + result = {stream_start, '$from', '$to', '$id', + '$version', '$xmlns', '$stream_xmlns', + '$db_xmlns', '$lang'}, + attrs = [#attr{name = <<"from">>, + dec = {dec_jid, []}, + enc = {enc_jid, []}}, + #attr{name = <<"to">>, + dec = {dec_jid, []}, + enc = {enc_jid, []}}, + #attr{name = <<"xmlns">>}, + #attr{name = <<"xmlns:stream">>, + label = '$stream_xmlns', + default = <<"">>}, + #attr{name = <<"xmlns:db">>, + label = '$db_xmlns', + default = <<"">>}, + #attr{name = <<"xml:lang">>, label = '$lang', + default = <<"">>}, + #attr{name = <<"version">>, default = <<"">>}, + #attr{name = <<"id">>, default = <<"">>}]}). + dec_tzo(Val) -> [H1, M1] = str:tokens(Val, <<":">>), H = jlib:binary_to_integer(H1), |