25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-12-22 17:28:25 +01:00

Rewrite S2S and ejabberd_service code to use XML generator

This commit is contained in:
Evgeniy Khramtsov 2016-07-27 10:45:08 +03:00
parent 23858469b7
commit c409ed2f2c
13 changed files with 1598 additions and 1264 deletions

View File

@ -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).

View File

@ -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,

View File

@ -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() |

View File

@ -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.

View File

@ -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,

View File

@ -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(

View File

@ -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 ->

View File

@ -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({xmlstreamend, _Name},
StateData) ->
send_text(StateData, ?STREAM_TRAILER),
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) ->
{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;

File diff suppressed because it is too large Load Diff

View File

@ -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()}).
-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}.
{stop, normal, StateData};
stream_established(_Event, StateData) ->
{next_state, stream_established, 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}.
%%----------------------------------------------------------------------
%% 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) ->

View File

@ -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.

View File

@ -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} =

View File

@ -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),