diff --git a/include/xmpp_codec.hrl b/include/xmpp_codec.hrl index 1bf1aac..4f7bf6a 100644 --- a/include/xmpp_codec.hrl +++ b/include/xmpp_codec.hrl @@ -193,10 +193,6 @@ -record(legacy_auth_feature, {}). -type legacy_auth_feature() :: #legacy_auth_feature{}. --record(bind, {jid :: undefined | jid:jid(), - resource = <<>> :: binary()}). --type bind() :: #bind{}. - -record(rosterver_feature, {}). -type rosterver_feature() :: #rosterver_feature{}. @@ -441,6 +437,12 @@ xmlns = <<>> :: binary()}). -type sm_enable() :: #sm_enable{}. +-record(fast, {zero_rtt :: 'false' | 'true' | 'undefined', + count :: 'undefined' | integer(), + invalidate :: 'false' | 'true' | 'undefined', + mechs = [] :: [binary()]}). +-type fast() :: #fast{}. + -record(feature_sm, {xmlns = <<>> :: binary()}). -type feature_sm() :: #feature_sm{}. @@ -464,6 +466,10 @@ retract :: 'undefined' | binary()}). -type ps_items() :: #ps_items{}. +-record(fast_token, {expiry :: undefined | erlang:timestamp(), + token = <<>> :: binary()}). +-type fast_token() :: #fast_token{}. + -record(idle, {since :: erlang:timestamp()}). -type idle() :: #idle{}. @@ -600,6 +606,9 @@ node = <<>> :: binary()}). -type push_disable() :: #push_disable{}. +-record(fast_request_token, {mech = <<>> :: binary()}). +-type fast_request_token() :: #fast_request_token{}. + -record(mark_displayed, {id = <<>> :: binary()}). -type mark_displayed() :: #mark_displayed{}. @@ -608,24 +617,6 @@ device :: 'undefined' | binary()}). -type sasl2_user_agent() :: #sasl2_user_agent{}. --record(jingle_ft_file, {date :: undefined | erlang:timestamp(), - desc = [] :: [#text{}], - hash = [] :: [#hash{}], - 'hash-used' :: 'undefined' | #hash_used{}, - 'media-type' :: 'undefined' | binary(), - name :: 'undefined' | binary(), - size :: 'undefined' | non_neg_integer(), - range :: 'undefined' | #jingle_ft_range{}}). --type jingle_ft_file() :: #jingle_ft_file{}. - --record(jingle_ft_checksum, {creator :: 'initiator' | 'responder' | 'undefined', - name = <<>> :: binary(), - file :: #jingle_ft_file{}}). --type jingle_ft_checksum() :: #jingle_ft_checksum{}. - --record(jingle_ft_description, {file :: 'undefined' | #jingle_ft_file{}}). --type jingle_ft_description() :: #jingle_ft_description{}. - -record(upload_request, {filename :: binary(), size :: non_neg_integer(), 'content-type' = <<>> :: binary(), @@ -850,14 +841,6 @@ xdata :: 'undefined' | #xdata{}}). -type ps_options() :: #ps_options{}. --record(ps_event, {items :: 'undefined' | #ps_items{}, - purge :: 'undefined' | binary(), - subscription :: 'undefined' | #ps_subscription{}, - delete :: 'undefined' | {binary(),binary()}, - create :: 'undefined' | binary(), - configuration :: 'undefined' | {binary(),'undefined' | #xdata{}}}). --type ps_event() :: #ps_event{}. - -record(message_thread, {parent = <<>> :: binary(), data = <<>> :: binary()}). -type message_thread() :: #message_thread{}. @@ -962,14 +945,6 @@ sub_els = [] :: [xmpp_element() | fxml:xmlel()]}). -type oob_x() :: #oob_x{}. --record(pubsub_owner, {affiliations :: 'undefined' | {binary(),[#ps_affiliation{}]}, - configure :: 'undefined' | {binary(),'undefined' | #xdata{}}, - default :: 'undefined' | {binary(),'undefined' | #xdata{}}, - delete :: 'undefined' | {binary(),binary()}, - purge :: 'undefined' | binary(), - subscriptions :: 'undefined' | {binary(),[#ps_subscription{}]}}). --type pubsub_owner() :: #pubsub_owner{}. - -record(x509_ca_list, {certs = [] :: [binary()]}). -type x509_ca_list() :: #x509_ca_list{}. @@ -1069,24 +1044,6 @@ ctry :: 'undefined' | binary()}). -type vcard_adr() :: #vcard_adr{}. --record(pubsub, {subscriptions :: 'undefined' | {binary(),[#ps_subscription{}]}, - subscription :: 'undefined' | #ps_subscription{}, - affiliations :: 'undefined' | {binary(),[#ps_affiliation{}]}, - publish :: 'undefined' | #ps_publish{}, - publish_options :: 'undefined' | #xdata{}, - subscribe :: 'undefined' | #ps_subscribe{}, - unsubscribe :: 'undefined' | #ps_unsubscribe{}, - options :: 'undefined' | #ps_options{}, - items :: 'undefined' | #ps_items{}, - retract :: 'undefined' | #ps_retract{}, - create :: 'undefined' | binary(), - configure :: 'undefined' | {binary(),'undefined' | #xdata{}}, - default :: 'undefined' | {binary(),'undefined' | #xdata{}}, - delete :: 'undefined' | {binary(),binary()}, - purge :: 'undefined' | binary(), - rsm :: 'undefined' | #rsm_set{}}). --type pubsub() :: #pubsub{}. - -record(vcard_tel, {home = false :: boolean(), work = false :: boolean(), voice = false :: boolean(), @@ -1370,15 +1327,6 @@ -record(sasl_challenge, {text = <<>> :: binary()}). -type sasl_challenge() :: #sasl_challenge{}. --record(sasl2_failure, {reason :: 'aborted' | 'account-disabled' | 'bad-protocol' | 'credentials-expired' | 'encryption-required' | 'incorrect-encoding' | 'invalid-authzid' | 'invalid-mechanism' | 'malformed-request' | 'mechanism-too-weak' | 'not-authorized' | 'temporary-auth-failure' | 'undefined', - text :: 'undefined' | binary(), - sub_els = [] :: [xmpp_element() | fxml:xmlel()]}). --type sasl2_failure() :: #sasl2_failure{}. - --record(sasl_failure, {reason :: 'aborted' | 'account-disabled' | 'bad-protocol' | 'credentials-expired' | 'encryption-required' | 'incorrect-encoding' | 'invalid-authzid' | 'invalid-mechanism' | 'malformed-request' | 'mechanism-too-weak' | 'not-authorized' | 'temporary-auth-failure' | 'undefined', - text = [] :: [#text{}]}). --type sasl_failure() :: #sasl_failure{}. - -record(roster_query, {items = [] :: [#roster_item{}], ver :: 'undefined' | binary(), mix_annotate = false :: boolean()}). @@ -1399,6 +1347,71 @@ -record(addresses, {list = [] :: [#address{}]}). -type addresses() :: #addresses{}. +-record(sasl2_failure, {reason :: 'aborted' | 'account-disabled' | 'bad-protocol' | 'credentials-expired' | 'encryption-required' | 'incorrect-encoding' | 'invalid-authzid' | 'invalid-mechanism' | 'malformed-request' | 'mechanism-too-weak' | 'not-authorized' | 'temporary-auth-failure' | 'undefined', + text :: 'undefined' | binary(), + sub_els = [] :: [xmpp_element() | fxml:xmlel()]}). +-type sasl2_failure() :: #sasl2_failure{}. + +-record(sasl_failure, {reason :: 'aborted' | 'account-disabled' | 'bad-protocol' | 'credentials-expired' | 'encryption-required' | 'incorrect-encoding' | 'invalid-authzid' | 'invalid-mechanism' | 'malformed-request' | 'mechanism-too-weak' | 'not-authorized' | 'temporary-auth-failure' | 'undefined', + text = [] :: [#text{}]}). +-type sasl_failure() :: #sasl_failure{}. + +-record(bind, {jid :: undefined | jid:jid(), + resource = <<>> :: binary()}). +-type bind() :: #bind{}. + +-record(jingle_ft_file, {date :: undefined | erlang:timestamp(), + desc = [] :: [#text{}], + hash = [] :: [#hash{}], + 'hash-used' :: 'undefined' | #hash_used{}, + 'media-type' :: 'undefined' | binary(), + name :: 'undefined' | binary(), + size :: 'undefined' | non_neg_integer(), + range :: 'undefined' | #jingle_ft_range{}}). +-type jingle_ft_file() :: #jingle_ft_file{}. + +-record(jingle_ft_checksum, {creator :: 'initiator' | 'responder' | 'undefined', + name = <<>> :: binary(), + file :: #jingle_ft_file{}}). +-type jingle_ft_checksum() :: #jingle_ft_checksum{}. + +-record(jingle_ft_description, {file :: 'undefined' | #jingle_ft_file{}}). +-type jingle_ft_description() :: #jingle_ft_description{}. + +-record(pubsub_owner, {affiliations :: 'undefined' | {binary(),[#ps_affiliation{}]}, + configure :: 'undefined' | {binary(),'undefined' | #xdata{}}, + default :: 'undefined' | {binary(),'undefined' | #xdata{}}, + delete :: 'undefined' | {binary(),binary()}, + purge :: 'undefined' | binary(), + subscriptions :: 'undefined' | {binary(),[#ps_subscription{}]}}). +-type pubsub_owner() :: #pubsub_owner{}. + +-record(pubsub, {subscriptions :: 'undefined' | {binary(),[#ps_subscription{}]}, + subscription :: 'undefined' | #ps_subscription{}, + affiliations :: 'undefined' | {binary(),[#ps_affiliation{}]}, + publish :: 'undefined' | #ps_publish{}, + publish_options :: 'undefined' | #xdata{}, + subscribe :: 'undefined' | #ps_subscribe{}, + unsubscribe :: 'undefined' | #ps_unsubscribe{}, + options :: 'undefined' | #ps_options{}, + items :: 'undefined' | #ps_items{}, + retract :: 'undefined' | #ps_retract{}, + create :: 'undefined' | binary(), + configure :: 'undefined' | {binary(),'undefined' | #xdata{}}, + default :: 'undefined' | {binary(),'undefined' | #xdata{}}, + delete :: 'undefined' | {binary(),binary()}, + purge :: 'undefined' | binary(), + rsm :: 'undefined' | #rsm_set{}}). +-type pubsub() :: #pubsub{}. + +-record(ps_event, {items :: 'undefined' | #ps_items{}, + purge :: 'undefined' | binary(), + subscription :: 'undefined' | #ps_subscription{}, + delete :: 'undefined' | {binary(),binary()}, + create :: 'undefined' | binary(), + configuration :: 'undefined' | {binary(),'undefined' | #xdata{}}}). +-type ps_event() :: #ps_event{}. + -type xmpp_element() :: address() | addresses() | adhoc_actions() | @@ -1444,6 +1457,9 @@ disco_item() | disco_items() | expire() | + fast() | + fast_request_token() | + fast_token() | fasten_apply_to() | fasten_external() | feature_csi() | diff --git a/specs/xmpp_codec.spec b/specs/xmpp_codec.spec index b4bc34f..5a8005f 100644 --- a/specs/xmpp_codec.spec +++ b/specs/xmpp_codec.spec @@ -5485,14 +5485,14 @@ xmlns = <<"urn:xmpp:sasl:2">>, module = 'xep0388', result = {sasl2_task_data, '$_els'}}). - + -xml(sasl2_next, #elem{name = <<"next">>, xmlns = <<"urn:xmpp:sasl:2">>, attrs = [#attr{name = <<"task">>}], module = 'xep0388', result = {sasl2_next, '$task', '$_els'}}). - + -xml(sasl2_abort, #elem{name = <<"abort">>, xmlns = <<"urn:xmpp:sasl:2">>, @@ -5542,7 +5542,7 @@ label = '$var', required = true}], result = {bind2_feature, '$var'}}). - + -xml(s2s_bidi, #elem{name = <<"bidi">>, xmlns = <<"urn:xmpp:features:bidi">>, @@ -5581,7 +5581,57 @@ enc = {base64, encode, []}, dec = {base64, decode, []}}, result = {scram_upgrade_hash, '$data'}}). - + +-xml(fast, + #elem{name = <<"fast">>, + xmlns = <<"urn:xmpp:fast:0">>, + module = 'xep0484', + attrs = [#attr{name = <<"tls-0rtt">>, + label = '$zero_rtt', + enc = {enc_bool, []}, + dec = {dec_bool, []}}, + #attr{name = <<"count">>, + label = '$count', + enc = {enc_int, []}, + dec = {dec_int, []}}, + #attr{name = <<"invalidate">>, + label = '$invalidate', + enc = {enc_bool, []}, + dec = {dec_bool, []}}], + refs = [#ref{name = fast_mech, + label = '$mechs', + min = 0}], + result = {fast, '$zero_rtt', '$count', '$invalidate', '$mechs'}}). + +-xml(fast_mech, + #elem{name = <<"mechanism">>, + xmlns = <<"urn:xmpp:fast:0">>, + module = 'xep0484', + result = '$cdata'}). + +-xml(fast_request_token, + #elem{name = <<"request-token">>, + xmlns = <<"urn:xmpp:fast:0">>, + module = 'xep0484', + attrs = [#attr{name = <<"mechanism">>, + label = '$mech', + required = true}], + result = {fast_request_token, '$mech'} + }). + +-xml(fast_token, + #elem{name = <<"token">>, + xmlns = <<"urn:xmpp:fast:0">>, + module = 'xep0484', + attrs = [#attr{name = <<"expiry">>, + label = '$expiry', + enc = {enc_utc, []}, + dec = {dec_utc, []}}, + #attr{name = <<"token">>, + label = '$token'}], + result = {fast_token, '$expiry', '$token'} + }). + -spec dec_tzo(_) -> {integer(), integer()}. dec_tzo(Val) -> [H1, M1] = binary:split(Val, <<":">>), diff --git a/src/xep0484.erl b/src/xep0484.erl new file mode 100644 index 0000000..423ae92 --- /dev/null +++ b/src/xep0484.erl @@ -0,0 +1,377 @@ +%% Created automatically by XML generator (fxml_gen.erl) +%% Source: xmpp_codec.spec + +-module(xep0484). + +-compile(export_all). + +do_decode(<<"token">>, <<"urn:xmpp:fast:0">>, El, + Opts) -> + decode_fast_token(<<"urn:xmpp:fast:0">>, Opts, El); +do_decode(<<"request-token">>, <<"urn:xmpp:fast:0">>, + El, Opts) -> + decode_fast_request_token(<<"urn:xmpp:fast:0">>, + Opts, + El); +do_decode(<<"mechanism">>, <<"urn:xmpp:fast:0">>, El, + Opts) -> + decode_fast_mech(<<"urn:xmpp:fast:0">>, Opts, El); +do_decode(<<"fast">>, <<"urn:xmpp:fast:0">>, El, + Opts) -> + decode_fast(<<"urn:xmpp:fast:0">>, Opts, El); +do_decode(Name, <<>>, _, _) -> + erlang:error({xmpp_codec, {missing_tag_xmlns, Name}}); +do_decode(Name, XMLNS, _, _) -> + erlang:error({xmpp_codec, {unknown_tag, Name, XMLNS}}). + +tags() -> + [{<<"token">>, <<"urn:xmpp:fast:0">>}, + {<<"request-token">>, <<"urn:xmpp:fast:0">>}, + {<<"mechanism">>, <<"urn:xmpp:fast:0">>}, + {<<"fast">>, <<"urn:xmpp:fast:0">>}]. + +do_encode({fast, _, _, _, _} = Fast, TopXMLNS) -> + encode_fast(Fast, TopXMLNS); +do_encode({fast_request_token, _} = Request_token, + TopXMLNS) -> + encode_fast_request_token(Request_token, TopXMLNS); +do_encode({fast_token, _, _} = Token, TopXMLNS) -> + encode_fast_token(Token, TopXMLNS). + +do_get_name({fast, _, _, _, _}) -> <<"fast">>; +do_get_name({fast_request_token, _}) -> + <<"request-token">>; +do_get_name({fast_token, _, _}) -> <<"token">>. + +do_get_ns({fast, _, _, _, _}) -> <<"urn:xmpp:fast:0">>; +do_get_ns({fast_request_token, _}) -> + <<"urn:xmpp:fast:0">>; +do_get_ns({fast_token, _, _}) -> <<"urn:xmpp:fast:0">>. + +pp(fast, 4) -> [zero_rtt, count, invalidate, mechs]; +pp(fast_request_token, 1) -> [mech]; +pp(fast_token, 2) -> [expiry, token]; +pp(_, _) -> no. + +records() -> + [{fast, 4}, {fast_request_token, 1}, {fast_token, 2}]. + +dec_bool(<<"false">>) -> false; +dec_bool(<<"0">>) -> false; +dec_bool(<<"true">>) -> true; +dec_bool(<<"1">>) -> true. + +dec_int(Val) -> dec_int(Val, infinity, infinity). + +dec_int(Val, Min, Max) -> + case erlang:binary_to_integer(Val) of + Int when Int =< Max, Min == infinity -> Int; + Int when Int =< Max, Int >= Min -> Int + end. + +dec_utc(Val) -> xmpp_util:decode_timestamp(Val). + +enc_bool(false) -> <<"false">>; +enc_bool(true) -> <<"true">>. + +enc_int(Int) -> erlang:integer_to_binary(Int). + +enc_utc(Val) -> xmpp_util:encode_timestamp(Val). + +decode_fast_token(__TopXMLNS, __Opts, + {xmlel, <<"token">>, _attrs, _els}) -> + {Expiry, Token} = decode_fast_token_attrs(__TopXMLNS, + _attrs, + undefined, + undefined), + {fast_token, Expiry, Token}. + +decode_fast_token_attrs(__TopXMLNS, + [{<<"expiry">>, _val} | _attrs], _Expiry, Token) -> + decode_fast_token_attrs(__TopXMLNS, + _attrs, + _val, + Token); +decode_fast_token_attrs(__TopXMLNS, + [{<<"token">>, _val} | _attrs], Expiry, _Token) -> + decode_fast_token_attrs(__TopXMLNS, + _attrs, + Expiry, + _val); +decode_fast_token_attrs(__TopXMLNS, [_ | _attrs], + Expiry, Token) -> + decode_fast_token_attrs(__TopXMLNS, + _attrs, + Expiry, + Token); +decode_fast_token_attrs(__TopXMLNS, [], Expiry, + Token) -> + {decode_fast_token_attr_expiry(__TopXMLNS, Expiry), + decode_fast_token_attr_token(__TopXMLNS, Token)}. + +encode_fast_token({fast_token, Expiry, Token}, + __TopXMLNS) -> + __NewTopXMLNS = + xmpp_codec:choose_top_xmlns(<<"urn:xmpp:fast:0">>, + [], + __TopXMLNS), + _els = [], + _attrs = encode_fast_token_attr_token(Token, + encode_fast_token_attr_expiry(Expiry, + xmpp_codec:enc_xmlns_attrs(__NewTopXMLNS, + __TopXMLNS))), + {xmlel, <<"token">>, _attrs, _els}. + +decode_fast_token_attr_expiry(__TopXMLNS, undefined) -> + undefined; +decode_fast_token_attr_expiry(__TopXMLNS, _val) -> + case catch dec_utc(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, + <<"expiry">>, + <<"token">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_fast_token_attr_expiry(undefined, _acc) -> _acc; +encode_fast_token_attr_expiry(_val, _acc) -> + [{<<"expiry">>, enc_utc(_val)} | _acc]. + +decode_fast_token_attr_token(__TopXMLNS, undefined) -> + <<>>; +decode_fast_token_attr_token(__TopXMLNS, _val) -> _val. + +encode_fast_token_attr_token(<<>>, _acc) -> _acc; +encode_fast_token_attr_token(_val, _acc) -> + [{<<"token">>, _val} | _acc]. + +decode_fast_request_token(__TopXMLNS, __Opts, + {xmlel, <<"request-token">>, _attrs, _els}) -> + Mech = decode_fast_request_token_attrs(__TopXMLNS, + _attrs, + undefined), + {fast_request_token, Mech}. + +decode_fast_request_token_attrs(__TopXMLNS, + [{<<"mechanism">>, _val} | _attrs], _Mech) -> + decode_fast_request_token_attrs(__TopXMLNS, + _attrs, + _val); +decode_fast_request_token_attrs(__TopXMLNS, + [_ | _attrs], Mech) -> + decode_fast_request_token_attrs(__TopXMLNS, + _attrs, + Mech); +decode_fast_request_token_attrs(__TopXMLNS, [], Mech) -> + decode_fast_request_token_attr_mechanism(__TopXMLNS, + Mech). + +encode_fast_request_token({fast_request_token, Mech}, + __TopXMLNS) -> + __NewTopXMLNS = + xmpp_codec:choose_top_xmlns(<<"urn:xmpp:fast:0">>, + [], + __TopXMLNS), + _els = [], + _attrs = encode_fast_request_token_attr_mechanism(Mech, + xmpp_codec:enc_xmlns_attrs(__NewTopXMLNS, + __TopXMLNS)), + {xmlel, <<"request-token">>, _attrs, _els}. + +decode_fast_request_token_attr_mechanism(__TopXMLNS, + undefined) -> + erlang:error({xmpp_codec, + {missing_attr, + <<"mechanism">>, + <<"request-token">>, + __TopXMLNS}}); +decode_fast_request_token_attr_mechanism(__TopXMLNS, + _val) -> + _val. + +encode_fast_request_token_attr_mechanism(_val, _acc) -> + [{<<"mechanism">>, _val} | _acc]. + +decode_fast_mech(__TopXMLNS, __Opts, + {xmlel, <<"mechanism">>, _attrs, _els}) -> + Cdata = decode_fast_mech_els(__TopXMLNS, + __Opts, + _els, + <<>>), + Cdata. + +decode_fast_mech_els(__TopXMLNS, __Opts, [], Cdata) -> + decode_fast_mech_cdata(__TopXMLNS, Cdata); +decode_fast_mech_els(__TopXMLNS, __Opts, + [{xmlcdata, _data} | _els], Cdata) -> + decode_fast_mech_els(__TopXMLNS, + __Opts, + _els, + <>); +decode_fast_mech_els(__TopXMLNS, __Opts, [_ | _els], + Cdata) -> + decode_fast_mech_els(__TopXMLNS, __Opts, _els, Cdata). + +encode_fast_mech(Cdata, __TopXMLNS) -> + __NewTopXMLNS = + xmpp_codec:choose_top_xmlns(<<"urn:xmpp:fast:0">>, + [], + __TopXMLNS), + _els = encode_fast_mech_cdata(Cdata, []), + _attrs = xmpp_codec:enc_xmlns_attrs(__NewTopXMLNS, + __TopXMLNS), + {xmlel, <<"mechanism">>, _attrs, _els}. + +decode_fast_mech_cdata(__TopXMLNS, <<>>) -> <<>>; +decode_fast_mech_cdata(__TopXMLNS, _val) -> _val. + +encode_fast_mech_cdata(<<>>, _acc) -> _acc; +encode_fast_mech_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + +decode_fast(__TopXMLNS, __Opts, + {xmlel, <<"fast">>, _attrs, _els}) -> + Mechs = decode_fast_els(__TopXMLNS, __Opts, _els, []), + {Zero_rtt, Count, Invalidate} = + decode_fast_attrs(__TopXMLNS, + _attrs, + undefined, + undefined, + undefined), + {fast, Zero_rtt, Count, Invalidate, Mechs}. + +decode_fast_els(__TopXMLNS, __Opts, [], Mechs) -> + lists:reverse(Mechs); +decode_fast_els(__TopXMLNS, __Opts, + [{xmlel, <<"mechanism">>, _attrs, _} = _el | _els], + Mechs) -> + case xmpp_codec:get_attr(<<"xmlns">>, + _attrs, + __TopXMLNS) + of + <<"urn:xmpp:fast:0">> -> + decode_fast_els(__TopXMLNS, + __Opts, + _els, + [decode_fast_mech(<<"urn:xmpp:fast:0">>, + __Opts, + _el) + | Mechs]); + _ -> decode_fast_els(__TopXMLNS, __Opts, _els, Mechs) + end; +decode_fast_els(__TopXMLNS, __Opts, [_ | _els], + Mechs) -> + decode_fast_els(__TopXMLNS, __Opts, _els, Mechs). + +decode_fast_attrs(__TopXMLNS, + [{<<"tls-0rtt">>, _val} | _attrs], _Zero_rtt, Count, + Invalidate) -> + decode_fast_attrs(__TopXMLNS, + _attrs, + _val, + Count, + Invalidate); +decode_fast_attrs(__TopXMLNS, + [{<<"count">>, _val} | _attrs], Zero_rtt, _Count, + Invalidate) -> + decode_fast_attrs(__TopXMLNS, + _attrs, + Zero_rtt, + _val, + Invalidate); +decode_fast_attrs(__TopXMLNS, + [{<<"invalidate">>, _val} | _attrs], Zero_rtt, Count, + _Invalidate) -> + decode_fast_attrs(__TopXMLNS, + _attrs, + Zero_rtt, + Count, + _val); +decode_fast_attrs(__TopXMLNS, [_ | _attrs], Zero_rtt, + Count, Invalidate) -> + decode_fast_attrs(__TopXMLNS, + _attrs, + Zero_rtt, + Count, + Invalidate); +decode_fast_attrs(__TopXMLNS, [], Zero_rtt, Count, + Invalidate) -> + {'decode_fast_attr_tls-0rtt'(__TopXMLNS, Zero_rtt), + decode_fast_attr_count(__TopXMLNS, Count), + decode_fast_attr_invalidate(__TopXMLNS, Invalidate)}. + +encode_fast({fast, Zero_rtt, Count, Invalidate, Mechs}, + __TopXMLNS) -> + __NewTopXMLNS = + xmpp_codec:choose_top_xmlns(<<"urn:xmpp:fast:0">>, + [], + __TopXMLNS), + _els = lists:reverse('encode_fast_$mechs'(Mechs, + __NewTopXMLNS, + [])), + _attrs = encode_fast_attr_invalidate(Invalidate, + encode_fast_attr_count(Count, + 'encode_fast_attr_tls-0rtt'(Zero_rtt, + xmpp_codec:enc_xmlns_attrs(__NewTopXMLNS, + __TopXMLNS)))), + {xmlel, <<"fast">>, _attrs, _els}. + +'encode_fast_$mechs'([], __TopXMLNS, _acc) -> _acc; +'encode_fast_$mechs'([Mechs | _els], __TopXMLNS, + _acc) -> + 'encode_fast_$mechs'(_els, + __TopXMLNS, + [encode_fast_mech(Mechs, __TopXMLNS) | _acc]). + +'decode_fast_attr_tls-0rtt'(__TopXMLNS, undefined) -> + undefined; +'decode_fast_attr_tls-0rtt'(__TopXMLNS, _val) -> + case catch dec_bool(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, + <<"tls-0rtt">>, + <<"fast">>, + __TopXMLNS}}); + _res -> _res + end. + +'encode_fast_attr_tls-0rtt'(undefined, _acc) -> _acc; +'encode_fast_attr_tls-0rtt'(_val, _acc) -> + [{<<"tls-0rtt">>, enc_bool(_val)} | _acc]. + +decode_fast_attr_count(__TopXMLNS, undefined) -> + undefined; +decode_fast_attr_count(__TopXMLNS, _val) -> + case catch dec_int(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, + <<"count">>, + <<"fast">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_fast_attr_count(undefined, _acc) -> _acc; +encode_fast_attr_count(_val, _acc) -> + [{<<"count">>, enc_int(_val)} | _acc]. + +decode_fast_attr_invalidate(__TopXMLNS, undefined) -> + undefined; +decode_fast_attr_invalidate(__TopXMLNS, _val) -> + case catch dec_bool(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, + <<"invalidate">>, + <<"fast">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_fast_attr_invalidate(undefined, _acc) -> _acc; +encode_fast_attr_invalidate(_val, _acc) -> + [{<<"invalidate">>, enc_bool(_val)} | _acc]. diff --git a/src/xmpp_codec.erl b/src/xmpp_codec.erl index 8dabcc7..c0114e0 100644 --- a/src/xmpp_codec.erl +++ b/src/xmpp_codec.erl @@ -342,16 +342,9 @@ get_mod(<<"failure">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>) -> rfc6120; get_mod(<<"PRIVATE">>, <<"vcard-temp">>) -> xep0054; -get_mod(<<"options">>, - <<"http://jabber.org/protocol/pubsub">>) -> - xep0060; get_mod(<<"pubsub">>, <<"http://jabber.org/protocol/pubsub">>) -> xep0060; -get_mod(<<"disable">>, <<"urn:xmpp:carbons:2">>) -> - xep0280; -get_mod(<<"mix">>, <<"urn:xmpp:mix:presence:0">>) -> - xep0403; get_mod(<<"unsupported-method">>, <<"http://jabber.org/protocol/compress">>) -> xep0138; @@ -614,6 +607,7 @@ get_mod(<<"size">>, xep0234; get_mod(<<"hash">>, <<"urn:xmpp:scram-upgrade:0">>) -> xep0480; +get_mod(<<"fast">>, <<"urn:xmpp:fast:0">>) -> xep0484; get_mod(<<"query">>, <<"http://jabber.org/protocol/stats">>) -> xep0039; @@ -878,6 +872,7 @@ get_mod(<<"CELL">>, <<"vcard-temp">>) -> xep0054; get_mod(<<"stream:stream">>, <<"jabber:component:accept">>) -> rfc6120; +get_mod(<<"token">>, <<"urn:xmpp:fast:0">>) -> xep0484; get_mod(<<"email">>, <<"jabber:iq:register">>) -> xep0077; get_mod(<<"event">>, @@ -1079,6 +1074,8 @@ get_mod(<<"TEL">>, <<"vcard-temp">>) -> xep0054; get_mod(<<"prefs">>, <<"urn:xmpp:mam:tmp">>) -> xep0313; get_mod(<<"captcha">>, <<"urn:xmpp:captcha">>) -> xep0158; +get_mod(<<"mechanism">>, <<"urn:xmpp:fast:0">>) -> + xep0484; get_mod(<<"identity">>, <<"http://jabber.org/protocol/disco#info">>) -> xep0030; @@ -1139,6 +1136,8 @@ get_mod(<<"invalid-subid">>, get_mod(<<"not-in-roster-group">>, <<"http://jabber.org/protocol/pubsub#errors">>) -> xep0060; +get_mod(<<"request-token">>, <<"urn:xmpp:fast:0">>) -> + xep0484; get_mod(<<"unsupported-encoding">>, <<"urn:ietf:params:xml:ns:xmpp-streams">>) -> rfc6120; @@ -1740,6 +1739,13 @@ get_mod(<<"internal-server-error">>, get_mod(<<"put">>, <<"eu:siacs:conversations:http:upload">>) -> xep0363; +get_mod(<<"options">>, + <<"http://jabber.org/protocol/pubsub">>) -> + xep0060; +get_mod(<<"disable">>, <<"urn:xmpp:carbons:2">>) -> + xep0280; +get_mod(<<"mix">>, <<"urn:xmpp:mix:presence:0">>) -> + xep0403; get_mod(Name, XMLNS) -> xmpp_codec_external:lookup(Name, XMLNS). @@ -1839,6 +1845,7 @@ get_mod({ps_subscribe, _, _}) -> xep0060; get_mod({adhoc_actions, _, _, _, _}) -> xep0050; get_mod({push_disable, _, _}) -> xep0357; get_mod({push_notification, _}) -> xep0357; +get_mod({fast_request_token, _}) -> xep0484; get_mod({disco_item, _, _, _}) -> xep0030; get_mod({register, _, @@ -1866,38 +1873,18 @@ get_mod({register, xep0077; get_mod({service, _, _, _, _, _, _, _, _, _, _, _}) -> xep0215; +get_mod({fast, _, _, _, _}) -> xep0484; get_mod({version, _, _, _}) -> xep0092; get_mod({muc_subscription, _, _, _}) -> p1_mucsub; get_mod({sasl2_authenticaton, _, _, _}) -> xep0388; get_mod({sasl2_authenticate, _, _, _, _}) -> xep0388; get_mod({sasl2_continue, _, _, _, _}) -> xep0388; get_mod({s2s_bidi}) -> xep0288; -get_mod({vcard_tel, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _}) -> - xep0054; get_mod({vcard_photo, _, _, _}) -> xep0054; get_mod({ps_publish, _, _}) -> xep0060; -get_mod({mam_query, _, _, _, _, _, _, _, _, _}) -> - xep0313; -get_mod({search_item, _, _, _, _, _}) -> xep0055; get_mod({xcaptcha, _}) -> xep0158; get_mod({avatar_data, _}) -> xep0084; -get_mod({hash, _, _}) -> xep0300; get_mod({fasten_apply_to, _, _, _}) -> xep0422; -get_mod({sasl2_task_data, _}) -> xep0388; get_mod({vcard_key, _, _}) -> xep0054; get_mod({sm_r, _}) -> xep0198; get_mod({mix_join, _, _, _, _, _}) -> xep0369; @@ -1989,6 +1976,7 @@ get_mod({mix_client_leave, _, _, _}) -> xep0405; get_mod({thumbnail, _, _, _, _}) -> xep0264; get_mod({delegation, _, _}) -> xep0355; get_mod({x509_challenge, _, _, _}) -> xep0417; +get_mod({fast_token, _, _}) -> xep0484; get_mod({sm_enabled, _, _, _, _, _}) -> xep0198; get_mod({privilege_perm, _, _, _, _}) -> xep0356; get_mod({message_retracted, _, _, _, _, _}) -> xep0424; @@ -2122,4 +2110,25 @@ get_mod({ps_items, _, _, _, _, _, _}) -> xep0060; get_mod({muc_subscribe, _, _, _, _}) -> p1_mucsub; get_mod({feature_csi}) -> xep0352; get_mod({ibb_open, _, _, _}) -> xep0047; +get_mod({vcard_tel, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _}) -> + xep0054; +get_mod({mam_query, _, _, _, _, _, _, _, _, _}) -> + xep0313; +get_mod({search_item, _, _, _, _, _}) -> xep0055; +get_mod({hash, _, _}) -> xep0300; +get_mod({sasl2_task_data, _}) -> xep0388; get_mod(Record) -> xmpp_codec_external:lookup(Record). diff --git a/src/xmpp_sasl.erl b/src/xmpp_sasl.erl index aaf36db..35d12e1 100644 --- a/src/xmpp_sasl.erl +++ b/src/xmpp_sasl.erl @@ -18,20 +18,19 @@ -module(xmpp_sasl). -author('alexey@process-one.net'). --export([server_new/4, server_start/5, server_step/2, +-export([server_new/5, server_start/6, server_step/2, listmech/0, format_error/2]). %% TODO: write correct types for these callbacks -type get_password_fun() :: fun(). -type check_password_fun() :: fun(). -type check_password_digest_fun() :: fun(). +-type get_fast_tokens_fun() :: fun(). -record(sasl_state, {server_host :: binary(), mech_name = <<"">> :: mechanism(), mech_state :: mech_state(), - get_password :: get_password_fun(), - check_password :: check_password_fun(), - check_password_digest :: check_password_digest_fun()}). + callbacks :: #{ atom => fun() | undefined }}). -type mechanism() :: binary(). -type mech_state() :: term(). @@ -60,10 +59,7 @@ sasl_state/0, sasl_return/0, sasl_property/0]). -callback mech_new(binary(), channel_bindings(), list(binary()), - binary(), - get_password_fun(), - check_password_fun(), - check_password_digest_fun()) -> mech_state(). + binary(), binary(), map()) -> mech_state(). -callback mech_step(mech_state(), binary()) -> sasl_return(). %%%=================================================================== @@ -97,25 +93,26 @@ listmech() -> -spec server_new(binary(), get_password_fun(), check_password_fun(), - check_password_digest_fun()) -> sasl_state(). -server_new(ServerHost, GetPassword, CheckPassword, CheckPasswordDigest) -> + check_password_digest_fun(), + get_fast_tokens_fun() | undefined) -> sasl_state(). +server_new(ServerHost, GetPassword, CheckPassword, CheckPasswordDigest, GetFastTokens) -> #sasl_state{server_host = ServerHost, - get_password = GetPassword, - check_password = CheckPassword, - check_password_digest = CheckPasswordDigest}. + callbacks = #{ + get_password => GetPassword, + get_fast_tokens => GetFastTokens, + check_password => CheckPassword, + check_password_digest => CheckPasswordDigest}}. -spec server_start(sasl_state(), mechanism(), binary(), channel_bindings(), - list(binary()) | undefined) -> sasl_return(). -server_start(State, Mech, ClientIn, ChannelBindings, Mechs) -> + list(binary()) | undefined, binary() | undefined) -> sasl_return(). +server_start(State, Mech, ClientIn, ChannelBindings, Mechs, UAId) -> case get_mod(Mech) of undefined -> {error, unsupported_mechanism, <<"">>}; Module -> - MechState = Module:mech_new(Mech, ChannelBindings, Mechs, + MechState = Module:mech_new(Mech, ChannelBindings, Mechs, UAId, State#sasl_state.server_host, - State#sasl_state.get_password, - State#sasl_state.check_password, - State#sasl_state.check_password_digest), + State#sasl_state.callbacks), State1 = State#sasl_state{mech_name = Mech, mech_state = MechState}, server_step(State1, ClientIn) @@ -167,4 +164,9 @@ get_mod(<<"SCRAM-SHA-256-PLUS">>) -> xmpp_sasl_scram; get_mod(<<"SCRAM-SHA-256">>) -> xmpp_sasl_scram; get_mod(<<"SCRAM-SHA-512">>) -> xmpp_sasl_scram; get_mod(<<"SCRAM-SHA-512-PLUS">>) -> xmpp_sasl_scram; +get_mod(<<"HT-SHA-256-NONE">>) -> xmpp_sasl_fast; +get_mod(<<"HT-SHA-256-ENDP">>) -> xmpp_sasl_fast; +get_mod(<<"HT-SHA-256-UNIQ">>) -> xmpp_sasl_fast; +get_mod(<<"HT-SHA-256-EXPR">>) -> xmpp_sasl_fast; + get_mod(_) -> undefined. diff --git a/src/xmpp_sasl_anonymous.erl b/src/xmpp_sasl_anonymous.erl index 775dfb0..798fecd 100644 --- a/src/xmpp_sasl_anonymous.erl +++ b/src/xmpp_sasl_anonymous.erl @@ -20,11 +20,11 @@ -behaviour(xmpp_sasl). -protocol({xep, 175, '1.2'}). --export([mech_new/7, mech_step/2]). +-export([mech_new/6, mech_step/2]). -record(state, {server = <<"">> :: binary()}). -mech_new(_Mech, _CB, _Mechs, Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) -> +mech_new(_Mech, _CB, _Mechs, _UAId, Host, _Callbacks) -> #state{server = Host}. mech_step(#state{}, _ClientIn) -> diff --git a/src/xmpp_sasl_digest.erl b/src/xmpp_sasl_digest.erl index d94de5b..3894131 100644 --- a/src/xmpp_sasl_digest.erl +++ b/src/xmpp_sasl_digest.erl @@ -20,7 +20,7 @@ -author('alexey@sevcom.net'). -dialyzer({no_match, [get_local_fqdn/1]}). --export([mech_new/7, mech_step/2, format_error/1]). +-export([mech_new/6, mech_step/2, format_error/1]). %% For tests -export([parse/1]). @@ -57,7 +57,8 @@ format_error(not_authorized) -> format_error(unexpected_response) -> {'not-authorized', <<"Unexpected response">>}. -mech_new(_Mech, _CB, _Mechs, Host, GetPassword, _CheckPassword, CheckPasswordDigest) -> +mech_new(_Mech, _CB, _Mechs, _UAId, Host, #{get_password := GetPassword, + check_password_digest := CheckPasswordDigest}) -> #state{step = 1, nonce = p1_rand:get_string(), host = Host, hostfqdn = get_local_fqdn(Host), get_password = GetPassword, diff --git a/src/xmpp_sasl_fast.erl b/src/xmpp_sasl_fast.erl new file mode 100644 index 0000000..89b6774 --- /dev/null +++ b/src/xmpp_sasl_fast.erl @@ -0,0 +1,128 @@ +%%%------------------------------------------------------------------- +%%% +%%% Copyright (C) 2002-2024 ProcessOne, SARL. All Rights Reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%% +%%%------------------------------------------------------------------- +-module(xmpp_sasl_fast). +-behaviour(xmpp_sasl). +-author('alexey@process-one.net'). + +-export([mech_new/6, mech_step/2, format_error/1]). +%% For tests +-export([parse/1]). + +-record(state, {mech, cb, ua, get_fast_tokens}). +-type error_reason() :: incompatible_mechs | missing_ua | bad_channel_binding | +parser_failed | not_authorized | {atom(), binary()}. +-export_type([error_reason/0]). + +-spec format_error(error_reason()) -> {atom(), binary()}. +format_error({Condition, Text}) -> + {Condition, Text}; +format_error(incompatible_mechs) -> + {'not-authorized', <<"Incompatible SCRAM methods">>}; +format_error(missing_ua) -> + {'malformed', <<"Missing user-agent">>}; +format_error(bad_channel_binding) -> + {'not-authorized', <<"Invalid channel binding">>}; +format_error(parser_failed) -> + {'not-authorized', <<"Response decoding failed">>}; +format_error(not_authorized) -> + {'not-authorized', <<"Invalid username or password">>}. + +mech_new(Mech, CB, _Mechs, UAId, _Host, #{get_fast_tokens := GetFastTokens}) -> + #state{mech = Mech, cb = CB, ua = UAId, get_fast_tokens = GetFastTokens}. + + +-ifdef(USE_OLD_CRYPTO_HMAC). +sha_mac(Algo, Key, Data) -> + crypto:hmac(Algo, Key, Data). +-else. +sha_mac(Algo, Key, Data) -> + crypto:mac(hmac, Algo, Key, Data). +-endif. + +validate_token(User, _Mech, _CB, [], _Hash) -> + {error, not_authorized, User}; +validate_token(User, Mech, CB, [{TokenId, Token} | Tokens], Hash) -> + {Hmac, CBName} = case Mech of + <<"HT-SHA-256-NONE">> -> {sha256, none}; + <<"HT-SHA-256-UNIQ">> -> {sha256, <<"tls-unique">>}; + <<"HT-SHA-256-EXPR">> -> {sha256, <<"tls-exporter">>}; + <<"HT-SHA-256-ENDP">> -> {sha256, <<"tls-server-end-point">>} + end, + CB2 = case maps:get(CBName, CB, missing) of + missing when CBName == none -> <<>>; + missing -> error; + Val -> Val + end, + case CB2 of + error -> {error, bad_channel_binding}; + _ -> + case sha_mac(Hmac, Token, <<"Initiator", CB2/binary>>) of + V when V == Hash -> + {ok, [{username, User}, + {auth_module, mod_auth_fast}, + {extra_info, {token, TokenId}}, + {authzid, User}], + sha_mac(Hmac, Token, <<"Responder", CB2/binary>>)}; + V -> + validate_token(User, Mech, CB, Tokens, Hash) + end + end. + +mech_step(#state{get_fast_tokens = undefined}, _ClientIn) -> + {error, incompatible_mechs}; +mech_step(#state{ua = undefined}, _ClientIn) -> + {error, missing_ua}; +mech_step(State, ClientIn) -> + case prepare(ClientIn) of + {User, Hash} -> + case (State#state.get_fast_tokens)(User, State#state.ua) of + [] -> + {error, not_authorized, User}; + Tokens -> + validate_token(User, State#state.mech, State#state.cb, + Tokens, Hash) + end; + error -> + {error, parser_failed} + end. + +-spec prepare(binary()) -> {binary(), binary(), binary()} | error. +prepare(ClientIn) -> + case parse(ClientIn) of + [User, Hash] -> + case parse_authzid(User) of + {ok, User} -> + {User, Hash}; + _ -> + error + end; + _ -> + error + end. + +-spec parse(binary()) -> [binary()]. +parse(S) -> + binary:split(S, <<0>>). + +-spec parse_authzid(binary()) -> {ok, binary()} | error. +parse_authzid(S) -> + case binary:split(S, <<$@>>) of + [User] -> {ok, User}; + [User, _Domain] -> {ok, User}; + _ -> error + end. diff --git a/src/xmpp_sasl_oauth.erl b/src/xmpp_sasl_oauth.erl index 6172b64..8c3e7f1 100644 --- a/src/xmpp_sasl_oauth.erl +++ b/src/xmpp_sasl_oauth.erl @@ -19,7 +19,7 @@ -behaviour(xmpp_sasl). -author('alexey@process-one.net'). --export([mech_new/7, mech_step/2, format_error/1]). +-export([mech_new/6, mech_step/2, format_error/1]). %% For tests -export([parse/1]). @@ -34,7 +34,7 @@ format_error(parser_failed) -> format_error(not_authorized) -> {'not-authorized', <<"Invalid token">>}. -mech_new(_Mech, _CB, _Mechs, Host, _GetPassword, CheckPassword, _CheckPasswordDigest) -> +mech_new(_Mech, _CB, _Mechs, _UAId, Host, #{check_password := CheckPassword}) -> #state{host = Host, check_password = CheckPassword}. mech_step(State, ClientIn) -> diff --git a/src/xmpp_sasl_plain.erl b/src/xmpp_sasl_plain.erl index 4699d39..64a7cc2 100644 --- a/src/xmpp_sasl_plain.erl +++ b/src/xmpp_sasl_plain.erl @@ -19,7 +19,7 @@ -behaviour(xmpp_sasl). -author('alexey@process-one.net'). --export([mech_new/7, mech_step/2, format_error/1]). +-export([mech_new/6, mech_step/2, format_error/1]). %% For tests -export([parse/1]). @@ -35,7 +35,7 @@ format_error(parser_failed) -> format_error(not_authorized) -> {'not-authorized', <<"Invalid username or password">>}. -mech_new(_Mech, _CB, _Mechs, _Host, _GetPassword, CheckPassword, _CheckPasswordDigest) -> +mech_new(_Mech, _CB, _Mechs, _UAId, _Host, #{check_password := CheckPassword}) -> #state{check_password = CheckPassword}. mech_step(State, ClientIn) -> diff --git a/src/xmpp_sasl_scram.erl b/src/xmpp_sasl_scram.erl index 196b65f..6a7122e 100644 --- a/src/xmpp_sasl_scram.erl +++ b/src/xmpp_sasl_scram.erl @@ -22,7 +22,7 @@ -protocol({rfc, 5802}). -protocol({xep, 474, '0.3.0'}). --export([mech_new/7, mech_step/2, format_error/1]). +-export([mech_new/6, mech_step/2, format_error/1]). -include("scram.hrl"). @@ -76,7 +76,7 @@ format_error(bad_channel_binding) -> format_error(incompatible_mechs) -> {'not-authorized', <<"Incompatible SCRAM methods">>}. -mech_new(Mech, ChannelBindings, Mechs, _Host, GetPassword, _CheckPassword, _CheckPasswordDigest) -> +mech_new(Mech, ChannelBindings, Mechs, _UAId, _Host, #{get_password := GetPassword}) -> {Algo, CB} = case Mech of <<"SCRAM-SHA-1">> -> {sha, none}; diff --git a/src/xmpp_stream_in.erl b/src/xmpp_stream_in.erl index 97471bc..1c8eda0 100644 --- a/src/xmpp_stream_in.erl +++ b/src/xmpp_stream_in.erl @@ -105,6 +105,7 @@ -callback get_password_fun(xmpp_sasl:mechanism(), state()) -> fun(). -callback check_password_fun(xmpp_sasl:mechanism(), state()) -> fun(). -callback check_password_digest_fun(xmpp_sasl:mechanism(), state()) -> fun(). +-callback get_fast_tokens_fun(xmpp_sasl:mechanism(), state()) -> fun(). -callback bind(binary(), state()) -> {ok, state()} | {error, stanza_error(), state()}. -callback compress_methods(state()) -> [binary()]. -callback tls_options(state()) -> [proplists:property()]. @@ -139,6 +140,7 @@ get_password_fun/2, check_password_fun/2, check_password_digest_fun/2, + get_fast_tokens_fun/2, bind/2, compress_methods/1, tls_options/1, @@ -976,9 +978,9 @@ process_sasl_request(#sasl_auth{mechanism = Mech, text = ClientIn}, end catch _:{?MODULE, undef} -> Mechs end, - SASLState = xmpp_sasl:server_new(LServer, GetPW, CheckPW, CheckPWDigest), + SASLState = xmpp_sasl:server_new(LServer, GetPW, CheckPW, CheckPWDigest, undefined), CB = maps:get(sasl_channel_bindings, State1, none), - Res = xmpp_sasl:server_start(SASLState, Mech, ClientIn, CB, Mechs2), + Res = xmpp_sasl:server_start(SASLState, Mech, ClientIn, CB, Mechs2, undefined), process_sasl_result(Res, disable_sasl2(State1#{sasl_state => SASLState})); false -> process_sasl_result({error, unsupported_mechanism, <<"">>}, disable_sasl2(State1)) @@ -1064,7 +1066,10 @@ process_sasl2_request(#sasl2_authenticate{mechanism = Mech, initial_response = C user_agent = UA} = Pkt, #{lserver := LServer} = State) -> State1 = State#{sasl_mech => Mech}, - Mechs = get_sasl_mechanisms(State1), + FastMechs = try callback(fast_mechanisms, State) + catch _:{?MODULE, undef} -> [] + end, + Mechs = get_sasl_mechanisms(State1) ++ FastMechs, UAId = case UA of #sasl2_user_agent{id = ID} when ID /= <<>> -> ID; @@ -1089,6 +1094,7 @@ process_sasl2_request(#sasl2_authenticate{mechanism = Mech, initial_response = C GetPW = get_password_fun(Mech, State1), CheckPW = check_password_fun(Mech, State1), CheckPWDigest = check_password_digest_fun(Mech, State1), + GetFastTokens = get_fast_tokens_fun(Mech, State1), Mechs2 = try callback(sasl_options, State) of Opts -> case lists:keyfind(scram_downgrade_protection, 1, Opts) of @@ -1097,9 +1103,10 @@ process_sasl2_request(#sasl2_authenticate{mechanism = Mech, initial_response = C end catch _:{?MODULE, undef} -> Mechs end, - SASLState = xmpp_sasl:server_new(LServer, GetPW, CheckPW, CheckPWDigest), + SASLState = xmpp_sasl:server_new(LServer, GetPW, CheckPW, + CheckPWDigest, GetFastTokens), CB = maps:get(sasl_channel_bindings, State1, none), - Res = xmpp_sasl:server_start(SASLState, Mech, ClientIn, CB, Mechs2), + Res = xmpp_sasl:server_start(SASLState, Mech, ClientIn, CB, Mechs2, UAId), process_sasl2_result(Res, State1#{sasl_state => SASLState, sasl2_inline_els => SaslInline, sasl2_ua_id => UAId}); @@ -1133,8 +1140,10 @@ process_sasl2_success(Props, ServerOut, process_sasl2_failure(not_authorized, User, State); true -> AuthModule = proplists:get_value(auth_module, Props), - State1 = try callback(handle_auth_success, User, Mech, AuthModule, State) - catch _:{?MODULE, undef} -> State + ExtraAuthInfo = proplists:get_value(extra_info, Props), + State0 = State#{sasl2_axtra_auth_info => ExtraAuthInfo}, + State1 = try callback(handle_auth_success, User, Mech, AuthModule, State0) + catch _:{?MODULE, undef} -> State0 end, case is_disconnected(State1) of true -> State1; @@ -1209,7 +1218,7 @@ process_sasl2_post_success(NewEls, Results, User, ServerOut, false -> map_remove_keys(State5, [sasl2_stream_from, sasl2_inline_els, sasl2_ua_id, sasl_state, sasl_mech, - sasl_channel_bindings]) + sasl_channel_bindings, sasl2_extra_auth_info]) end end. @@ -1371,6 +1380,12 @@ check_password_digest_fun(Mech, State) -> catch _:{?MODULE, undef} -> fun(_, _, _, _, _) -> {false, undefined} end end. +-spec get_fast_tokens_fun(xmpp_sasl:mechanism(), state()) -> fun(). +get_fast_tokens_fun(Mech, State) -> + try callback(get_fast_tokens_fun, Mech, State) + catch _:{?MODULE, undef} -> fun(_, _) -> [] end + end. + -spec get_sasl_mechanisms(state()) -> [xmpp_sasl:mechanism()]. get_sasl_mechanisms(#{stream_encrypted := Encrypted, xmlns := NS} = State) ->