diff options
Diffstat (limited to 'src/ejabberd_s2s_in.erl')
-rw-r--r-- | src/ejabberd_s2s_in.erl | 1166 |
1 files changed, 581 insertions, 585 deletions
diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 6ae4f3446..2dc7c86b2 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -25,119 +25,110 @@ %%%---------------------------------------------------------------------- -module(ejabberd_s2s_in). + -author('alexey@process-one.net'). -behaviour(p1_fsm). %% External exports --export([start/2, - start_link/2, - match_domain/2, +-export([start/2, start_link/2, match_domain/2, socket_type/0]). %% gen_fsm callbacks --export([init/1, - wait_for_stream/2, - wait_for_feature_request/2, - stream_established/2, - handle_event/3, - handle_sync_event/4, - code_change/4, - handle_info/3, - print_state/1, - terminate/3]). +-export([init/1, wait_for_stream/2, + wait_for_feature_request/2, stream_established/2, + handle_event/3, handle_sync_event/4, code_change/4, + handle_info/3, print_state/1, terminate/3]). -include("ejabberd.hrl"). + -include("jlib.hrl"). --ifdef(SSL40). --include_lib("public_key/include/public_key.hrl"). + +-include_lib("public_key/include/public_key.hrl"). + -define(PKIXEXPLICIT, 'OTP-PUB-KEY'). + -define(PKIXIMPLICIT, 'OTP-PUB-KEY'). --else. --ifdef(SSL39). --include_lib("ssl/include/ssl_pkix.hrl"). --define(PKIXEXPLICIT, 'OTP-PKIX'). --define(PKIXIMPLICIT, 'OTP-PKIX'). --else. --include_lib("ssl/include/PKIX1Explicit88.hrl"). --include_lib("ssl/include/PKIX1Implicit88.hrl"). --define(PKIXEXPLICIT, 'PKIX1Explicit88'). --define(PKIXIMPLICIT, 'PKIX1Implicit88'). --endif. --endif. + -include("XmppAddr.hrl"). -define(DICT, dict). --record(state, {socket, - sockmod, - streamid, - shaper, - tls = false, - tls_enabled = false, - tls_required = false, - tls_certverify = false, - tls_options = [], - server, - authenticated = false, - auth_domain, - connections = ?DICT:new(), - timer}). - +-record(state, + {socket :: ejabberd_socket:socket_state(), + sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket, + streamid = <<"">> :: binary(), + shaper = none :: shaper:shaper(), + tls = false :: boolean(), + tls_enabled = false :: boolean(), + tls_required = false :: boolean(), + tls_certverify = false :: boolean(), + tls_options = [] :: list(), + server = <<"">> :: binary(), + authenticated = false :: boolean(), + auth_domain = <<"">> :: binary(), + connections = (?DICT):new() :: dict(), + timer = make_ref() :: reference()}). %-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). --endif. --define(FSMLIMITS, [{max_queue, 2000}]). %% if queue grows more than this, we shutdown this connection. +-endif. %% Module start with or without supervisor: -ifdef(NO_TRANSIENT_SUPERVISORS). --define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_in, [SockData, Opts], - ?FSMOPTS ++ ?FSMLIMITS)). + +-define(SUPERVISOR_START, + p1_fsm:start(ejabberd_s2s_in, [SockData, Opts], + ?FSMOPTS ++ fsm_limit_opts(Opts)). + -else. --define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_in_sup, - [SockData, Opts])). + +-define(SUPERVISOR_START, + supervisor:start_child(ejabberd_s2s_in_sup, + [SockData, Opts])). + -endif. -define(STREAM_HEADER(Version), - ("<?xml version='1.0'?>" - "<stream:stream " - "xmlns:stream='http://etherx.jabber.org/streams' " - "xmlns='jabber:server' " - "xmlns:db='jabber:server:dialback' " - "id='" ++ StateData#state.streamid ++ "'" ++ Version ++ ">") - ). + <<"<?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(STREAM_TRAILER, <<"</stream:stream>">>). -define(INVALID_NAMESPACE_ERR, - xml:element_to_string(?SERR_INVALID_NAMESPACE)). + xml:element_to_binary(?SERR_INVALID_NAMESPACE)). -define(HOST_UNKNOWN_ERR, - xml:element_to_string(?SERR_HOST_UNKNOWN)). + xml:element_to_binary(?SERR_HOST_UNKNOWN)). -define(INVALID_FROM_ERR, - xml:element_to_string(?SERR_INVALID_FROM)). + xml:element_to_binary(?SERR_INVALID_FROM)). -define(INVALID_XML_ERR, - xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)). + xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -start(SockData, Opts) -> - ?SUPERVISOR_START. +start(SockData, Opts) -> ?SUPERVISOR_START. start_link(SockData, Opts) -> - p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS ++ ?FSMLIMITS). + p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], + ?FSMOPTS ++ fsm_limit_opts(Opts)). -socket_type() -> - xml_stream. +socket_type() -> xml_stream. %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm @@ -153,36 +144,44 @@ socket_type() -> init([{SockMod, Socket}, Opts]) -> ?DEBUG("started: ~p", [{SockMod, Socket}]), Shaper = case lists:keysearch(shaper, 1, Opts) of - {value, {_, S}} -> S; - _ -> none + {value, {_, S}} -> S; + _ -> none end, - {StartTLS, TLSRequired, TLSCertverify} = case ejabberd_config:get_local_option(s2s_use_starttls) of - UseTls when (UseTls==undefined) or (UseTls==false) -> - {false, false, false}; - UseTls when (UseTls==true) or (UseTls==optional) -> - {true, false, false}; - required -> - {true, true, false}; - required_trusted -> - {true, true, true} - end, - TLSOpts = case ejabberd_config:get_local_option(s2s_certfile) of - undefined -> - []; - CertFile -> - [{certfile, CertFile}] + {StartTLS, TLSRequired, TLSCertverify} = + case ejabberd_config:get_local_option( + s2s_use_starttls, + fun(false) -> false; + (true) -> true; + (optional) -> optional; + (required) -> required; + (required_trusted) -> required_trusted + end, + false) of + UseTls + when (UseTls == undefined) or + (UseTls == false) -> + {false, false, false}; + UseTls + when (UseTls == true) or + (UseTls == + optional) -> + {true, false, false}; + required -> {true, true, false}; + required_trusted -> + {true, true, true} + end, + TLSOpts = case ejabberd_config:get_local_option( + s2s_certfile, + fun iolist_to_binary/1) of + undefined -> []; + CertFile -> [{certfile, CertFile}] end, Timer = erlang:start_timer(?S2STIMEOUT, self(), []), {ok, wait_for_stream, - #state{socket = Socket, - sockmod = SockMod, - streamid = new_id(), - shaper = Shaper, - tls = StartTLS, - tls_enabled = false, - tls_required = TLSRequired, - tls_certverify = TLSCertverify, - tls_options = TLSOpts, + #state{socket = Socket, sockmod = SockMod, + streamid = new_id(), shaper = Shaper, tls = StartTLS, + tls_enabled = false, tls_required = TLSRequired, + tls_certverify = TLSCertverify, tls_options = TLSOpts, timer = Timer}}. %%---------------------------------------------------------------------- @@ -192,373 +191,371 @@ init([{SockMod, Socket}, Opts]) -> %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- -wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> - case {xml:get_attr_s("xmlns", Attrs), - xml:get_attr_s("xmlns:db", Attrs), - xml:get_attr_s("to", Attrs), - xml: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'")), - SASL = - if - StateData#state.tls_enabled -> - case (StateData#state.sockmod):get_peer_certificate( - StateData#state.socket) of - {ok, Cert} -> - case (StateData#state.sockmod):get_verify_result(StateData#state.socket) of - 0 -> - [{xmlelement, "mechanisms", - [{"xmlns", ?NS_SASL}], - [{xmlelement, "mechanism", [], - [{xmlcdata, "EXTERNAL"}]}]}]; - CertVerifyRes -> - case StateData#state.tls_certverify of - true -> {error_cert_verif, CertVerifyRes, Cert}; - false -> [] - end - end; - error -> - [] +wait_for_stream({xmlstreamstart, _Name, Attrs}, + StateData) -> + case {xml:get_attr_s(<<"xmlns">>, Attrs), + xml:get_attr_s(<<"xmlns:db">>, Attrs), + xml:get_attr_s(<<"to">>, Attrs), + xml: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'">>)), + SASL = if StateData#state.tls_enabled -> + case + (StateData#state.sockmod):get_peer_certificate(StateData#state.socket) + of + {ok, Cert} -> + case + (StateData#state.sockmod):get_verify_result(StateData#state.socket) + of + 0 -> + [#xmlel{name = <<"mechanisms">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = <<"mechanism">>, + attrs = [], + children = + [{xmlcdata, + <<"EXTERNAL">>}]}]}]; + CertVerifyRes -> + case StateData#state.tls_certverify of + true -> + {error_cert_verif, CertVerifyRes, + Cert}; + false -> [] + end + end; + error -> [] end; - true -> - [] - end, - StartTLS = if - StateData#state.tls_enabled -> - []; - (not StateData#state.tls_enabled) and (not StateData#state.tls_required) -> - [{xmlelement, "starttls", [{"xmlns", ?NS_TLS}], []}]; - (not StateData#state.tls_enabled) and StateData#state.tls_required -> - [{xmlelement, "starttls", [{"xmlns", ?NS_TLS}], - [{xmlelement, "required", [], []}] - }] - end, - case SASL of - {error_cert_verif, CertVerifyResult, Certificate} -> - CertError = tls:get_cert_verify_string(CertVerifyResult, Certificate), - RemoteServer = xml:get_attr_s("from", Attrs), - ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)", [StateData#state.server, RemoteServer, CertError]), - send_text(StateData, xml:element_to_string(?SERRT_POLICY_VIOLATION("en", CertError))), - {atomic, Pid} = ejabberd_s2s:find_connection(jlib:make_jid("", Server, ""), jlib:make_jid("", RemoteServer, "")), - ejabberd_s2s_out:stop_connection(Pid), - - {stop, normal, StateData}; - _ -> - send_element(StateData, - {xmlelement, "stream:features", [], - SASL ++ StartTLS ++ - ejabberd_hooks:run_fold( - s2s_stream_features, - Server, - [], [Server])}), - {next_state, wait_for_feature_request, StateData#state{server = Server}} - end; - {"jabber:server", _, Server, true} when - StateData#state.authenticated -> - send_text(StateData, ?STREAM_HEADER(" version='1.0'")), - send_element(StateData, - {xmlelement, "stream:features", [], - ejabberd_hooks:run_fold( - s2s_stream_features, - Server, - [], [Server])}), - {next_state, stream_established, StateData}; - {"jabber:server", "jabber:server:dialback", _Server, _} -> - send_text(StateData, ?STREAM_HEADER("")), - {next_state, stream_established, StateData}; - _ -> - send_text(StateData, ?INVALID_NAMESPACE_ERR), - {stop, normal, StateData} + true -> [] + 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 + StateData#state.tls_required -> + [#xmlel{name = <<"starttls">>, + attrs = [{<<"xmlns">>, ?NS_TLS}], + children = + [#xmlel{name = <<"required">>, + attrs = [], children = []}]}] + end, + case SASL of + {error_cert_verif, CertVerifyResult, Certificate} -> + CertError = tls:get_cert_verify_string(CertVerifyResult, + Certificate), + RemoteServer = xml:get_attr_s(<<"from">>, Attrs), + ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)", + [StateData#state.server, RemoteServer, CertError]), + send_text(StateData, + xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>, + CertError))), + {atomic, Pid} = + ejabberd_s2s:find_connection(jlib:make_jid(<<"">>, + Server, <<"">>), + jlib:make_jid(<<"">>, + RemoteServer, + <<"">>)), + ejabberd_s2s_out:stop_connection(Pid), + {stop, normal, StateData}; + _ -> + send_element(StateData, + #xmlel{name = <<"stream:features">>, attrs = [], + children = + SASL ++ + StartTLS ++ + ejabberd_hooks:run_fold(s2s_stream_features, + Server, [], + [Server])}), + {next_state, wait_for_feature_request, + StateData#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, _} -> + send_text(StateData, ?STREAM_HEADER(<<"">>)), + {next_state, stream_established, StateData}; + _ -> + send_text(StateData, ?INVALID_NAMESPACE_ERR), + {stop, normal, StateData} end; - wait_for_stream({xmlstreamerror, _}, StateData) -> send_text(StateData, - ?STREAM_HEADER("") ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER), + <<(?STREAM_HEADER(<<"">>))/binary, + (?INVALID_XML_ERR)/binary, (?STREAM_TRAILER)/binary>>), {stop, normal, StateData}; - wait_for_stream(timeout, StateData) -> {stop, normal, StateData}; - wait_for_stream(closed, StateData) -> {stop, normal, StateData}. - -wait_for_feature_request({xmlstreamelement, El}, StateData) -> - {xmlelement, Name, Attrs, Els} = El, +wait_for_feature_request({xmlstreamelement, El}, + StateData) -> + #xmlel{name = Name, attrs = Attrs, children = Els} = El, TLS = StateData#state.tls, TLSEnabled = StateData#state.tls_enabled, - SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket), - case {xml:get_attr_s("xmlns", Attrs), Name} of - {?NS_TLS, "starttls"} when TLS == true, - TLSEnabled == false, - SockMod == gen_tcp -> - ?DEBUG("starttls", []), - Socket = StateData#state.socket, - TLSOpts = case ejabberd_config:get_local_option( - {domain_certfile, - StateData#state.server}) of - undefined -> - StateData#state.tls_options; - CertFile -> - [{certfile, CertFile} | - lists:keydelete( - certfile, 1, - StateData#state.tls_options)] - end, - TLSSocket = (StateData#state.sockmod):starttls( - Socket, TLSOpts, - xml:element_to_binary( - {xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})), - {next_state, wait_for_stream, - StateData#state{socket = TLSSocket, - streamid = new_id(), - tls_enabled = true, - tls_options = TLSOpts - }}; - {?NS_SASL, "auth"} when TLSEnabled -> - Mech = xml:get_attr_s("mechanism", Attrs), - case Mech of - "EXTERNAL" -> - Auth = jlib:decode_base64(xml:get_cdata(Els)), - AuthDomain = jlib:nameprep(Auth), - AuthRes = - case (StateData#state.sockmod):get_peer_certificate( - StateData#state.socket) of + SockMod = + (StateData#state.sockmod):get_sockmod(StateData#state.socket), + case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of + {?NS_TLS, <<"starttls">>} + when TLS == true, TLSEnabled == false, + SockMod == gen_tcp -> + ?DEBUG("starttls", []), + Socket = StateData#state.socket, + TLSOpts = case + ejabberd_config:get_local_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, + TLSSocket = (StateData#state.sockmod):starttls(Socket, + TLSOpts, + xml: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 = xml:get_attr_s(<<"mechanism">>, Attrs), + case Mech of + <<"EXTERNAL">> -> + Auth = jlib:decode_base64(xml:get_cdata(Els)), + AuthDomain = jlib:nameprep(Auth), + AuthRes = case + (StateData#state.sockmod):get_peer_certificate(StateData#state.socket) + of {ok, Cert} -> - case (StateData#state.sockmod):get_verify_result( - StateData#state.socket) of - 0 -> - case AuthDomain of - error -> - false; - _ -> - case idna:domain_utf8_to_ascii(AuthDomain) of - false -> - false; - PCAuthDomain -> - lists:any( - fun(D) -> - match_domain( - PCAuthDomain, D) - end, get_cert_domains(Cert)) - end - end; - _ -> - false + case + (StateData#state.sockmod):get_verify_result(StateData#state.socket) + of + 0 -> + case AuthDomain of + error -> false; + _ -> + case + idna:domain_utf8_to_ascii(AuthDomain) + of + false -> false; + PCAuthDomain -> + lists:any(fun (D) -> + match_domain(PCAuthDomain, + D) + end, + get_cert_domains(Cert)) + end + end; + _ -> false end; - error -> - false - end, - AllowRemoteHost = ejabberd_s2s:allow_host("", AuthDomain), - if - AuthRes andalso AllowRemoteHost -> - (StateData#state.sockmod):reset_stream( - StateData#state.socket), - send_element(StateData, - {xmlelement, "success", - [{"xmlns", ?NS_SASL}], []}), - ?DEBUG("(~w) Accepted s2s authentication for ~s", - [StateData#state.socket, AuthDomain]), - - %% acess rules are first checked against the globally defined ones, that have precedence over - %% domain-specific ones.. http://www.process-one.net/docs/ejabberd/guide_en.html#AccessRights - %% since there is allways a shaper defined globally for s2s, it doesn't matter the actual - %% local host, since the globall one will be used, even if this domain has a special rule - change_shaper(StateData, "", jlib:make_jid("", AuthDomain, "")), - {next_state, wait_for_stream, - StateData#state{streamid = new_id(), - authenticated = true, - auth_domain = AuthDomain - }}; - true -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], []}), - send_text(StateData, ?STREAM_TRAILER), - {stop, normal, StateData} - end; - _ -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], - [{xmlelement, "invalid-mechanism", [], []}]}), - {stop, normal, StateData} - end; - _ -> - stream_established({xmlstreamelement, El}, StateData) + error -> false + end, + AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>, + AuthDomain), + if AuthRes andalso AllowRemoteHost -> + (StateData#state.sockmod):reset_stream(StateData#state.socket), + send_element(StateData, + #xmlel{name = <<"success">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = []}), + ?DEBUG("(~w) Accepted s2s authentication for ~s", + [StateData#state.socket, AuthDomain]), + change_shaper(StateData, <<"">>, + jlib:make_jid(<<"">>, AuthDomain, <<"">>)), + {next_state, wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_domain = AuthDomain}}; + 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) end; - -wait_for_feature_request({xmlstreamend, _Name}, StateData) -> +wait_for_feature_request({xmlstreamend, _Name}, + StateData) -> send_text(StateData, ?STREAM_TRAILER), {stop, normal, StateData}; - -wait_for_feature_request({xmlstreamerror, _}, StateData) -> - send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER), +wait_for_feature_request({xmlstreamerror, _}, + StateData) -> + send_text(StateData, + <<(?INVALID_XML_ERR)/binary, + (?STREAM_TRAILER)/binary>>), {stop, normal, StateData}; - wait_for_feature_request(closed, StateData) -> {stop, normal, 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 = jlib:nameprep(To), - LFrom = jlib:nameprep(From), - %% Checks if the from domain is allowed and if the to - %% domain is handled by this server: - 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, jlib:make_jid("", 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 = jlib:nameprep(To), - LFrom = jlib:nameprep(From), - Type = case ejabberd_s2s:has_key({LTo, LFrom}, Key) of - true -> "valid"; - _ -> "invalid" - end, - %Type = if Key == Key1 -> "valid"; - % true -> "invalid" - % end, - send_element(StateData, - {xmlelement, - "db:verify", - [{"from", To}, - {"to", From}, - {"id", Id}, - {"type", Type}], - []}), - {next_state, stream_established, StateData#state{timer = Timer}}; - _ -> - NewEl = jlib:remove_attr("xmlns", El), - {xmlelement, Name, Attrs, _Els} = NewEl, - From_s = xml:get_attr_s("from", Attrs), - From = jlib:string_to_jid(From_s), - To_s = xml:get_attr_s("to", Attrs), - To = jlib:string_to_jid(To_s), - 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}} + {key, To, From, Id, Key} -> + ?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]), + LTo = jlib:nameprep(To), + LFrom = jlib: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, + jlib:make_jid(<<"">>, 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 = jlib:nameprep(To), + LFrom = jlib:nameprep(From), + Type = case ejabberd_s2s:has_key({LTo, LFrom}, Key) of + true -> <<"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 = xml:get_attr_s(<<"from">>, Attrs), + From = jlib:string_to_jid(From_s), + To_s = xml:get_attr_s(<<"to">>, Attrs), + To = jlib:string_to_jid(To_s), + 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}} end; - stream_established({valid, From, To}, StateData) -> send_element(StateData, - {xmlelement, - "db:result", - [{"from", To}, - {"to", From}, - {"type", "valid"}], - []}), + #xmlel{name = <<"db:result">>, + attrs = + [{<<"from">>, To}, {<<"to">>, From}, + {<<"type">>, <<"valid">>}], + children = []}), LFrom = jlib:nameprep(From), LTo = jlib:nameprep(To), - NSD = StateData#state{ - connections = ?DICT:store({LFrom, LTo}, established, - StateData#state.connections)}, + NSD = StateData#state{connections = + (?DICT):store({LFrom, LTo}, established, + StateData#state.connections)}, {next_state, stream_established, NSD}; - stream_established({invalid, From, To}, StateData) -> send_element(StateData, - {xmlelement, - "db:result", - [{"from", To}, - {"to", From}, - {"type", "invalid"}], - []}), + #xmlel{name = <<"db:result">>, + attrs = + [{<<"from">>, To}, {<<"to">>, From}, + {<<"type">>, <<"invalid">>}], + children = []}), LFrom = jlib:nameprep(From), LTo = jlib:nameprep(To), - NSD = StateData#state{ - connections = ?DICT:erase({LFrom, LTo}, - StateData#state.connections)}, + NSD = StateData#state{connections = + (?DICT):erase({LFrom, LTo}, + StateData#state.connections)}, {next_state, stream_established, NSD}; - stream_established({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; - stream_established({xmlstreamerror, _}, StateData) -> send_text(StateData, - ?INVALID_XML_ERR ++ ?STREAM_TRAILER), + <<(?INVALID_XML_ERR)/binary, + (?STREAM_TRAILER)/binary>>), {stop, normal, StateData}; - stream_established(timeout, StateData) -> {stop, normal, StateData}; - stream_established(closed, StateData) -> {stop, normal, StateData}. - - %%---------------------------------------------------------------------- %% Func: StateName/3 %% Returns: {next_state, NextStateName, NextStateData} | @@ -586,32 +583,30 @@ handle_event(_Event, StateName, StateData) -> %% {reply, Reply, NextStateName, NextStateData} %% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()] %%---------------------------------------------------------------------- -handle_sync_event(get_state_infos, _From, StateName, StateData) -> + +handle_sync_event(get_state_infos, _From, StateName, + StateData) -> SockMod = StateData#state.sockmod, - {Addr,Port} = try SockMod:peername(StateData#state.socket) of - {ok, {A,P}} -> {A,P}; - {error, _} -> {unknown,unknown} - catch - _:_ -> {unknown,unknown} - end, - Domains = get_external_hosts(StateData), - Infos = [ - {direction, in}, - {statename, StateName}, - {addr, Addr}, - {port, Port}, + {Addr, Port} = try + SockMod:peername(StateData#state.socket) + of + {ok, {A, P}} -> {A, P}; + {error, _} -> {unknown, unknown} + catch + _:_ -> {unknown, unknown} + end, + Domains = get_external_hosts(StateData), + Infos = [{direction, in}, {statename, StateName}, + {addr, Addr}, {port, Port}, {streamid, StateData#state.streamid}, {tls, StateData#state.tls}, {tls_enabled, StateData#state.tls_enabled}, {tls_options, StateData#state.tls_options}, {authenticated, StateData#state.authenticated}, - {shaper, StateData#state.shaper}, - {sockmod, SockMod}, - {domains, Domains} - ], + {shaper, StateData#state.shaper}, {sockmod, SockMod}, + {domains, Domains}], Reply = {state_infos, Infos}, - {reply,Reply,StateName,StateData}; - + {reply, Reply, StateName, StateData}; %%---------------------------------------------------------------------- %% Func: handle_sync_event/4 %% Returns: {next_state, NextStateName, NextStateData} | @@ -621,9 +616,9 @@ handle_sync_event(get_state_infos, _From, StateName, StateData) -> %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- -handle_sync_event(_Event, _From, StateName, StateData) -> - Reply = ok, - {reply, Reply, StateName, StateData}. +handle_sync_event(_Event, _From, StateName, + StateData) -> + Reply = ok, {reply, Reply, StateName, StateData}. code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. @@ -637,15 +632,12 @@ 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, #state{timer = Timer} = StateData) -> {stop, normal, StateData}; - handle_info(_, StateName, StateData) -> {next_state, StateName, StateData}. - %%---------------------------------------------------------------------- %% Func: terminate/3 %% Purpose: Shutdown the fsm @@ -654,21 +646,21 @@ handle_info(_, StateName, StateData) -> terminate(Reason, _StateName, StateData) -> ?DEBUG("terminated: ~p", [Reason]), case Reason of - {process_limit, _} -> - [ejabberd_s2s:external_host_overloaded(Host) || Host <- get_external_hosts(StateData)]; - _ -> - ok + {process_limit, _} -> + [ejabberd_s2s:external_host_overloaded(Host) + || Host <- get_external_hosts(StateData)]; + _ -> ok end, (StateData#state.sockmod):close(StateData#state.socket), ok. get_external_hosts(StateData) -> case StateData#state.authenticated of - true -> - [StateData#state.auth_domain]; - false -> - Connections = StateData#state.connections, - [D || {{D, _}, established} <- dict:to_list(Connections)] + true -> [StateData#state.auth_domain]; + false -> + Connections = StateData#state.connections, + [D + || {{D, _}, established} <- dict:to_list(Connections)] end. %%---------------------------------------------------------------------- @@ -676,168 +668,172 @@ get_external_hosts(StateData) -> %% Purpose: Prepare the state to be printed on error log %% Returns: State to print %%---------------------------------------------------------------------- -print_state(State) -> - State. +print_state(State) -> State. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- send_text(StateData, Text) -> - (StateData#state.sockmod):send(StateData#state.socket, Text). + (StateData#state.sockmod):send(StateData#state.socket, + Text). send_element(StateData, El) -> send_text(StateData, xml:element_to_binary(El)). - change_shaper(StateData, Host, JID) -> - Shaper = acl:match_rule(Host, StateData#state.shaper, JID), - (StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper). - + Shaper = acl:match_rule(Host, StateData#state.shaper, + JID), + (StateData#state.sockmod):change_shaper(StateData#state.socket, + Shaper). -new_id() -> - randoms:get_string(). +new_id() -> randoms:get_string(). cancel_timer(Timer) -> erlang:cancel_timer(Timer), - receive - {timeout, Timer, _} -> - ok - after 0 -> - ok - end. - - -is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:result" -> - {key, - xml:get_attr_s("to", Attrs), - xml:get_attr_s("from", Attrs), - xml:get_attr_s("id", Attrs), - xml:get_cdata(Els)}; -is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:verify" -> - {verify, - xml:get_attr_s("to", Attrs), - xml:get_attr_s("from", Attrs), - xml:get_attr_s("id", Attrs), - xml:get_cdata(Els)}; -is_key_packet(_) -> - false. - + receive {timeout, Timer, _} -> ok after 0 -> ok end. + +is_key_packet(#xmlel{name = Name, attrs = Attrs, + children = Els}) + when Name == <<"db:result">> -> + {key, xml:get_attr_s(<<"to">>, Attrs), + xml:get_attr_s(<<"from">>, Attrs), + xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)}; +is_key_packet(#xmlel{name = Name, attrs = Attrs, + children = Els}) + when Name == <<"db:verify">> -> + {verify, xml:get_attr_s(<<"to">>, Attrs), + xml:get_attr_s(<<"from">>, Attrs), + xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)}; +is_key_packet(_) -> false. get_cert_domains(Cert) -> {rdnSequence, Subject} = (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject, Extensions = (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.extensions, - lists:flatmap( - fun(#'AttributeTypeAndValue'{type = ?'id-at-commonName', - value = Val}) -> - case ?PKIXEXPLICIT:decode('X520CommonName', Val) of - {ok, {_, D1}} -> - D = if - is_list(D1) -> D1; - is_binary(D1) -> binary_to_list(D1); - true -> error - end, - if - D /= error -> - case jlib:string_to_jid(D) of - #jid{luser = "", - lserver = LD, - lresource = ""} -> - [LD]; - _ -> - [] - end; - true -> - [] - end; - _ -> - [] - end; - (_) -> - [] - end, lists:flatten(Subject)) ++ - lists:flatmap( - fun(#'Extension'{extnID = ?'id-ce-subjectAltName', - extnValue = Val}) -> - BVal = if - is_list(Val) -> list_to_binary(Val); - is_binary(Val) -> Val; - true -> Val - end, - case ?PKIXIMPLICIT:decode('SubjectAltName', BVal) of - {ok, SANs} -> - lists:flatmap( - fun({otherName, - #'AnotherName'{'type-id' = ?'id-on-xmppAddr', - value = XmppAddr - }}) -> - case 'XmppAddr':decode( - 'XmppAddr', XmppAddr) of - {ok, D} when is_binary(D) -> - case jlib:string_to_jid( - binary_to_list(D)) of - #jid{luser = "", - lserver = LD, - lresource = ""} -> - case idna:domain_utf8_to_ascii(LD) of - false -> - []; - PCLD -> - [PCLD] - end; - _ -> - [] - end; - _ -> - [] - end; - ({dNSName, D}) when is_list(D) -> - case jlib:string_to_jid(D) of - #jid{luser = "", - lserver = LD, - lresource = ""} -> - [LD]; - _ -> - [] - end; - (_) -> - [] - end, SANs); - _ -> - [] - end; - (_) -> - [] - end, Extensions). - -match_domain(Domain, Domain) -> - true; + lists:flatmap(fun (#'AttributeTypeAndValue'{type = + ?'id-at-commonName', + value = Val}) -> + case 'OTP-PUB-KEY':decode('X520CommonName', Val) of + {ok, {_, D1}} -> + D = if is_binary(D1) -> D1; + is_binary(D1) -> (D1); + true -> error + end, + if D /= error -> + case jlib:string_to_jid(D) of + #jid{luser = <<"">>, lserver = LD, + lresource = <<"">>} -> + [LD]; + _ -> [] + end; + true -> [] + end; + _ -> [] + end; + (_) -> [] + end, + lists:flatten(Subject)) + ++ + lists:flatmap(fun (#'Extension'{extnID = + ?'id-ce-subjectAltName', + extnValue = Val}) -> + BVal = if is_binary(Val) -> iolist_to_binary(Val); + is_binary(Val) -> Val; + true -> Val + end, + case 'OTP-PUB-KEY':decode('SubjectAltName', BVal) + of + {ok, SANs} -> + lists:flatmap(fun ({otherName, + #'AnotherName'{'type-id' = + ?'id-on-xmppAddr', + value = + XmppAddr}}) -> + case + 'XmppAddr':decode('XmppAddr', + XmppAddr) + of + {ok, D} + when + is_binary(D) -> + case + jlib:string_to_jid((D)) + of + #jid{luser = + <<"">>, + lserver = + LD, + lresource = + <<"">>} -> + case + idna:domain_utf8_to_ascii(LD) + of + false -> + []; + PCLD -> + [PCLD] + end; + _ -> [] + end; + _ -> [] + end; + ({dNSName, D}) + when is_binary(D) -> + case + jlib:string_to_jid(D) + of + #jid{luser = <<"">>, + lserver = LD, + lresource = + <<"">>} -> + [LD]; + _ -> [] + end; + (_) -> [] + end, + SANs); + _ -> [] + end; + (_) -> [] + end, + Extensions). + +match_domain(Domain, Domain) -> true; match_domain(Domain, Pattern) -> - DLabels = string:tokens(Domain, "."), - PLabels = string:tokens(Pattern, "."), + DLabels = str:tokens(Domain, <<".">>), + PLabels = str:tokens(Pattern, <<".">>), match_labels(DLabels, PLabels). -match_labels([], []) -> - true; -match_labels([], [_ | _]) -> - false; -match_labels([_ | _], []) -> - false; +match_labels([], []) -> true; +match_labels([], [_ | _]) -> false; +match_labels([_ | _], []) -> false; match_labels([DL | DLabels], [PL | PLabels]) -> - case lists:all(fun(C) -> (($a =< C) andalso (C =< $z)) - orelse (($0 =< C) andalso (C =< $9)) - orelse (C == $-) orelse (C == $*) - end, PL) of - true -> - Regexp = ejabberd_regexp:sh_to_awk(PL), - case ejabberd_regexp:run(DL, Regexp) of - match -> - match_labels(DLabels, PLabels); - nomatch -> - false - end; - false -> - false + case lists:all(fun (C) -> + $a =< C andalso C =< $z orelse + $0 =< C andalso C =< $9 orelse + C == $- orelse C == $* + end, + binary_to_list(PL)) + of + true -> + Regexp = ejabberd_regexp:sh_to_awk(PL), + case ejabberd_regexp:run(DL, Regexp) of + match -> match_labels(DLabels, PLabels); + nomatch -> false + end; + false -> false + end. + +fsm_limit_opts(Opts) -> + case lists:keysearch(max_fsm_queue, 1, Opts) of + {value, {_, N}} when is_integer(N) -> [{max_queue, N}]; + _ -> + case ejabberd_config:get_local_option( + max_fsm_queue, + fun(I) when is_integer(I), I > 0 -> I end) of + undefined -> []; + N -> [{max_queue, N}] + end end. |