aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/xmpp_codec.hrl2
-rw-r--r--src/ejabberd_c2s.erl65
-rw-r--r--src/ejabberd_s2s.erl18
-rw-r--r--src/ejabberd_s2s_in.erl101
-rw-r--r--src/ejabberd_s2s_out.erl48
-rw-r--r--src/ejabberd_service.erl17
-rw-r--r--src/xmpp_codec.erl23
-rw-r--r--test/ejabberd_SUITE.erl183
-rw-r--r--test/ejabberd_SUITE_data/ca.key27
-rw-r--r--test/ejabberd_SUITE_data/ca.pem22
-rw-r--r--test/ejabberd_SUITE_data/cert.pem94
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.yml5
-rwxr-xr-xtest/ejabberd_SUITE_data/extauth.py5
-rwxr-xr-xtest/ejabberd_SUITE_data/gencerts.sh18
-rw-r--r--test/ejabberd_SUITE_data/openssl.cnf323
-rw-r--r--test/ejabberd_SUITE_data/self-signed-cert.pem46
-rw-r--r--test/suite.erl143
-rw-r--r--test/suite.hrl1
-rw-r--r--tools/xmpp_codec.spec12
19 files changed, 917 insertions, 236 deletions
diff --git a/include/xmpp_codec.hrl b/include/xmpp_codec.hrl
index 845861948..5428aad11 100644
--- a/include/xmpp_codec.hrl
+++ b/include/xmpp_codec.hrl
@@ -158,7 +158,7 @@
-record(stream_start, {from :: jid:jid(),
to :: jid:jid(),
id = <<>> :: binary(),
- version = <<>> :: binary(),
+ version :: {non_neg_integer(),non_neg_integer()},
xmlns = <<>> :: binary(),
stream_xmlns = <<>> :: binary(),
db_xmlns = <<>> :: binary(),
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 7dc9960e6..858e285a6 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -352,16 +352,16 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
S -> S
end,
StreamVersion = case Version of
- <<"1.0">> -> <<"1.0">>;
- _ -> <<"">>
+ {1,0} -> {1,0};
+ _ -> undefined
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">>, ?MYLANG),
+ {1,0} ->
+ send_header(StateData, Server, {1,0}, ?MYLANG),
case StateData#state.authenticated of
false ->
TLS = StateData#state.tls,
@@ -490,10 +490,14 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
send_header(StateData, ?MYNAME, StreamVersion, ?MYLANG),
send_element(StateData, xmpp:serr_host_unknown()),
{stop, normal, StateData}
- end
+ end;
+ _ ->
+ send_header(StateData, ?MYNAME, {1,0}, ?MYLANG),
+ send_element(StateData, xmpp:serr_invalid_xml()),
+ {stop, normal, StateData}
catch _:{xmpp_codec, Why} ->
Txt = xmpp:format_error(Why),
- send_header(StateData, ?MYNAME, <<"1.0">>, ?MYLANG),
+ send_header(StateData, ?MYNAME, {1,0}, ?MYLANG),
send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
{stop, normal, StateData}
end;
@@ -506,7 +510,7 @@ wait_for_stream({xmlstreamend, _}, StateData) ->
send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_stream({xmlstreamerror, _}, StateData) ->
- send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>),
+ send_header(StateData, ?MYNAME, {1,0}, <<"">>),
send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
@@ -1374,7 +1378,7 @@ handle_info({'DOWN', Monitor, _Type, _Object, _Info},
handle_info(system_shutdown, StateName, StateData) ->
case StateName of
wait_for_stream ->
- send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>),
+ send_header(StateData, ?MYNAME, {1,0}, <<"en">>),
send_element(StateData, xmpp:serr_system_shutdown()),
ok;
_ ->
@@ -1597,39 +1601,20 @@ send_packet(StateData, Packet) ->
end.
-spec send_header(state(), binary(), binary(), binary()) -> ok | {error, any()}.
-send_header(StateData, Server, Version, Lang)
- when StateData#state.xml_socket ->
- VersionAttr = case Version of
- <<"">> -> [];
- _ -> [{<<"version">>, Version}]
- end,
- LangAttr = case Lang of
- <<"">> -> [];
- _ -> [{<<"xml:lang">>, Lang}]
- end,
- Header = {xmlstreamstart, <<"stream:stream">>,
- VersionAttr ++
- LangAttr ++
- [{<<"xmlns">>, <<"jabber:client">>},
- {<<"xmlns:stream">>,
- <<"http://etherx.jabber.org/streams">>},
- {<<"id">>, StateData#state.streamid},
- {<<"from">>, Server}]},
- (StateData#state.sockmod):send_xml(StateData#state.socket,
- Header);
send_header(StateData, Server, Version, Lang) ->
- VersionStr = case Version of
- <<"">> -> <<"">>;
- _ -> [<<" version='">>, Version, <<"'">>]
- end,
- LangStr = case Lang of
- <<"">> -> <<"">>;
- _ -> [<<" xml:lang='">>, Lang, <<"'">>]
- end,
- Header = io_lib:format(?STREAM_HEADER,
- [StateData#state.streamid, Server, VersionStr,
- LangStr]),
- send_text(StateData, iolist_to_binary(Header)).
+ Header = #xmlel{name = Name, attrs = Attrs} =
+ xmpp:encode(#stream_start{version = Version,
+ lang = Lang,
+ xmlns = ?NS_CLIENT,
+ stream_xmlns = ?NS_STREAM,
+ id = StateData#state.streamid,
+ from = jid:make(Server)}),
+ if StateData#state.xml_socket ->
+ (StateData#state.sockmod):send_xml(StateData#state.socket,
+ {xmlstreamstart, Name, Attrs});
+ true ->
+ send_text(StateData, fxml:element_to_header(Header))
+ end.
-spec send_trailer(state()) -> ok | {error, any()}.
send_trailer(StateData)
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index e585257e8..3c3e698ad 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -39,6 +39,7 @@
remove_connection/2, find_connection/2,
dirty_get_connections/0, allow_host/2,
incoming_s2s_number/0, outgoing_s2s_number/0,
+ stop_all_connections/0,
clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0,
external_host_overloaded/1, is_temporarly_blocked/1,
@@ -480,7 +481,13 @@ get_commands_spec() ->
"the node",
policy = admin,
module = ?MODULE, function = outgoing_s2s_number,
- args = [], result = {s2s_outgoing, integer}}].
+ args = [], result = {s2s_outgoing, integer}},
+ #ejabberd_commands{name = stop_all_connections,
+ tags = [s2s],
+ desc = "Stop all outgoing and incoming connections",
+ policy = admin,
+ module = ?MODULE, function = stop_all_connections,
+ args = [], result = {res, rescode}}].
incoming_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_in_sup)).
@@ -488,6 +495,15 @@ incoming_s2s_number() ->
outgoing_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_out_sup)).
+stop_all_connections() ->
+ lists:foreach(
+ fun({_Id, Pid, _Type, _Module}) ->
+ exit(Pid, kill)
+ end,
+ supervisor:which_children(ejabberd_s2s_in_sup) ++
+ supervisor:which_children(ejabberd_s2s_out_sup)),
+ mnesia:clear_table(s2s).
+
%%%----------------------------------------------------------------------
%%% Update Mnesia tables
diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl
index fd560a451..6d1791d0b 100644
--- a/src/ejabberd_s2s_in.erl
+++ b/src/ejabberd_s2s_in.erl
@@ -168,21 +168,26 @@ 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_header(StateData, {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">>}
+ from = From, version = {1,0}}
when StateData#state.tls and not StateData#state.authenticated ->
- send_header(StateData, <<" version='1.0'">>),
+ send_header(StateData, {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};
+ case From of
+ #jid{} ->
+ {Result, Message} =
+ ejabberd_s2s:check_peer_certificate(
+ StateData#state.sockmod,
+ StateData#state.socket,
+ From#jid.lserver),
+ {Result, From#jid.lserver, Message};
+ undefined ->
+ {error, <<"(unknown)">>,
+ <<"Got no valid 'from' attribute">>}
+ end;
true ->
{no_verify, <<"(unknown)">>, <<"TLS not (yet) enabled">>}
end,
@@ -225,8 +230,8 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
NewStateData#state{server = Server}}
end;
#stream_start{to = #jid{lserver = Server},
- version = <<"1.0">>} when StateData#state.authenticated ->
- send_header(StateData, <<" version='1.0'">>),
+ version = {1,0}} when StateData#state.authenticated ->
+ send_header(StateData, {1,0}),
send_element(StateData,
#stream_features{
sub_els = ejabberd_hooks:run_fold(
@@ -236,24 +241,28 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, 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, <<"">>),
+ send_header(StateData, undefined),
{next_state, stream_established, StateData};
#stream_start{} ->
- send_header(StateData, <<" version='1.0'">>),
+ send_header(StateData, {1,0}),
send_element(StateData, xmpp:serr_undefined_condition()),
- {stop, normal, StateData}
+ {stop, normal, StateData};
+ _ ->
+ send_header(StateData, {1,0}),
+ send_element(StateData, xmpp:serr_invalid_xml()),
+ {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)),
+ send_header(StateData, {1,0}),
+ send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
{stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
- send_header(StateData, <<"">>),
+ send_header(StateData, {1,0}),
send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_stream(timeout, StateData) ->
- send_header(StateData, <<"">>),
+ send_header(StateData, {1,0}),
send_element(StateData, xmpp:serr_connection_timeout()),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
@@ -277,13 +286,21 @@ wait_for_feature_request(#starttls{},
StateData#state.tls_options,
{certfile, CertFile})
end,
+ TLSOpts2 = case ejabberd_config:get_option(
+ {s2s_cafile, StateData#state.server},
+ fun iolist_to_binary/1) of
+ undefined -> TLSOpts1;
+ CAFile ->
+ lists:keystore(cafile, 1, TLSOpts1,
+ {cafile, CAFile})
+ 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]
+ true -> lists:delete(compression_none, TLSOpts2);
+ false -> [compression_none | TLSOpts2]
end,
TLSSocket = (StateData#state.sockmod):starttls(
Socket, TLSOpts,
@@ -293,8 +310,7 @@ wait_for_feature_request(#starttls{},
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)),
+ send_element(StateData, #starttls_failure{}),
{stop, normal, StateData}
end;
wait_for_feature_request(#sasl_auth{mechanism = Mech},
@@ -313,7 +329,10 @@ wait_for_feature_request(#sasl_auth{mechanism = Mech},
StateData#state{streamid = new_id(),
authenticated = true}};
true ->
- send_element(StateData, #sasl_failure{}),
+ Txt = xmpp:mk_text(<<"Denied by ACL">>, ?MYLANG),
+ send_element(StateData,
+ #sasl_failure{reason = 'not-authorized',
+ text = Txt}),
{stop, normal, StateData}
end;
_ ->
@@ -495,7 +514,7 @@ handle_info({send_text, Text}, StateName, StateData) ->
handle_info({timeout, Timer, _}, StateName,
#state{timer = Timer} = StateData) ->
if StateName == wait_for_stream ->
- send_header(StateData, <<"">>);
+ send_header(StateData, undefined);
true ->
ok
end,
@@ -555,15 +574,15 @@ send_error(StateData, Stanza, Error) ->
send_trailer(StateData) ->
send_text(StateData, <<"</stream:stream>">>).
--spec send_header(state(), binary()) -> ok.
+-spec send_header(state(), undefined | {integer(), integer()}) -> 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,
- ">">>).
+ Header = xmpp:encode(
+ #stream_start{xmlns = ?NS_SERVER,
+ stream_xmlns = ?NS_STREAM,
+ db_xmlns = ?NS_SERVER_DIALBACK,
+ id = StateData#state.streamid,
+ version = Version}),
+ send_text(StateData, fxml:element_to_header(Header)).
-spec change_shaper(state(), binary(), jid()) -> ok.
change_shaper(StateData, Host, JID) ->
@@ -606,9 +625,14 @@ fsm_limit_opts(Opts) ->
end
end.
--spec decode_element(xmlel(), state_name(), state()) -> fsm_transition().
+-spec decode_element(xmlel() | xmpp_element(), state_name(), state()) -> fsm_transition().
decode_element(#xmlel{} = El, StateName, StateData) ->
- try xmpp:decode(El) of
+ Opts = if StateName == stream_established ->
+ [ignore_els];
+ true ->
+ []
+ end,
+ try xmpp:decode(El, Opts) of
Pkt -> ?MODULE:StateName(Pkt, StateData)
catch error:{xmpp_codec, Why} ->
case xmpp:is_stanza(El) of
@@ -620,12 +644,15 @@ decode_element(#xmlel{} = El, StateName, StateData) ->
ok
end,
{next_state, StateName, StateData}
- end.
+ end;
+decode_element(Pkt, StateName, StateData) ->
+ ?MODULE:StateName(Pkt, StateData).
opt_type(domain_certfile) -> fun iolist_to_binary/1;
opt_type(max_fsm_queue) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(s2s_certfile) -> fun iolist_to_binary/1;
+opt_type(s2s_cafile) -> fun iolist_to_binary/1;
opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
opt_type(s2s_protocol_options) ->
@@ -647,6 +674,6 @@ opt_type(s2s_use_starttls) ->
(required_trusted) -> required_trusted
end;
opt_type(_) ->
- [domain_certfile, max_fsm_queue, s2s_certfile,
+ [domain_certfile, max_fsm_queue, s2s_certfile, s2s_cafile,
s2s_ciphers, s2s_dhfile, s2s_protocol_options,
s2s_tls_compression, s2s_use_starttls].
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index dd37445d7..06ba16863 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -106,12 +106,6 @@
%% Specified in miliseconds. Default value is 5 minutes.
-define(MAX_RETRY_DELAY, 300000).
--define(STREAM_HEADER,
- <<"<?xml version='1.0'?><stream:stream "
- "xmlns:stream='http://etherx.jabber.org/stream"
- "s' xmlns='jabber:server' xmlns:db='jabber:ser"
- "ver:dialback' from='~s' to='~s'~s>">>).
-
-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
%%%----------------------------------------------------------------------
@@ -228,9 +222,8 @@ open_socket(init, StateData) ->
?SOCKET_DEFAULT_RESULT, AddrList)
of
{ok, Socket} ->
- Version = if StateData#state.use_v10 ->
- <<" version='1.0'">>;
- true -> <<"">>
+ Version = if StateData#state.use_v10 -> {1,0};
+ true -> undefined
end,
NewStateData = StateData#state{socket = Socket,
tls_enabled = false,
@@ -318,11 +311,10 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, StateData0) ->
{stop, normal, StateData};
#stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM}
when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM ->
- send_header(StateData, <<" version='1.0'">>),
send_element(StateData, xmpp:serr_invalid_namespace()),
{stop, normal, StateData};
#stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID,
- version = V} when V /= <<"1.0">> ->
+ version = V} when V /= {1,0} ->
send_db_request(StateData#state{remote_streamid = ID});
#stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID}
when StateData#state.use_v10 ->
@@ -337,13 +329,14 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, StateData0) ->
StateData#state{db_enabled = false, remote_streamid = ID},
?FSMTIMEOUT};
#stream_start{} ->
- send_header(StateData, <<"">>),
send_element(StateData, xmpp:serr_invalid_namespace()),
- {stop, normal, StateData}
+ {stop, normal, StateData};
+ _ ->
+ send_element(StateData, xmpp:serr_invalid_xml()),
+ {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)),
+ send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
{stop, normal, StateData}
end;
wait_for_stream(Event, StateData) ->
@@ -469,7 +462,7 @@ wait_for_auth_result({xmlstreamelement, El}, StateData) ->
wait_for_auth_result(#sasl_success{}, StateData) ->
?DEBUG("auth: ~p", [{StateData#state.myname, StateData#state.server}]),
ejabberd_socket:reset_stream(StateData#state.socket),
- send_header(StateData, <<" version='1.0'">>),
+ send_header(StateData, {1,0}),
{next_state, wait_for_stream,
StateData#state{streamid = new_id(), authenticated = true},
?FSMTIMEOUT};
@@ -500,7 +493,7 @@ wait_for_starttls_proceed(#starttls_proceed{}, StateData) ->
streamid = new_id(),
tls_enabled = true,
tls_options = TLSOpts},
- send_header(NewStateData, <<" version='1.0'">>),
+ send_header(NewStateData, {1,0}),
{next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
wait_for_starttls_proceed(Event, StateData) ->
handle_unexpected_event(Event, wait_for_starttls_proceed, StateData).
@@ -567,7 +560,8 @@ handle_unexpected_event(Event, StateName, StateData) ->
{xmlstreamend, _} ->
?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
"XML stream closed by peer",
- [StateData#state.myname, StateData#state.server]),
+ [StateData#state.myname, StateData#state.server,
+ StateName]),
{stop, normal, StateData};
timeout ->
send_element(StateData, xmpp:serr_connection_timeout()),
@@ -741,6 +735,7 @@ print_state(State) -> State.
-spec send_text(state(), iodata()) -> ok.
send_text(StateData, Text) ->
+ ?DEBUG("Send Text on stream = ~s", [Text]),
ejabberd_socket:send(StateData#state.socket, Text).
-spec send_element(state(), xmpp_element()) -> ok.
@@ -748,15 +743,16 @@ send_element(StateData, El) ->
El1 = fix_ns(xmpp:encode(El)),
send_text(StateData, fxml:element_to_binary(El1)).
--spec send_header(state(), binary()) -> ok.
+-spec send_header(state(), undefined | {integer(), integer()}) -> ok.
send_header(StateData, Version) ->
- Txt = io_lib:format(
- "<?xml version='1.0'?><stream:stream "
- "xmlns:stream='http://etherx.jabber.org/stream"
- "s' xmlns='jabber:server' xmlns:db='jabber:ser"
- "ver:dialback' from='~s' to='~s'~s>",
- [StateData#state.myname, StateData#state.server, Version]),
- send_text(StateData, Txt).
+ Header = xmpp:encode(
+ #stream_start{xmlns = ?NS_SERVER,
+ stream_xmlns = ?NS_STREAM,
+ db_xmlns = ?NS_SERVER_DIALBACK,
+ from = jid:make(StateData#state.myname),
+ to = jid:make(StateData#state.server),
+ version = Version}),
+ send_text(StateData, fxml:element_to_header(Header)).
-spec send_trailer(state()) -> ok.
send_trailer(StateData) ->
diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl
index f4338593d..46d32e4fd 100644
--- a/src/ejabberd_service.erl
+++ b/src/ejabberd_service.erl
@@ -149,6 +149,10 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
#stream_start{} ->
send_header(StateData, ?MYNAME),
send_element(StateData, xmpp:serr_improper_addressing()),
+ {stop, normal, StateData};
+ _ ->
+ send_header(StateData, ?MYNAME),
+ send_element(StateData, xmpp:serr_invalid_xml()),
{stop, normal, StateData}
catch _:{xmpp_codec, Why} ->
Txt = xmpp:format_error(Why),
@@ -319,13 +323,12 @@ send_error(StateData, Stanza, Error) ->
-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)])).
+ Header = xmpp:encode(
+ #stream_start{xmlns = ?NS_COMPONENT,
+ stream_xmlns = ?NS_STREAM,
+ from = jid:make(Host),
+ id = StateData#state.streamid}),
+ send_text(StateData, fxml:element_to_header(Header)).
-spec send_trailer(state()) -> ok.
send_trailer(StateData) ->
diff --git a/src/xmpp_codec.erl b/src/xmpp_codec.erl
index 00ee53aaf..0a9258195 100644
--- a/src/xmpp_codec.erl
+++ b/src/xmpp_codec.erl
@@ -3955,6 +3955,14 @@ pp(upload_slot, 3) -> [get, put, xmlns];
pp(thumbnail, 4) -> [uri, 'media-type', width, height];
pp(_, _) -> no.
+enc_version({Maj, Min}) ->
+ <<(integer_to_binary(Maj))/binary, $.,
+ (integer_to_binary(Min))/binary>>.
+
+dec_version(S) ->
+ [Major, Minor] = binary:split(S, <<$.>>),
+ {binary_to_integer(Major), binary_to_integer(Minor)}.
+
enc_host_port(Host) when is_binary(Host) -> Host;
enc_host_port({{_, _, _, _, _, _, _, _} = IPv6,
Port}) ->
@@ -5284,13 +5292,20 @@ encode_stream_start_attr_xmlns(_val, _acc) ->
decode_stream_start_attr_version(__TopXMLNS,
undefined) ->
- <<>>;
+ undefined;
decode_stream_start_attr_version(__TopXMLNS, _val) ->
- _val.
+ case catch dec_version(_val) of
+ {'EXIT', _} ->
+ erlang:error({xmpp_codec,
+ {bad_attr_value, <<"version">>, <<"stream:stream">>,
+ __TopXMLNS}});
+ _res -> _res
+ end.
-encode_stream_start_attr_version(<<>>, _acc) -> _acc;
+encode_stream_start_attr_version(undefined, _acc) ->
+ _acc;
encode_stream_start_attr_version(_val, _acc) ->
- [{<<"version">>, _val} | _acc].
+ [{<<"version">>, enc_version(_val)} | _acc].
decode_stream_start_attr_id(__TopXMLNS, undefined) ->
<<>>;
diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl
index dbcd9a905..063f51bd1 100644
--- a/test/ejabberd_SUITE.erl
+++ b/test/ejabberd_SUITE.erl
@@ -20,13 +20,13 @@
make_iq_result/1, start_event_relay/0,
stop_event_relay/1, put_event/2, get_event/1,
bind/1, auth/1, auth/2, open_session/1, open_session/2,
- zlib/1, starttls/1, close_socket/1, init_stream/1,
- auth_legacy/2, auth_legacy/3]).
+ zlib/1, starttls/1, starttls/2, close_socket/1, init_stream/1,
+ auth_legacy/2, auth_legacy/3, tcp_connect/1, send_text/2]).
-include("suite.hrl").
suite() ->
- [{timetrap, {seconds,10}}].
+ [{timetrap, {seconds,30}}].
init_per_suite(Config) ->
NewConfig = init_config(Config),
@@ -36,6 +36,10 @@ init_per_suite(Config) ->
LDIFFile = filename:join([DataDir, "ejabberd.ldif"]),
{ok, _} = file:copy(ExtAuthScript, filename:join([CWD, "extauth.py"])),
{ok, _} = ldap_srv:start(LDIFFile),
+ inet_db:add_host({127,0,0,1}, [binary_to_list(?S2S_VHOST),
+ binary_to_list(?MNESIA_VHOST)]),
+ inet_db:set_domain(binary_to_list(randoms:get_string())),
+ inet_db:set_lookup([file, native]),
start_ejabberd(NewConfig),
NewConfig.
@@ -125,6 +129,16 @@ do_init_per_group(riak, Config) ->
Err ->
{skip, {riak_not_available, Err}}
end;
+do_init_per_group(s2s, Config) ->
+ ejabberd_config:add_option(s2s_use_starttls, required_trusted),
+ ejabberd_config:add_option(domain_certfile, "cert.pem"),
+ Port = ?config(s2s_port, Config),
+ set_opt(server, ?COMMON_VHOST,
+ set_opt(xmlns, ?NS_SERVER,
+ set_opt(type, server,
+ set_opt(server_port, Port,
+ set_opt(stream_from, ?S2S_VHOST,
+ set_opt(lang, <<"">>, Config))))));
do_init_per_group(component, Config) ->
Server = ?config(server, Config),
Port = ?config(component_port, Config),
@@ -132,7 +146,7 @@ do_init_per_group(component, Config) ->
set_opt(server, <<"component.", Server/binary>>,
set_opt(type, component,
set_opt(server_port, Port,
- set_opt(stream_version, <<"">>,
+ set_opt(stream_version, undefined,
set_opt(lang, <<"">>, Config))))));
do_init_per_group(_GroupName, Config) ->
Pid = start_event_relay(),
@@ -158,6 +172,8 @@ end_per_group(riak, _Config) ->
ok;
end_per_group(component, _Config) ->
ok;
+end_per_group(s2s, _Config) ->
+ ejabberd_config:add_option(s2s_use_starttls, false);
end_per_group(_GroupName, Config) ->
stop_event_relay(Config),
ok.
@@ -215,7 +231,7 @@ init_per_testcase(TestCase, OrigConfig) ->
"test_connect" ++ _ ->
Config;
"test_legacy_auth" ++ _ ->
- init_stream(set_opt(stream_version, <<"">>, Config));
+ init_stream(set_opt(stream_version, undefined, Config));
"test_auth" ++ _ ->
connect(Config);
"test_starttls" ++ _ ->
@@ -244,6 +260,8 @@ init_per_testcase(TestCase, OrigConfig) ->
Password = ?config(password, Config),
ejabberd_auth:try_register(User, Server, Password),
open_session(bind(auth(connect(Config))));
+ _ when TestGroup == s2s_tests ->
+ auth(connect(starttls(connect(Config))));
_ ->
open_session(bind(auth(connect(Config))))
end.
@@ -262,6 +280,7 @@ legacy_auth_tests() ->
no_db_tests() ->
[{generic, [parallel],
[test_connect_bad_xml,
+ test_connect_unexpected_xml,
test_connect_unknown_ns,
test_connect_bad_xmlns,
test_connect_bad_ns_stream,
@@ -286,7 +305,12 @@ no_db_tests() ->
time,
stats,
disco]},
- {presence, [sequence], [presence]},
+ {presence_and_s2s, [sequence],
+ [presence,
+ s2s_dialback,
+ s2s_optional,
+ s2s_required,
+ s2s_required_trusted]},
{sm, [sequence],
[sm,
sm_resume,
@@ -433,18 +457,39 @@ extauth_tests() ->
test_unregister]}].
component_tests() ->
- [{component_tests, [sequence],
+ [{component_connect, [parallel],
[test_connect_bad_xml,
+ test_connect_unexpected_xml,
test_connect_unknown_ns,
test_connect_bad_xmlns,
test_connect_bad_ns_stream,
test_connect_missing_to,
test_connect,
test_auth,
- test_auth_fail,
- component_missing_address,
- component_invalid_from,
- component_send,
+ test_auth_fail]},
+ {component_tests, [sequence],
+ [test_missing_address,
+ test_invalid_from,
+ test_component_send,
+ bad_nonza,
+ codec_failure]}].
+
+s2s_tests() ->
+ [{s2s_connect, [parallel],
+ [test_connect_bad_xml,
+ test_connect_unexpected_xml,
+ test_connect_unknown_ns,
+ test_connect_bad_xmlns,
+ test_connect_bad_ns_stream,
+ test_connect,
+ test_connect_s2s_starttls_required,
+ test_starttls,
+ test_connect_missing_from,
+ test_connect_s2s_unauthenticated_iq,
+ test_auth_starttls]},
+ {s2s_tests, [sequence],
+ [test_missing_address,
+ test_invalid_from,
bad_nonza,
codec_failure]}].
@@ -453,6 +498,7 @@ groups() ->
{extauth, [sequence], extauth_tests()},
{no_db, [sequence], no_db_tests()},
{component, [sequence], component_tests()},
+ {s2s, [sequence], s2s_tests()},
{mnesia, [sequence], db_tests(mnesia)},
{redis, [sequence], db_tests(redis)},
{mysql, [sequence], db_tests(mysql)},
@@ -461,16 +507,17 @@ groups() ->
{riak, [sequence], db_tests(riak)}].
all() ->
- [{group, component},
- %%{group, ldap},
+ [{group, ldap},
{group, no_db},
{group, mnesia},
- %%{group, redis},
- %%{group, mysql},
- %%{group, pgsql},
- %%{group, sqlite},
- %%{group, extauth},
- %%{group, riak},
+ {group, redis},
+ {group, mysql},
+ {group, pgsql},
+ {group, sqlite},
+ {group, extauth},
+ {group, riak},
+ {group, component},
+ {group, s2s},
stop_ejabberd].
stop_ejabberd(Config) ->
@@ -480,11 +527,23 @@ stop_ejabberd(Config) ->
Config.
test_connect_bad_xml(Config) ->
- Config0 = init_stream(set_opt(xmlns, <<"'">>, Config)),
+ Config0 = tcp_connect(Config),
+ send_text(Config0, <<"<'/>">>),
+ Version = ?config(stream_version, Config0),
+ ?recv1(#stream_start{version = Version}),
?recv1(#stream_error{reason = 'not-well-formed'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
+test_connect_unexpected_xml(Config) ->
+ Config0 = tcp_connect(Config),
+ send(Config0, #caps{}),
+ Version = ?config(stream_version, Config0),
+ ?recv1(#stream_start{version = Version}),
+ ?recv1(#stream_error{reason = 'invalid-xml'}),
+ ?recv1({xmlstreamend, <<"stream:stream">>}),
+ close_socket(Config0).
+
test_connect_unknown_ns(Config) ->
Config0 = init_stream(set_opt(xmlns, <<"wrong">>, Config)),
?recv1(#stream_error{reason = 'invalid-xml'}),
@@ -492,7 +551,11 @@ test_connect_unknown_ns(Config) ->
close_socket(Config0).
test_connect_bad_xmlns(Config) ->
- Config0 = init_stream(set_opt(xmlns, ?NS_SERVER, Config)),
+ NS = case ?config(type, Config) of
+ client -> ?NS_SERVER;
+ _ -> ?NS_CLIENT
+ end,
+ Config0 = init_stream(set_opt(xmlns, NS, Config)),
?recv1(#stream_error{reason = 'invalid-namespace'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
@@ -521,19 +584,32 @@ test_connect_missing_to(Config) ->
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
+test_connect_missing_from(Config) ->
+ Config1 = starttls(connect(Config)),
+ Config2 = set_opt(stream_from, <<"">>, Config1),
+ Config3 = init_stream(Config2),
+ ?recv1(#stream_error{reason = 'policy-violation'}),
+ ?recv1({xmlstreamend, <<"stream:stream">>}),
+ close_socket(Config3).
+
test_connect(Config) ->
disconnect(connect(Config)).
-test_component_connect(Config) ->
- disconnect(component_connect(Config)).
+test_connect_s2s_starttls_required(Config) ->
+ Config1 = connect(Config),
+ send(Config1, #caps{}),
+ ?recv1(#stream_error{reason = 'policy-violation'}),
+ ?recv1({xmlstreamend, <<"stream:stream">>}),
+ close_socket(Config1).
-component_connect(Config) ->
- init_stream(Config).
+test_connect_s2s_unauthenticated_iq(Config) ->
+ Config1 = connect(starttls(connect(Config))),
+ unauthenticated_iq(Config1).
test_starttls(Config) ->
case ?config(starttls, Config) of
true ->
- disconnect(starttls(Config));
+ disconnect(connect(starttls(Config)));
_ ->
{skipped, 'starttls_not_available'}
end.
@@ -597,8 +673,11 @@ unauthenticated_stanza(Config) ->
disconnect(Config).
unauthenticated_iq(Config) ->
+ From = my_jid(Config),
+ To = server_jid(Config),
#iq{type = error} =
- send_recv(Config, #iq{type = get, sub_els = [#disco_info{}]}),
+ send_recv(Config, #iq{type = get, from = From, to = To,
+ sub_els = [#disco_info{}]}),
disconnect(Config).
bad_nonza(Config) ->
@@ -613,23 +692,57 @@ invalid_from(Config) ->
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config).
-component_missing_address(Config) ->
+test_missing_address(Config) ->
Server = server_jid(Config),
#iq{type = error} = send_recv(Config, #iq{type = get, from = Server}),
#iq{type = error} = send_recv(Config, #iq{type = get, to = Server}),
disconnect(Config).
-component_invalid_from(Config) ->
+test_invalid_from(Config) ->
From = jid:make(randoms:get_string()),
To = jid:make(randoms:get_string()),
#iq{type = error} =
send_recv(Config, #iq{type = get, from = From, to = To}),
disconnect(Config).
-component_send(Config) ->
- JID = my_jid(Config),
- send(Config, #message{from = JID, to = JID}),
- #message{from = JID, to = JID} = recv(),
+test_component_send(Config) ->
+ To = jid:make(?COMMON_VHOST),
+ From = server_jid(Config),
+ #iq{type = result, from = To, to = From} =
+ send_recv(Config, #iq{type = get, to = To, from = From,
+ sub_els = [#ping{}]}),
+ disconnect(Config).
+
+s2s_dialback(Config) ->
+ ejabberd_s2s:stop_all_connections(),
+ ejabberd_config:add_option(s2s_use_starttls, false),
+ ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
+ s2s_ping(Config).
+
+s2s_optional(Config) ->
+ ejabberd_s2s:stop_all_connections(),
+ ejabberd_config:add_option(s2s_use_starttls, optional),
+ ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
+ s2s_ping(Config).
+
+s2s_required(Config) ->
+ ejabberd_s2s:stop_all_connections(),
+ ejabberd_config:add_option(s2s_use_starttls, required),
+ ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
+ s2s_ping(Config).
+
+s2s_required_trusted(Config) ->
+ ejabberd_s2s:stop_all_connections(),
+ ejabberd_config:add_option(s2s_use_starttls, required),
+ ejabberd_config:add_option(domain_certfile, "cert.pem"),
+ s2s_ping(Config).
+
+s2s_ping(Config) ->
+ From = my_jid(Config),
+ To = jid:make(?MNESIA_VHOST),
+ ID = randoms:get_string(),
+ ejabberd_s2s:route(From, To, #iq{id = ID, type = get, sub_els = [#ping{}]}),
+ ?recv1(#iq{type = result, id = ID, sub_els = []}),
disconnect(Config).
auth_md5(Config) ->
@@ -674,6 +787,9 @@ test_legacy_auth_fail(Config0) ->
test_auth(Config) ->
disconnect(auth(Config)).
+test_auth_starttls(Config) ->
+ disconnect(auth(connect(starttls(Config)))).
+
test_auth_fail(Config0) ->
Config = set_opt(user, <<"wrong">>,
set_opt(password, <<"wrong">>, Config0)),
@@ -1895,7 +2011,6 @@ announce_slave(Config) ->
flex_offline_master(Config) ->
Peer = ?config(slave, Config),
- ct:log("hooks = ~p", [ets:tab2list(hooks)]),
LPeer = jid:remove_resource(Peer),
lists:foreach(
fun(I) ->
diff --git a/test/ejabberd_SUITE_data/ca.key b/test/ejabberd_SUITE_data/ca.key
new file mode 100644
index 000000000..858100686
--- /dev/null
+++ b/test/ejabberd_SUITE_data/ca.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAxGSSFSDTbBTk2GwkORLCXoBdYq5YxwPfen8bK+8WjxRb9Thp
+FsHYfImtDQV0qvcZyWnjUFxRh7Dyw7A2X690nplCdzZ9Gl+5yzzlRefHborMSnNY
+rnTqx3vs9qiac0A5bzdjMY7XN3VuVwz0XWY6rAiL/7OxunCNUnQz+oswDx7cj1W4
+bb9pFzBvW5TjaAiziyzS3IxvTc7kYQYJEa99vIlDZ+Ov9rHtiF/5CZ8kHc457B3s
+uc9hHxO2t0EzmBiqg7wpksJjoJeXaJvT9sKSgW6LXkjBCm/7jm1ElPq+7FCph0qp
+uIsxMtu15exLKQaSRLcc+tyNkWIZGQ371D2+7wIDAQABAoIBACzcNCozV1fm5ecx
+vIx05oUjmTFDVfAPyGp4wkIk2OhR5Dd9bTPPj53S7P5+coni67cAQvZGQDFYj/t3
+MtRkhaT8qRwGDEmL+CqefFidewabGdMfye//sOlkO1qUZMNStkvbQQM+95Ypcszb
+nq3+/gPx59i+uSg3MXDWLlFand217d8oU4JxmCxHc9ezhkpWsdReiAukWTud+q/5
+DzyPetaP09z8Ua/YNXuI6IdsvObYxOSCI1hPPuMSQGM4hQiqkHPqPNBIJDwfM9wk
+WzGom5M7nGitrKynJHdS2VRzsZwFL3Hg0yBXnSY1o8er5A6i5//dS2ISSEN9xHjz
+9PRRCbECgYEA+yVmv8i5uBLuz/Oeu/iOcX9ZNHfNowuIpInWVyfVhURIc1OwP1Sy
+uj5Qst2IY+Hm4IVq0sNg3cZdEk+K6RMyc/Qgd7GoYeJNKH1v0RbA6E1zEzqm8Xv+
+jA3dd7RLb5NTwFv11Qh0BDZfw2e8pCmN4oDp+n8fo7RE3NQGaLb77QsCgYEAyDBE
+FoYVwXhGaKnhDT1AqM3hkOGBqheJJIxkNUnyMhlU/AxmWtfvTtw7MCP+311bz4Ma
+h6yUfaEiHQJs2wkPyIaZ8CbbVyP7bXWMZzA/Rnk4dQWZ/VjRYvEzIvmz9di3w5j6
+P1fWX0QODqcY2CvHyMmPLIysbC0cjVDA4ZpDvC0CgYEAlqvrpuevtCV3rL7F3pPS
+MXlrdTTi5AyJX91qAEPfr+I1bSsqM/SGfYHhPE34A6SFtPGWEvgwZx0YvWGHPynL
+PRGbYPPuxzrTe5U1vkVeWoAMp96qRXpUToYK9kPudfP3bRI+vB4kLFrKvRrBa+Oa
+QeeBeE1IGBiQr8NsTOpq3d0CgYB9R+d0iRlYaKL3oUjcdjbe7Wl6uAXjorMLEmks
+CEjwHXZX/pKXy4dSPPU1nXFF7DEm3o9d1R1gudSVfw0MztD313TDHC4sjLIuwF/L
+vB/9RKOWaJkEOe9gEj7EZqy+8I+gcz45IglguUBq3xvnPQ7ck3dsk+TcFidGMQFk
+rpwxSQKBgQDbdzOJagPep0HVJPkOmF1X4idb1rnQUuMi59I3k6lFTXAaypy6nU69
+aAUgv7UY4i3XglEhbztk/o51W4/fJ1N8UzbXlBur/pJD8GN2h52ea77CbpOAmDSm
+Bjjoj92wmYGfBRf7DwJQDgqxvpa0s1cwtYjNf0RmbDPzBsfzrKLKbQ==
+-----END RSA PRIVATE KEY-----
diff --git a/test/ejabberd_SUITE_data/ca.pem b/test/ejabberd_SUITE_data/ca.pem
new file mode 100644
index 000000000..3daa7f5d6
--- /dev/null
+++ b/test/ejabberd_SUITE_data/ca.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDtTCCAp2gAwIBAgIJAKI8WTrCnPXzMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTUwNDE1MTQxNTI0WhcNNDIwODMxMTQxNTI0WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAxGSSFSDTbBTk2GwkORLCXoBdYq5YxwPfen8bK+8WjxRb9ThpFsHYfImt
+DQV0qvcZyWnjUFxRh7Dyw7A2X690nplCdzZ9Gl+5yzzlRefHborMSnNYrnTqx3vs
+9qiac0A5bzdjMY7XN3VuVwz0XWY6rAiL/7OxunCNUnQz+oswDx7cj1W4bb9pFzBv
+W5TjaAiziyzS3IxvTc7kYQYJEa99vIlDZ+Ov9rHtiF/5CZ8kHc457B3suc9hHxO2
+t0EzmBiqg7wpksJjoJeXaJvT9sKSgW6LXkjBCm/7jm1ElPq+7FCph0qpuIsxMtu1
+5exLKQaSRLcc+tyNkWIZGQ371D2+7wIDAQABo4GnMIGkMB0GA1UdDgQWBBTQ9mbL
+xyIyE3pDyrNMsC36DRHp+TB1BgNVHSMEbjBsgBTQ9mbLxyIyE3pDyrNMsC36DRHp
++aFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
+BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAKI8WTrCnPXzMAwGA1UdEwQF
+MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGyAi//UQaUhy8RLGc33T36Ni6TnRgpz
+1xu2aahMe0YfPUZsZwwCP6dK+6fSw7OsRqyXZNZJntlur30yMMDlvjXmV6UDzeS4
+/HGd/hr0LqruYpmvOKmvT/y8VkmBqsGlcaRNhSJGDzMHAVEQ0hzAJe3Emw5R753p
+iVRbxPqiOVt4U/gjwtrVumSt1v9O4buWo1lTp0jxK1L6K8YWmETLuxyS3IG+i9Ij
+DDNyU/UxyocP/mcscUAoV9MJX56exwPC93rPxOlwJT5e5ZMRGnwwUt017dPUrKbA
+u+24S8uJCKN2w0OzsrqzC6lvxOf0JRfNxxxGr1KZYyEGT7ps1jhTebA=
+-----END CERTIFICATE-----
diff --git a/test/ejabberd_SUITE_data/cert.pem b/test/ejabberd_SUITE_data/cert.pem
index 11e18491f..ee9cf1641 100644
--- a/test/ejabberd_SUITE_data/cert.pem
+++ b/test/ejabberd_SUITE_data/cert.pem
@@ -1,52 +1,54 @@
-----BEGIN CERTIFICATE-----
-MIIGbDCCBVSgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTET
+MIIEmTCCA4GgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTET
MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
-dHkgTHRkMB4XDTE2MDUyNDE3NDIyNVoXDTQzMTAxMDE3NDIyNVowVjELMAkGA1UE
+dHkgTHRkMB4XDTE2MDkyMzA3MDMyNFoXDTQ0MDIwOTA3MDMyNFowVjELMAkGA1UE
BhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdp
-ZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAxMGYWN0aXZlMIGfMA0GCSqGSIb3DQEBAQUA
-A4GNADCBiQKBgQC+GTA1D1+yiXgLqUhJXkSj3hj5FiqlBAfJT/8OSXYifY4M4HYv
-VQrqER2Fs7jdCaeoGWDvwfK/UOV0b1ROnf+T/2bXFs8EOeqjOz4xG2oexNKVrYj9
-ICYAgmSh6Hf2cZJM/YCAISje93Xl2J2w/N7oFC1ZXasPoBIZv3Fgg7hTtQIDAQAB
-o4ID2DCCA9QwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5l
-cmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFEynWiCoZK4tLDk3KM1wMsbrz9Ug
-MB8GA1UdIwQYMBaAFND2ZsvHIjITekPKs0ywLfoNEen5MDMGA1UdHwQsMCowKKAm
-oCSGImh0dHA6Ly9sb2NhbGhvc3Q6NTI4MC9kYXRhL2NybC5kZXIwNgYIKwYBBQUH
-AQEEKjAoMCYGCCsGAQUFBzABhhpodHRwOi8vbG9jYWxob3N0OjUyODAvb2NzcDAL
-BgNVHQ8EBAMCBeAwEwYDVR0lBAwwCgYIKwYBBQUHAwkwggLIBgNVHREEggK/MIIC
-u6A4BggrBgEFBQcIBaAsDCp0ZXN0X3NpbmdsZSEjJCVeKigpYH4rLTtfPVtde318
-XEBsb2NhbGhvc3SgPwYIKwYBBQUHCAWgMwwxdGVzdF9zaW5nbGUhIyQlXiooKWB+
-Ky07Xz1bXXt9fFxAbW5lc2lhLmxvY2FsaG9zdKA+BggrBgEFBQcIBaAyDDB0ZXN0
-X3NpbmdsZSEjJCVeKigpYH4rLTtfPVtde318XEBteXNxbC5sb2NhbGhvc3SgPgYI
-KwYBBQUHCAWgMgwwdGVzdF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAcGdz
-cWwubG9jYWxob3N0oD8GCCsGAQUFBwgFoDMMMXRlc3Rfc2luZ2xlISMkJV4qKClg
-fistO189W117fXxcQHNxbGl0ZS5sb2NhbGhvc3SgQAYIKwYBBQUHCAWgNAwydGVz
-dF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAZXh0YXV0aC5sb2NhbGhvc3Sg
-PQYIKwYBBQUHCAWgMQwvdGVzdF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxA
-bGRhcC5sb2NhbGhvc3SgPQYIKwYBBQUHCAWgMQwvdGVzdF9zaW5nbGUhIyQlXioo
-KWB+Ky07Xz1bXXt9fFxAcDFkYi5sb2NhbGhvc3SgPQYIKwYBBQUHCAWgMQwvdGVz
-dF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAcmlhay5sb2NhbGhvc3SgPgYI
-KwYBBQUHCAWgMgwwdGVzdF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAcmVk
-aXMubG9jYWxob3N0oD4GCCsGAQUFBwgFoDIMMHRlc3Rfc2luZ2xlISMkJV4qKClg
-fistO189W117fXxcQG1zc3FsLmxvY2FsaG9zdDANBgkqhkiG9w0BAQUFAAOCAQEA
-et4jpmpwlE+2bw+/iqCt7sfU/5nPmQ8YtgMB+32wf7DINNJgkwOdkYJpzhlMXKrh
-/bn8+Ybmq6MbK0r2R91Uu858xQf8VKExQm44qaGSyL5Ug3jsAWb3GLZSaWQo37e9
-QdDeP8XijCEyr3rum19tRIdiImsRAxJqwfaE4pUSgfCEQMkvb+6//8HSf9RRPToD
-o6eAg8QerEtTfxerEdW/0K1ozOrzSrQembWOu+JjvANRl+p59j+1YOWHzS/yQeZl
-K3sjFoCvXPvocRnUznvT+TSdy3ORJSjwfEcP5Crim70amZZ6NeMAxfby9wwmmX0x
-zkwPCSUXliXke6T88Olj7Q==
+ZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAxMGYWN0aXZlMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAselBnOh089g/VN7gH1m43Vo67kSqh8QRnXZxfjpzt3oP
+Dl5nd04eNey4ezoSBo7o1hKhj/m5KLxmy1kN+xssyutgzto1FZu8GC2jDyLvByNL
+h0Z3XLmzdzBzBjosCtllJtzHlVL08SPuuOId5hToiiT8h3ElgNI4L6w+eLzhZIk5
+Rj1WojGa+pnaTEgoOaZPcNrkOj81o1tgnbLXN7HY3hJKnRp78DmPySq82cRhvfNr
+ePCs6BJr3y7yYJk0nG+EOaj5BK95YSJondZ8fOZuCigJPMogEaSw0SGsSUiQrPsd
++3vZQ+3ctOimnhW7cF3fAM79g+zDdv9N9E3D+inhyQIDAQABo4IBgTCCAX0wCQYD
+VR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlm
+aWNhdGUwHQYDVR0OBBYEFJgip1fThIyZu9J+YNz3XKDkOcMKMB8GA1UdIwQYMBaA
+FND2ZsvHIjITekPKs0ywLfoNEen5MDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9s
+b2NhbGhvc3Q6NTI4MC9kYXRhL2NybC5kZXIwNgYIKwYBBQUHAQEEKjAoMCYGCCsG
+AQUFBzABhhpodHRwOi8vbG9jYWxob3N0OjUyODAvb2NzcDALBgNVHQ8EBAMCBeAw
+JwYDVR0lBCAwHgYIKwYBBQUHAwkGCCsGAQUFBwMBBggrBgEFBQcDAjBfBgNVHREE
+WDBWoBcGCCsGAQUFBwgFoAsMCWxvY2FsaG9zdKAbBggrBgEFBQcIBaAPDA1zMnMu
+bG9jYWxob3N0oB4GCCsGAQUFBwgFoBIMEG1uZXNpYS5sb2NhbGhvc3QwDQYJKoZI
+hvcNAQEFBQADggEBAEwHeECqeEJIz0VFA0OZ0w9+3rfZPX9K59rbJNNnKVATPhk5
+g5NFpXy1mFTV/3MWjDS1QRbgoXzOYR64S87oez4l3jyDz3YxklyjbbiN3QKaUq5h
+284Ze6CiRqxIi6V2bhjjp3voMSP8BQ72bX9uAWjqQl7Z16wYuCzV4QzVZRD5p0c1
+y45WZ6J+sU1GTwEGh0vXZBlDMeTb+53smjEoCxET1ecJmStAvJi+UHiLn63Z3Yzz
+CTfdAZ/mj+ytaNLVsgrULXrmZAeo064HVqeyLWL8ZBoM0zLs6u14OQOeDCCB62cj
+UXb9npKmIdfsWvdii6emCVQqKBQmHnlUMCh56tE=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQC+GTA1D1+yiXgLqUhJXkSj3hj5FiqlBAfJT/8OSXYifY4M4HYv
-VQrqER2Fs7jdCaeoGWDvwfK/UOV0b1ROnf+T/2bXFs8EOeqjOz4xG2oexNKVrYj9
-ICYAgmSh6Hf2cZJM/YCAISje93Xl2J2w/N7oFC1ZXasPoBIZv3Fgg7hTtQIDAQAB
-AoGALddtJJ58eVVlOYqs/+RXsRyR8R9DUV/TcNx1qUBV2KNmafyHA4sCgsd10xQv
-9D2rzIGyOp8OpswfSSC/t+WqB9+ezSruzMuX6IURdHZbX6aWWX6maICtPKEEkCmI
-gaLxE/ojuOXnTEBTkVuVWtuFL9PsK/WGi/FIDzJbwqTWJ4ECQQDy9DrBAQM96B6u
-G4XpFzBsfgJZoS+NaMdCwK+/jgcEpI6oxobK8tuGB6drp5jNSuQ905W9n8XjA6Xq
-x8/GH9I5AkEAyE5g05HhMlxBWCq+P70pBDIamdHJcPQVL8+6NXkT+mTqqZxxkUy4
-nMfTh5zE6WfmqYNtrmNBDxXUyaoRSBydXQJACnFnCR7DBekxUGiMc/10LmWoMjQU
-eC6Vyg/APiqbsJ5mJ2kJKDYSK4uurZjxn3lloCa1HAZ/GgfxHMtj6e86OQJAetq3
-wIwE12KGIZF1xpo6gfxJHHbzWngaVozN5OYyPq2O0CDH9xpbUK2vK8oXbCDx9J5L
-s13lFV+Kd3X7y4LhcQJBAKSFg7ht33l8Sa0TdUkY6Tl1NBMCCLf+np+HYrAbQZux
-2NtR6nj2YqeOpEe1ibWZm8tj3dzlTm1FCOIpa+pm114=
+MIIEpAIBAAKCAQEAselBnOh089g/VN7gH1m43Vo67kSqh8QRnXZxfjpzt3oPDl5n
+d04eNey4ezoSBo7o1hKhj/m5KLxmy1kN+xssyutgzto1FZu8GC2jDyLvByNLh0Z3
+XLmzdzBzBjosCtllJtzHlVL08SPuuOId5hToiiT8h3ElgNI4L6w+eLzhZIk5Rj1W
+ojGa+pnaTEgoOaZPcNrkOj81o1tgnbLXN7HY3hJKnRp78DmPySq82cRhvfNrePCs
+6BJr3y7yYJk0nG+EOaj5BK95YSJondZ8fOZuCigJPMogEaSw0SGsSUiQrPsd+3vZ
+Q+3ctOimnhW7cF3fAM79g+zDdv9N9E3D+inhyQIDAQABAoIBAQCWIyxVx+36YgGA
+E927VzIkyqJ0tMncbOAYq/228oj4yy6th4l1Kx1fkHdWtnjDxBJFpc9l+u4ArI1r
+Cao8wIAadmxp48dshtJC7TBv86EXuvdgH11XiPcknGRVWv4T4cX099gN8cX3QcWR
+jHCC3B4phnD9s8RcZAs6X/cQWQU0mxiHodYJefSXDyRIx9wimXmmW83ZqcsFftXS
+MI0+jflmRTf07M4gALVL0LlaBkg2FMoNiaKYPTbubcrEMUgTDsoDsjX3Fi43qLdF
+QTq+lF7HrHQ1EQlngCJupka9JxwZc3Fae6XYlDQvSDPcRxzWJoOuVBPtheGeoU3c
+PAry9KihAoGBAN8HCb0k4bMN06WZjSzClKhb6eFw4GMbVpDAOwPDl2N+9+pwrGxE
+ztekrM+VdXVScIj23g6wKd6fPqK6EYuEEu3Hre82e9ApqjJ34p1UcOs9Vs4N3VDy
+HJnWhEytsc9c03O5nhsK1YAXoGHEPmCYGsg2UA171LDcarnO1WDmpKkNAoGBAMw2
+sTCC/LBwgsuPZL5fR10wQ1sr1fIheSL+VK10jSRDwNXT2Y4wdCpQXQ6XNi+n98n5
+VvKaE6PxFqjnKCrUUty8X5+fzVcTKpBYVICceEzpVY9FrKbeY1shMnOBRTCkaQwz
+8CoEbbQz6SH5s4qW7M8iJdUJ0RulaFDfpmangTStAoGBALMkMxVjZ4rsI0GT2grG
+7KNi2LTFducEUX8JeR2n4JUBql78S/LXPhGGa2x9z5ACPPQ23tyLccYowSXyMR+Q
+YafuyO4pJEBrBxNsqnDXH7BEX9I43rkjEAgdf70bk4RNOmdtA+sSw7UUxTVibPwn
+kPOadKiv+4JoOa2vzkL8X+yNAoGAbU85OUZkC+2tlViEDILjqDYVV8/3DUxtkxWg
+LdidVDQQHGTxpvK4u42Ywh6empPGRw54RBPFP5PlFTPmhEZytEUAymi3eUyBFBKz
+6MPYgRLFAZPB/vA7LqRuZPVlG8xljmqeu17zeenveIg4Wo6+44Dbz1UZ4TqAxAlz
+AK/YsWECgYAPuZnIo9fWJtUAIe5IA2LIqcN0rj3PsZ/tL6eaMXqKZgCYwTvVUGbT
+XD4O352t+yLM8v2hJGHrIPuHooN2dCadYuzoBvVFsRTZjGpBlAZ+EJ5WfDYFL0qf
+68O2KZNXaSS8ZARlp9g3C8AFiakm/uWhtSfwx09uSBHJgld1V3GAoA==
-----END RSA PRIVATE KEY-----
diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml
index 99af18bbf..128be2aed 100644
--- a/test/ejabberd_SUITE_data/ejabberd.yml
+++ b/test/ejabberd_SUITE_data/ejabberd.yml
@@ -409,6 +409,7 @@ acl:
user_regexp: ""
define_macro:
CERTFILE: "cert.pem"
+ CAFILE: "ca.pem"
language: "en"
listen:
-
@@ -450,6 +451,10 @@ Welcome to this XMPP server."
mod_time: []
mod_version: []
registration_timeout: infinity
+route_subdomains: s2s
+domain_certfile: CERTFILE
+s2s_use_starttls: false
+s2s_cafile: CAFILE
shaper:
fast: 50000
normal: 1000
diff --git a/test/ejabberd_SUITE_data/extauth.py b/test/ejabberd_SUITE_data/extauth.py
index 84c000144..fa2c9efd0 100755
--- a/test/ejabberd_SUITE_data/extauth.py
+++ b/test/ejabberd_SUITE_data/extauth.py
@@ -7,7 +7,10 @@ def read():
cmd = pkt[0]
args_num = len(pkt) - 1
if cmd == 'auth' and args_num >= 3:
- write(True)
+ if pkt[1] == "wrong":
+ write(False)
+ else:
+ write(True)
elif cmd == 'isuser' and args_num == 2:
write(True)
elif cmd == 'setpass' and args_num >= 3:
diff --git a/test/ejabberd_SUITE_data/gencerts.sh b/test/ejabberd_SUITE_data/gencerts.sh
new file mode 100755
index 000000000..d0acd4b0c
--- /dev/null
+++ b/test/ejabberd_SUITE_data/gencerts.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+# Update openssl.cnf if needed (in particular section [alt_names])
+
+rm -rf ssl
+mkdir -p ssl/newcerts
+touch ssl/index.txt
+echo 01 > ssl/serial
+echo 1000 > ssl/crlnumber
+openssl genrsa -out ssl/client.key
+openssl req -new -key ssl/client.key -out ssl/client.csr -config openssl.cnf -batch -subj /C=AU/ST=Some-State/O=Internet\ Widgits\ Pty\ Ltd/CN=active
+openssl ca -keyfile ca.key -cert ca.pem -in ssl/client.csr -out ssl/client.crt -config openssl.cnf -days 10000 -batch -notext
+openssl req -new -key ssl/client.key -out ssl/self-signed-client.csr -batch -subj /C=AU/ST=Some-State/O=Internet\ Widgits\ Pty\ Ltd/CN=active
+openssl x509 -req -in ssl/self-signed-client.csr -signkey ssl/client.key -out ssl/self-signed-client.crt -days 10000
+cat ssl/client.crt > cert.pem
+cat ssl/self-signed-client.crt > self-signed-cert.pem
+cat ssl/client.key >> cert.pem
+cat ssl/client.key >> self-signed-cert.pem
+rm -rf ssl
diff --git a/test/ejabberd_SUITE_data/openssl.cnf b/test/ejabberd_SUITE_data/openssl.cnf
new file mode 100644
index 000000000..ff11d1460
--- /dev/null
+++ b/test/ejabberd_SUITE_data/openssl.cnf
@@ -0,0 +1,323 @@
+#
+# OpenSSL example configuration file.
+# This is mostly being used for generation of certificate requests.
+#
+
+# This definition stops the following lines choking if HOME isn't
+# defined.
+HOME = .
+RANDFILE = $ENV::HOME/.rnd
+
+# Extra OBJECT IDENTIFIER info:
+#oid_file = $ENV::HOME/.oid
+oid_section = new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+extensions = v3_req
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+# We can add new OIDs in here for use by 'ca' and 'req'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+####################################################################
+[ ca ]
+default_ca = CA_default # The default ca section
+
+####################################################################
+[ CA_default ]
+
+#dir = ./demoCA # Where everything is kept
+dir = ssl
+certs = $dir/certs # Where the issued certs are kept
+crl_dir = $dir/crl # Where the issued crl are kept
+database = $dir/index.txt # database index file.
+#unique_subject = no # Set to 'no' to allow creation of
+ # several ctificates with same subject.
+new_certs_dir = $dir/newcerts # default place for new certs.
+
+certificate = $dir/cacert.pem # The CA certificate
+serial = $dir/serial # The current serial number
+crlnumber = $dir/crlnumber # the current crl number
+ # must be commented out to leave a V1 CRL
+crl = $dir/crl.pem # The current CRL
+private_key = $dir/private/cakey.pem# The private key
+RANDFILE = $dir/private/.rand # private random number file
+
+x509_extensions = usr_cert # The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt = ca_default # Subject Name options
+cert_opt = ca_default # Certificate field options
+
+# Extension copying option: use with caution.
+copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions = crl_ext
+
+default_days = 365 # how long to certify for
+default_crl_days= 30 # how long before next CRL
+default_md = sha1 # which md to use.
+preserve = no # keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy = policy_match
+
+# For the CA policy
+[ policy_match ]
+countryName = match
+stateOrProvinceName = match
+organizationName = match
+organizationalUnitName = optional
+commonName = optional
+emailAddress = optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = optional
+emailAddress = optional
+
+####################################################################
+[ req ]
+default_bits = 1024
+default_keyfile = privkey.pem
+distinguished_name = req_distinguished_name
+attributes = req_attributes
+x509_extensions = v3_ca # The extentions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+
+# This sets a mask for permitted string types. There are several options.
+# default: PrintableString, T61String, BMPString.
+# pkix : PrintableString, BMPString.
+# utf8only: only UTF8Strings.
+# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
+# MASK:XXXX a literal mask value.
+# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings
+# so use this option with caution!
+string_mask = nombstr
+
+req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName = Country Name (2 letter code)
+countryName_default = AU
+countryName_min = 2
+countryName_max = 2
+
+stateOrProvinceName = State or Province Name (full name)
+stateOrProvinceName_default = Some-State
+
+localityName = Locality Name (eg, city)
+
+0.organizationName = Organization Name (eg, company)
+0.organizationName_default = Internet Widgits Pty Ltd
+
+# we can do this but it is not needed normally :-)
+#1.organizationName = Second Organization Name (eg, company)
+#1.organizationName_default = World Wide Web Pty Ltd
+
+organizationalUnitName = Organizational Unit Name (eg, section)
+#organizationalUnitName_default =
+
+commonName = Common Name (eg, YOUR name)
+commonName_max = 64
+
+emailAddress = Email Address
+emailAddress_max = 64
+
+# SET-ex3 = SET extension number 3
+
+[ req_attributes ]
+challengePassword = A challenge password
+challengePassword_min = 4
+challengePassword_max = 20
+
+unstructuredName = An optional company name
+
+[ usr_cert ]
+
+# These extensions are added when 'ca' signs a request.
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+crlDistributionPoints = URI:http://localhost:5280/data/crl.der
+authorityInfoAccess = OCSP;URI:http://localhost:5280/ocsp
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+extendedKeyUsage = OCSPSigning,serverAuth,clientAuth
+subjectAltName = @alt_names
+
+[alt_names]
+otherName.1 = 1.3.6.1.5.5.7.8.5;UTF8:"localhost"
+otherName.2 = 1.3.6.1.5.5.7.8.5;UTF8:"s2s.localhost"
+otherName.3 = 1.3.6.1.5.5.7.8.5;UTF8:"mnesia.localhost"
+
+[ v3_ca ]
+crlDistributionPoints = URI:http://localhost:5280/data/crl.der
+
+# Extensions for a typical CA
+
+
+# PKIX recommendation.
+
+subjectKeyIdentifier=hash
+
+authorityKeyIdentifier=keyid:always,issuer:always
+
+# This is what PKIX recommends but some broken software chokes on critical
+# extensions.
+#basicConstraints = critical,CA:true
+# So we do this instead.
+basicConstraints = CA:true
+
+# Key usage: this is typical for a CA certificate. However since it will
+# prevent it being used as an test self-signed certificate it is best
+# left out by default.
+# keyUsage = cRLSign, keyCertSign
+
+# Some might want this also
+# nsCertType = sslCA, emailCA
+
+# Include email address in subject alt name: another PKIX recommendation
+# subjectAltName=email:copy
+# Copy issuer details
+# issuerAltName=issuer:copy
+
+# DER hex encoding of an extension: beware experts only!
+# obj=DER:02:03
+# Where 'obj' is a standard or added object
+# You can even override a supported extension:
+# basicConstraints= critical, DER:30:03:01:01:FF
+
+[ crl_ext ]
+
+# CRL extensions.
+# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
+
+# issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always,issuer:always
+
+[ proxy_cert_ext ]
+# These extensions should be added when creating a proxy certificate
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer:always
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This really needs to be in place for it to be a proxy certificate.
+proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
diff --git a/test/ejabberd_SUITE_data/self-signed-cert.pem b/test/ejabberd_SUITE_data/self-signed-cert.pem
new file mode 100644
index 000000000..d6b34f50e
--- /dev/null
+++ b/test/ejabberd_SUITE_data/self-signed-cert.pem
@@ -0,0 +1,46 @@
+-----BEGIN CERTIFICATE-----
+MIIDKDCCAhACCQCsLYnJDV1wHDANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMQ8wDQYDVQQDEwZhY3RpdmUwHhcNMTYwOTIzMDcwMzI0WhcNNDQw
+MjA5MDcwMzI0WjBWMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh
+MB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZhY3Rp
+dmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx6UGc6HTz2D9U3uAf
+WbjdWjruRKqHxBGddnF+OnO3eg8OXmd3Th417Lh7OhIGjujWEqGP+bkovGbLWQ37
+GyzK62DO2jUVm7wYLaMPIu8HI0uHRndcubN3MHMGOiwK2WUm3MeVUvTxI+644h3m
+FOiKJPyHcSWA0jgvrD54vOFkiTlGPVaiMZr6mdpMSCg5pk9w2uQ6PzWjW2Cdstc3
+sdjeEkqdGnvwOY/JKrzZxGG982t48KzoEmvfLvJgmTScb4Q5qPkEr3lhImid1nx8
+5m4KKAk8yiARpLDRIaxJSJCs+x37e9lD7dy06KaeFbtwXd8Azv2D7MN2/030TcP6
+KeHJAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEAIFwHpNCVUiivAcfkxcUPKp0nn
+mhGqkMDRrPA7fOCm0ir1Puz4GQ/G4i+tWejzzFoS6kKQl+sUZAUYJdziftJFFoZ7
+br3q3Xafc2dWa8SHNcHH6lA1OEk8tXlhkNl+EgSLnRGMhIf0iZL2wGjE8Hlig6cu
+3h+OpbUijXUmq0XdH+ui3wNgXb7+Tosg/Od+lr0fNjkopsk3t1oiVXD4OQBZdUyq
+V5XValiZjMFDUUBdxBA+l6/Qj3bFmluz+FXI8UwfbinukqADTJzkMeUjEkvmKZWO
+tb+EU77NIuvg/k7b1yp4lEmATpdUfcGEuhWNtgeh5AqgMxOhAsJ7zUTA80I=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAselBnOh089g/VN7gH1m43Vo67kSqh8QRnXZxfjpzt3oPDl5n
+d04eNey4ezoSBo7o1hKhj/m5KLxmy1kN+xssyutgzto1FZu8GC2jDyLvByNLh0Z3
+XLmzdzBzBjosCtllJtzHlVL08SPuuOId5hToiiT8h3ElgNI4L6w+eLzhZIk5Rj1W
+ojGa+pnaTEgoOaZPcNrkOj81o1tgnbLXN7HY3hJKnRp78DmPySq82cRhvfNrePCs
+6BJr3y7yYJk0nG+EOaj5BK95YSJondZ8fOZuCigJPMogEaSw0SGsSUiQrPsd+3vZ
+Q+3ctOimnhW7cF3fAM79g+zDdv9N9E3D+inhyQIDAQABAoIBAQCWIyxVx+36YgGA
+E927VzIkyqJ0tMncbOAYq/228oj4yy6th4l1Kx1fkHdWtnjDxBJFpc9l+u4ArI1r
+Cao8wIAadmxp48dshtJC7TBv86EXuvdgH11XiPcknGRVWv4T4cX099gN8cX3QcWR
+jHCC3B4phnD9s8RcZAs6X/cQWQU0mxiHodYJefSXDyRIx9wimXmmW83ZqcsFftXS
+MI0+jflmRTf07M4gALVL0LlaBkg2FMoNiaKYPTbubcrEMUgTDsoDsjX3Fi43qLdF
+QTq+lF7HrHQ1EQlngCJupka9JxwZc3Fae6XYlDQvSDPcRxzWJoOuVBPtheGeoU3c
+PAry9KihAoGBAN8HCb0k4bMN06WZjSzClKhb6eFw4GMbVpDAOwPDl2N+9+pwrGxE
+ztekrM+VdXVScIj23g6wKd6fPqK6EYuEEu3Hre82e9ApqjJ34p1UcOs9Vs4N3VDy
+HJnWhEytsc9c03O5nhsK1YAXoGHEPmCYGsg2UA171LDcarnO1WDmpKkNAoGBAMw2
+sTCC/LBwgsuPZL5fR10wQ1sr1fIheSL+VK10jSRDwNXT2Y4wdCpQXQ6XNi+n98n5
+VvKaE6PxFqjnKCrUUty8X5+fzVcTKpBYVICceEzpVY9FrKbeY1shMnOBRTCkaQwz
+8CoEbbQz6SH5s4qW7M8iJdUJ0RulaFDfpmangTStAoGBALMkMxVjZ4rsI0GT2grG
+7KNi2LTFducEUX8JeR2n4JUBql78S/LXPhGGa2x9z5ACPPQ23tyLccYowSXyMR+Q
+YafuyO4pJEBrBxNsqnDXH7BEX9I43rkjEAgdf70bk4RNOmdtA+sSw7UUxTVibPwn
+kPOadKiv+4JoOa2vzkL8X+yNAoGAbU85OUZkC+2tlViEDILjqDYVV8/3DUxtkxWg
+LdidVDQQHGTxpvK4u42Ywh6empPGRw54RBPFP5PlFTPmhEZytEUAymi3eUyBFBKz
+6MPYgRLFAZPB/vA7LqRuZPVlG8xljmqeu17zeenveIg4Wo6+44Dbz1UZ4TqAxAlz
+AK/YsWECgYAPuZnIo9fWJtUAIe5IA2LIqcN0rj3PsZ/tL6eaMXqKZgCYwTvVUGbT
+XD4O352t+yLM8v2hJGHrIPuHooN2dCadYuzoBvVFsRTZjGpBlAZ+EJ5WfDYFL0qf
+68O2KZNXaSS8ZARlp9g3C8AFiakm/uWhtSfwx09uSBHJgld1V3GAoA==
+-----END RSA PRIVATE KEY-----
diff --git a/test/suite.erl b/test/suite.erl
index f78b7bd1a..e4be0054f 100644
--- a/test/suite.erl
+++ b/test/suite.erl
@@ -27,13 +27,18 @@ init_config(Config) ->
SASLPath = filename:join([PrivDir, "sasl.log"]),
MnesiaDir = filename:join([PrivDir, "mnesia"]),
CertFile = filename:join([DataDir, "cert.pem"]),
+ SelfSignedCertFile = filename:join([DataDir, "self-signed-cert.pem"]),
+ CAFile = filename:join([DataDir, "ca.pem"]),
{ok, CWD} = file:get_cwd(),
{ok, _} = file:copy(CertFile, filename:join([CWD, "cert.pem"])),
+ {ok, _} = file:copy(SelfSignedCertFile,
+ filename:join([CWD, "self-signed-cert.pem"])),
+ {ok, _} = file:copy(CAFile, filename:join([CWD, "ca.pem"])),
{ok, CfgContentTpl} = file:read_file(ConfigPathTpl),
Password = <<"password!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>,
CfgContent = process_config_tpl(CfgContentTpl, [
{c2s_port, 5222},
- {loglevel, 5},
+ {loglevel, 4},
{s2s_port, 5269},
{component_port, 5270},
{web_port, 5280},
@@ -62,6 +67,7 @@ init_config(Config) ->
[{server_port, ct:get_config(c2s_port, 5222)},
{server_host, "localhost"},
{component_port, ct:get_config(component_port, 5270)},
+ {s2s_port, ct:get_config(s2s_port, 5269)},
{server, ?COMMON_VHOST},
{user, <<"test_single!#$%^*()`~+-;_=[]{}|\\">>},
{master_nick, <<"master_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
@@ -71,11 +77,14 @@ init_config(Config) ->
{type, client},
{xmlns, ?NS_CLIENT},
{ns_stream, ?NS_STREAM},
- {stream_version, <<"1.0">>},
+ {stream_version, {1, 0}},
{stream_id, <<"">>},
+ {stream_from, <<"">>},
+ {db_xmlns, <<"">>},
{mechs, []},
{lang, <<"en">>},
{base_dir, BaseDir},
+ {socket, undefined},
{resource, <<"resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
{master_resource, <<"master_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
{slave_resource, <<"slave_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
@@ -130,42 +139,50 @@ process_config_tpl(Content, [{Name, DefaultValue} | Rest]) ->
process_config_tpl(NewContent, Rest).
stream_header(Config) ->
- NSStream = ?config(ns_stream, Config),
- XMLNS = ?config(xmlns, Config),
- Lang = case ?config(lang, Config) of
- <<"">> -> <<"">>;
- L -> iolist_to_binary(["xml:lang='", L, "'"])
- end,
To = case ?config(server, Config) of
- <<"">> -> <<"">>;
- Server -> <<"to='", Server/binary, "'">>
+ <<"">> -> undefined;
+ Server -> jid:make(Server)
end,
- Version = case ?config(stream_version, Config) of
- <<"">> -> <<"">>;
- V -> <<"version='", V/binary, "'">>
- end,
- io_lib:format("<?xml version='1.0'?><stream:stream "
- "xmlns:stream='~s' xmlns='~s' ~s ~s ~s>",
- [NSStream, XMLNS, To, Version, Lang]).
+ From = case ?config(stream_from, Config) of
+ <<"">> -> undefined;
+ Frm -> jid:make(Frm)
+ end,
+ #stream_start{to = To,
+ from = From,
+ lang = ?config(lang, Config),
+ version = ?config(stream_version, Config),
+ xmlns = ?config(xmlns, Config),
+ db_xmlns = ?config(db_xmlns, Config),
+ stream_xmlns = ?config(ns_stream, Config)}.
connect(Config) ->
NewConfig = init_stream(Config),
case ?config(type, NewConfig) of
client -> process_stream_features(NewConfig);
+ server -> process_stream_features(NewConfig);
component -> NewConfig
end.
+tcp_connect(Config) ->
+ case ?config(socket, Config) of
+ undefined ->
+ {ok, Sock} = ejabberd_socket:connect(
+ ?config(server_host, Config),
+ ?config(server_port, Config),
+ [binary, {packet, 0}, {active, false}]),
+ set_opt(socket, Sock, Config);
+ _ ->
+ Config
+ end.
+
init_stream(Config) ->
Version = ?config(stream_version, Config),
- {ok, Sock} = ejabberd_socket:connect(
- ?config(server_host, Config),
- ?config(server_port, Config),
- [binary, {packet, 0}, {active, false}]),
- NewConfig = set_opt(socket, Sock, Config),
- ok = send_text(NewConfig, stream_header(NewConfig)),
+ NewConfig = tcp_connect(Config),
+ send(NewConfig, stream_header(NewConfig)),
XMLNS = case ?config(type, Config) of
client -> ?NS_CLIENT;
- component -> ?NS_COMPONENT
+ component -> ?NS_COMPONENT;
+ server -> ?NS_SERVER
end,
#stream_start{id = ID, xmlns = XMLNS, version = Version} = recv(),
set_opt(stream_id, ID, NewConfig).
@@ -206,13 +223,24 @@ close_socket(Config) ->
Config.
starttls(Config) ->
+ starttls(Config, false).
+
+starttls(Config, ShouldFail) ->
send(Config, #starttls{}),
- #starttls_proceed{} = recv(),
- TLSSocket = ejabberd_socket:starttls(
- ?config(socket, Config),
- [{certfile, ?config(certfile, Config)},
- connect]),
- process_stream_features(init_stream(set_opt(socket, TLSSocket, Config))).
+ case recv() of
+ #starttls_proceed{} when ShouldFail ->
+ ct:fail(starttls_should_have_failed);
+ #starttls_failure{} when ShouldFail ->
+ Config;
+ #starttls_failure{} ->
+ ct:fail(starttls_failed);
+ #starttls_proceed{} ->
+ TLSSocket = ejabberd_socket:starttls(
+ ?config(socket, Config),
+ [{certfile, ?config(certfile, Config)},
+ connect]),
+ set_opt(socket, TLSSocket, Config)
+ end.
zlib(Config) ->
send(Config, #compress{methods = [<<"zlib">>]}),
@@ -228,14 +256,19 @@ auth(Config, ShouldFail) ->
Mechs = ?config(mechs, Config),
HaveMD5 = lists:member(<<"DIGEST-MD5">>, Mechs),
HavePLAIN = lists:member(<<"PLAIN">>, Mechs),
+ HaveExternal = lists:member(<<"EXTERNAL">>, Mechs),
if HavePLAIN ->
auth_SASL(<<"PLAIN">>, Config, ShouldFail);
HaveMD5 ->
auth_SASL(<<"DIGEST-MD5">>, Config, ShouldFail);
+ HaveExternal andalso Type == server ->
+ auth_SASL(<<"EXTERNAL">>, Config, ShouldFail);
Type == client ->
auth_legacy(Config, false, ShouldFail);
Type == component ->
- auth_component(Config, ShouldFail)
+ auth_component(Config, ShouldFail);
+ true ->
+ ct:fail(no_known_sasl_mechanism_available)
end.
bind(Config) ->
@@ -341,10 +374,19 @@ wait_auth_SASL_result(Config, ShouldFail) ->
ct:fail(sasl_auth_should_have_failed);
#sasl_success{} ->
ejabberd_socket:reset_stream(?config(socket, Config)),
- send_text(Config, stream_header(Config)),
- #stream_start{xmlns = ?NS_CLIENT, version = <<"1.0">>} = recv(),
+ send(Config, stream_header(Config)),
+ Type = ?config(type, Config),
+ NS = if Type == client -> ?NS_CLIENT;
+ Type == server -> ?NS_SERVER
+ end,
+ #stream_start{xmlns = NS, version = {1,0}} = recv(),
#stream_features{sub_els = Fs} = recv(),
- #xmpp_session{optional = true} = lists:keyfind(xmpp_session, 1, Fs),
+ if Type == client ->
+ #xmpp_session{optional = true} =
+ lists:keyfind(xmpp_session, 1, Fs);
+ true ->
+ ok
+ end,
lists:foldl(
fun(#feature_sm{}, ConfigAcc) ->
set_opt(sm, true, ConfigAcc);
@@ -427,7 +469,11 @@ send(State, Pkt) ->
end,
El = xmpp_codec:encode(NewPkt),
ct:pal("sent: ~p <-~n~s", [El, xmpp_codec:pp(NewPkt)]),
- ok = send_text(State, fxml:element_to_binary(El)),
+ Data = case NewPkt of
+ #stream_start{} -> fxml:element_to_header(El);
+ _ -> fxml:element_to_binary(El)
+ end,
+ ok = send_text(State, Data),
NewID.
send_recv(State, IQ) ->
@@ -437,6 +483,9 @@ send_recv(State, IQ) ->
sasl_new(<<"PLAIN">>, User, Server, Password) ->
{<<User/binary, $@, Server/binary, 0, User/binary, 0, Password/binary>>,
fun (_) -> {error, <<"Invalid SASL challenge">>} end};
+sasl_new(<<"EXTERNAL">>, _User, _Server, _Password) ->
+ {<<"">>,
+ fun(_) -> ct:fail(sasl_challenge_is_not_expected) end};
sasl_new(<<"DIGEST-MD5">>, User, Server, Password) ->
{<<"">>,
fun (ServerIn) ->
@@ -567,11 +616,21 @@ set_opt(Opt, Val, Config) ->
wait_for_master(Config) ->
put_event(Config, slave_ready),
- master_ready = get_event(Config).
+ case get_event(Config) of
+ master_ready ->
+ ok;
+ Other ->
+ suite:match_failure([Other], [master_ready])
+ end.
wait_for_slave(Config) ->
put_event(Config, master_ready),
- slave_ready = get_event(Config).
+ case get_event(Config) of
+ slave_ready ->
+ ok;
+ Other ->
+ suite:match_failure([Other], [slave_ready])
+ end.
make_iq_result(#iq{from = From} = IQ) ->
IQ#iq{type = result, to = From, from = undefined, sub_els = []}.
@@ -592,6 +651,7 @@ event_relay() ->
event_relay(Events, Subscribers) ->
receive
{subscribe, From} ->
+ erlang:monitor(process, From),
From ! {ok, self()},
lists:foreach(
fun(Event) -> From ! {event, Event, self()}
@@ -605,7 +665,14 @@ event_relay(Events, Subscribers) ->
(_) ->
ok
end, Subscribers),
- event_relay([Event|Events], Subscribers)
+ event_relay([Event|Events], Subscribers);
+ {'DOWN', _MRef, process, Pid, _Info} ->
+ NewSubscribers = lists:delete(Pid, Subscribers),
+ lists:foreach(
+ fun(Subscriber) ->
+ Subscriber ! {event, peer_down, self()}
+ end, NewSubscribers),
+ event_relay(Events, NewSubscribers)
end.
subscribe_to_events(Config) ->
diff --git a/test/suite.hrl b/test/suite.hrl
index 3095e1bb5..cbeedff53 100644
--- a/test/suite.hrl
+++ b/test/suite.hrl
@@ -75,6 +75,7 @@
-define(LDAP_VHOST, <<"ldap.localhost">>).
-define(EXTAUTH_VHOST, <<"extauth.localhost">>).
-define(RIAK_VHOST, <<"riak.localhost">>).
+-define(S2S_VHOST, <<"s2s.localhost">>).
insert(Val, N, Tuple) ->
L = tuple_to_list(Tuple),
diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec
index aa18899c9..7b5ca5e66 100644
--- a/tools/xmpp_codec.spec
+++ b/tools/xmpp_codec.spec
@@ -3215,7 +3215,9 @@
default = <<"">>},
#attr{name = <<"xml:lang">>, label = '$lang',
default = <<"">>},
- #attr{name = <<"version">>, default = <<"">>},
+ #attr{name = <<"version">>,
+ dec = {dec_version, []},
+ enc = {enc_version, []}},
#attr{name = <<"id">>, default = <<"">>}]}).
-xml(bob_data,
@@ -3479,6 +3481,14 @@ enc_host_port({Host, Port}) ->
enc_host_port(Addr) ->
enc_ip(Addr).
+-spec dec_version(_) -> {non_neg_integer(), non_neg_integer()}.
+dec_version(S) ->
+ [Major, Minor] = binary:split(S, <<$.>>),
+ {binary_to_integer(Major), binary_to_integer(Minor)}.
+
+enc_version({Maj, Min}) ->
+ <<(integer_to_binary(Maj))/binary, $., (integer_to_binary(Min))/binary>>.
+
%% Local Variables:
%% mode: erlang
%% End: