mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-20 16:15:59 +01:00
Move XMPP stream and SASL processing to xmpp repo
This commit is contained in:
parent
59f5a098b5
commit
0bb14d16c7
@ -1,14 +0,0 @@
|
||||
XmppAddr { iso(1) identified-organization(3)
|
||||
dod(6) internet(1) security(5) mechanisms(5) pkix(7)
|
||||
id-on(8) id-on-xmppAddr(5) }
|
||||
|
||||
DEFINITIONS EXPLICIT TAGS ::=
|
||||
BEGIN
|
||||
|
||||
id-on-xmppAddr OBJECT IDENTIFIER ::= { iso(1) identified-organization(3)
|
||||
dod(6) internet(1) security(5) mechanisms(5) pkix(7)
|
||||
id-on(8) 5 }
|
||||
|
||||
XmppAddr ::= UTF8String
|
||||
|
||||
END
|
@ -1,28 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(scram, {storedkey = <<"">> :: binary(),
|
||||
serverkey = <<"">> :: binary(),
|
||||
salt = <<"">> :: binary(),
|
||||
iterationcount = 0 :: integer()}).
|
||||
|
||||
-type scram() :: #scram{}.
|
||||
|
||||
-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
|
@ -25,7 +25,7 @@
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.23"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.12"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.32"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "0e2ef5d"}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "2a5193c"}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.15"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.3"}}},
|
||||
@ -100,7 +100,7 @@
|
||||
{if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
|
||||
{if_var_true, new_sql_schema, {d, 'NEW_SQL_SCHEMA'}},
|
||||
{if_var_true, hipe, native},
|
||||
{src_dirs, [asn1, src,
|
||||
{src_dirs, [src,
|
||||
{if_var_true, tools, tools},
|
||||
{if_var_true, elixir, include}]}]}.
|
||||
|
||||
|
229
src/cyrsasl.erl
229
src/cyrsasl.erl
@ -1,229 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Cyrus SASL-like library
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start_link/0, register_mechanism/3, listmech/1,
|
||||
server_new/7, server_start/3, server_step/2,
|
||||
get_mech/1, format_error/2]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
-record(sasl_mechanism,
|
||||
{mechanism = <<"">> :: mechanism() | '$1',
|
||||
module :: atom(),
|
||||
password_type = plain :: password_type() | '$2'}).
|
||||
|
||||
-type(mechanism() :: binary()).
|
||||
-type(mechanisms() :: [mechanism(),...]).
|
||||
-type(password_type() :: plain | digest | scram).
|
||||
-type sasl_property() :: {username, binary()} |
|
||||
{authzid, binary()} |
|
||||
{mechanism, binary()} |
|
||||
{auth_module, atom()}.
|
||||
-type sasl_return() :: {ok, [sasl_property()]} |
|
||||
{ok, [sasl_property()], binary()} |
|
||||
{continue, binary(), sasl_state()} |
|
||||
{error, atom(), binary()}.
|
||||
|
||||
-type(sasl_mechanism() :: #sasl_mechanism{}).
|
||||
-type error_reason() :: cyrsasl_digest:error_reason() |
|
||||
cyrsasl_oauth:error_reason() |
|
||||
cyrsasl_plain:error_reason() |
|
||||
cyrsasl_scram:error_reason() |
|
||||
unsupported_mechanism | nodeprep_failed |
|
||||
empty_username | aborted.
|
||||
-record(sasl_state,
|
||||
{
|
||||
service,
|
||||
myname,
|
||||
realm,
|
||||
get_password,
|
||||
check_password,
|
||||
check_password_digest,
|
||||
mech_name = <<"">>,
|
||||
mech_mod,
|
||||
mech_state
|
||||
}).
|
||||
-type sasl_state() :: #sasl_state{}.
|
||||
-export_type([mechanism/0, mechanisms/0, sasl_mechanism/0, error_reason/0,
|
||||
sasl_state/0, sasl_return/0, sasl_property/0]).
|
||||
|
||||
-callback start(list()) -> any().
|
||||
-callback stop() -> any().
|
||||
-callback mech_new(binary(), fun(), fun(), fun()) -> any().
|
||||
-callback mech_step(any(), binary()) -> sasl_return().
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
init([]) ->
|
||||
ets:new(sasl_mechanism,
|
||||
[named_table, public,
|
||||
{keypos, #sasl_mechanism.mechanism}]),
|
||||
cyrsasl_plain:start([]),
|
||||
cyrsasl_digest:start([]),
|
||||
cyrsasl_scram:start([]),
|
||||
cyrsasl_anonymous:start([]),
|
||||
cyrsasl_oauth:start([]),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
cyrsasl_plain:stop(),
|
||||
cyrsasl_digest:stop(),
|
||||
cyrsasl_scram:stop(),
|
||||
cyrsasl_anonymous:stop(),
|
||||
cyrsasl_oauth:stop().
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
-spec format_error(mechanism() | sasl_state(), error_reason()) -> {atom(), binary()}.
|
||||
format_error(_, unsupported_mechanism) ->
|
||||
{'invalid-mechanism', <<"Unsupported mechanism">>};
|
||||
format_error(_, nodeprep_failed) ->
|
||||
{'bad-protocol', <<"Nodeprep failed">>};
|
||||
format_error(_, empty_username) ->
|
||||
{'bad-protocol', <<"Empty username">>};
|
||||
format_error(_, aborted) ->
|
||||
{'aborted', <<"Aborted">>};
|
||||
format_error(#sasl_state{mech_mod = Mod}, Reason) ->
|
||||
Mod:format_error(Reason);
|
||||
format_error(Mech, Reason) ->
|
||||
case ets:lookup(sasl_mechanism, Mech) of
|
||||
[#sasl_mechanism{module = Mod}] ->
|
||||
Mod:format_error(Reason);
|
||||
[] ->
|
||||
{'invalid-mechanism', <<"Unsupported mechanism">>}
|
||||
end.
|
||||
|
||||
-spec register_mechanism(Mechanim :: mechanism(), Module :: module(),
|
||||
PasswordType :: password_type()) -> any().
|
||||
|
||||
register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
ets:insert(sasl_mechanism,
|
||||
#sasl_mechanism{mechanism = Mechanism, module = Module,
|
||||
password_type = PasswordType}).
|
||||
|
||||
check_credentials(_State, Props) ->
|
||||
User = proplists:get_value(authzid, Props, <<>>),
|
||||
case jid:nodeprep(User) of
|
||||
error -> {error, nodeprep_failed};
|
||||
<<"">> -> {error, empty_username};
|
||||
_LUser -> ok
|
||||
end.
|
||||
|
||||
-spec listmech(Host ::binary()) -> Mechanisms::mechanisms().
|
||||
|
||||
listmech(Host) ->
|
||||
ets:select(sasl_mechanism,
|
||||
[{#sasl_mechanism{mechanism = '$1',
|
||||
password_type = '$2', _ = '_'},
|
||||
case catch ejabberd_auth:store_type(Host) of
|
||||
external -> [{'==', '$2', plain}];
|
||||
scram -> [{'/=', '$2', digest}];
|
||||
{'EXIT', {undef, [{Module, store_type, []} | _]}} ->
|
||||
?WARNING_MSG("~p doesn't implement the function store_type/0",
|
||||
[Module]),
|
||||
[];
|
||||
_Else -> []
|
||||
end,
|
||||
['$1']}]).
|
||||
|
||||
-spec server_new(binary(), binary(), binary(), term(),
|
||||
fun(), fun(), fun()) -> sasl_state().
|
||||
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
|
||||
GetPassword, CheckPassword, CheckPasswordDigest) ->
|
||||
#sasl_state{service = Service, myname = ServerFQDN,
|
||||
realm = UserRealm, get_password = GetPassword,
|
||||
check_password = CheckPassword,
|
||||
check_password_digest = CheckPasswordDigest}.
|
||||
|
||||
-spec server_start(sasl_state(), mechanism(), binary()) -> sasl_return().
|
||||
server_start(State, Mech, ClientIn) ->
|
||||
case lists:member(Mech,
|
||||
listmech(State#sasl_state.myname))
|
||||
of
|
||||
true ->
|
||||
case ets:lookup(sasl_mechanism, Mech) of
|
||||
[#sasl_mechanism{module = Module}] ->
|
||||
{ok, MechState} =
|
||||
Module:mech_new(State#sasl_state.myname,
|
||||
State#sasl_state.get_password,
|
||||
State#sasl_state.check_password,
|
||||
State#sasl_state.check_password_digest),
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_name = Mech,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
_ -> {error, unsupported_mechanism, <<"">>}
|
||||
end;
|
||||
false -> {error, unsupported_mechanism, <<"">>}
|
||||
end.
|
||||
|
||||
-spec server_step(sasl_state(), binary()) -> sasl_return().
|
||||
server_step(State, ClientIn) ->
|
||||
Module = State#sasl_state.mech_mod,
|
||||
MechState = State#sasl_state.mech_state,
|
||||
case Module:mech_step(MechState, ClientIn) of
|
||||
{ok, Props} ->
|
||||
case check_credentials(State, Props) of
|
||||
ok -> {ok, Props};
|
||||
{error, Error} -> {error, Error, <<"">>}
|
||||
end;
|
||||
{ok, Props, ServerOut} ->
|
||||
case check_credentials(State, Props) of
|
||||
ok -> {ok, Props, ServerOut};
|
||||
{error, Error} -> {error, Error, <<"">>}
|
||||
end;
|
||||
{continue, ServerOut, NewMechState} ->
|
||||
{continue, ServerOut, State#sasl_state{mech_state = NewMechState}};
|
||||
{error, Error, Username} ->
|
||||
{error, Error, Username};
|
||||
{error, Error} ->
|
||||
{error, Error, <<"">>}
|
||||
end.
|
||||
|
||||
-spec get_mech(sasl_state()) -> binary().
|
||||
get_mech(#sasl_state{mech_name = Mech}) ->
|
||||
Mech.
|
@ -1,50 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_anonymous.erl
|
||||
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Purpose : ANONYMOUS SASL mechanism
|
||||
%%% See http://www.ietf.org/internet-drafts/draft-ietf-sasl-anon-05.txt
|
||||
%%% Created : 23 Aug 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl_anonymous).
|
||||
|
||||
-protocol({xep, 175, '1.2'}).
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {server = <<"">> :: binary()}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism(<<"ANONYMOUS">>, ?MODULE, plain).
|
||||
|
||||
stop() -> ok.
|
||||
|
||||
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{server = Host}}.
|
||||
|
||||
mech_step(#state{}, _ClientIn) ->
|
||||
User = iolist_to_binary([p1_rand:get_string(),
|
||||
integer_to_binary(p1_time_compat:unique_integer([positive]))]),
|
||||
{ok, [{username, User},
|
||||
{authzid, User},
|
||||
{auth_module, ejabberd_auth_anonymous}]}.
|
@ -1,270 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_digest.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : DIGEST-MD5 SASL mechanism
|
||||
%%% Created : 11 Mar 2003 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl_digest).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@sevcom.net').
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2,
|
||||
parse/1, format_error/1, opt_type/1]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-type get_password_fun() :: fun((binary()) -> {false, any()} |
|
||||
{binary(), atom()}).
|
||||
-type check_password_fun() :: fun((binary(), binary(), binary(), binary(),
|
||||
fun((binary()) -> binary())) ->
|
||||
{boolean(), any()} |
|
||||
false).
|
||||
-type error_reason() :: parser_failed | invalid_digest_uri |
|
||||
not_authorized | unexpected_response.
|
||||
-export_type([error_reason/0]).
|
||||
|
||||
-record(state, {step = 1 :: 1 | 3 | 5,
|
||||
nonce = <<"">> :: binary(),
|
||||
username = <<"">> :: binary(),
|
||||
authzid = <<"">> :: binary(),
|
||||
get_password :: get_password_fun(),
|
||||
check_password :: check_password_fun(),
|
||||
auth_module :: atom(),
|
||||
host = <<"">> :: binary(),
|
||||
hostfqdn = [] :: [binary()]}).
|
||||
|
||||
start(_Opts) ->
|
||||
Fqdn = get_local_fqdn(),
|
||||
?DEBUG("FQDN used to check DIGEST-MD5 SASL authentication: ~s",
|
||||
[Fqdn]),
|
||||
cyrsasl:register_mechanism(<<"DIGEST-MD5">>, ?MODULE,
|
||||
digest).
|
||||
|
||||
stop() -> ok.
|
||||
|
||||
-spec format_error(error_reason()) -> {atom(), binary()}.
|
||||
format_error(parser_failed) ->
|
||||
{'bad-protocol', <<"Response decoding failed">>};
|
||||
format_error(invalid_digest_uri) ->
|
||||
{'bad-protocol', <<"Invalid digest URI">>};
|
||||
format_error(not_authorized) ->
|
||||
{'not-authorized', <<"Invalid username or password">>};
|
||||
format_error(unexpected_response) ->
|
||||
{'bad-protocol', <<"Unexpected response">>}.
|
||||
|
||||
mech_new(Host, GetPassword, _CheckPassword,
|
||||
CheckPasswordDigest) ->
|
||||
{ok,
|
||||
#state{step = 1, nonce = p1_rand:get_string(),
|
||||
host = Host, hostfqdn = get_local_fqdn(),
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPasswordDigest}}.
|
||||
|
||||
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
{continue,
|
||||
<<"nonce=\"", Nonce/binary,
|
||||
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess">>,
|
||||
State#state{step = 3}};
|
||||
mech_step(#state{step = 3, nonce = Nonce} = State,
|
||||
ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
bad -> {error, parser_failed};
|
||||
KeyVals ->
|
||||
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
|
||||
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
|
||||
case is_digesturi_valid(DigestURI, State#state.host,
|
||||
State#state.hostfqdn)
|
||||
of
|
||||
false ->
|
||||
?DEBUG("User login not authorized because digest-uri "
|
||||
"seems invalid: ~p (checking for Host "
|
||||
"~p, FQDN ~p)",
|
||||
[DigestURI, State#state.host, State#state.hostfqdn]),
|
||||
{error, invalid_digest_uri, UserName};
|
||||
true ->
|
||||
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} -> {error, not_authorized, UserName};
|
||||
{Passwd, AuthModule} ->
|
||||
case (State#state.check_password)(UserName, UserName, <<"">>,
|
||||
proplists:get_value(<<"response">>, KeyVals, <<>>),
|
||||
fun (PW) ->
|
||||
response(KeyVals,
|
||||
UserName,
|
||||
PW,
|
||||
Nonce,
|
||||
AuthzId,
|
||||
<<"AUTHENTICATE">>)
|
||||
end)
|
||||
of
|
||||
{true, _} ->
|
||||
RspAuth = response(KeyVals, UserName, Passwd, Nonce,
|
||||
AuthzId, <<"">>),
|
||||
{continue, <<"rspauth=", RspAuth/binary>>,
|
||||
State#state{step = 5, auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
false -> {error, not_authorized, UserName};
|
||||
{false, _} -> {error, not_authorized, UserName}
|
||||
end
|
||||
end
|
||||
end
|
||||
end;
|
||||
mech_step(#state{step = 5, auth_module = AuthModule,
|
||||
username = UserName, authzid = AuthzId},
|
||||
<<"">>) ->
|
||||
{ok,
|
||||
[{username, UserName}, {authzid, case AuthzId of
|
||||
<<"">> -> UserName;
|
||||
_ -> AuthzId
|
||||
end
|
||||
},
|
||||
{auth_module, AuthModule}]};
|
||||
mech_step(A, B) ->
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
|
||||
{error, unexpected_response}.
|
||||
|
||||
parse(S) -> parse1(binary_to_list(S), "", []).
|
||||
|
||||
parse1([$= | Cs], S, Ts) ->
|
||||
parse2(Cs, lists:reverse(S), "", Ts);
|
||||
parse1([$, | Cs], [], Ts) -> parse1(Cs, [], Ts);
|
||||
parse1([$\s | Cs], [], Ts) -> parse1(Cs, [], Ts);
|
||||
parse1([C | Cs], S, Ts) -> parse1(Cs, [C | S], Ts);
|
||||
parse1([], [], T) -> lists:reverse(T);
|
||||
parse1([], _S, _T) -> bad.
|
||||
|
||||
parse2([$" | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, Val, Ts);
|
||||
parse2([C | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, [C | Val], Ts);
|
||||
parse2([], _, _, _) -> bad.
|
||||
|
||||
parse3([$" | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, Val, Ts);
|
||||
parse3([$\\, C | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, [C | Val], Ts);
|
||||
parse3([C | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, [C | Val], Ts);
|
||||
parse3([], _, _, _) -> bad.
|
||||
|
||||
parse4([$, | Cs], Key, Val, Ts) ->
|
||||
parse1(Cs, "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]);
|
||||
parse4([$\s | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, Val, Ts);
|
||||
parse4([C | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, [C | Val], Ts);
|
||||
parse4([], Key, Val, Ts) ->
|
||||
%% @doc Check if the digest-uri is valid.
|
||||
%% RFC-2831 allows to provide the IP address in Host,
|
||||
%% however ejabberd doesn't allow that.
|
||||
%% If the service (for example jabber.example.org)
|
||||
%% is provided by several hosts (being one of them server3.example.org),
|
||||
%% then acceptable digest-uris would be:
|
||||
%% xmpp/server3.example.org/jabber.example.org, xmpp/server3.example.org and
|
||||
%% xmpp/jabber.example.org
|
||||
%% The last version is not actually allowed by the RFC, but implemented by popular clients
|
||||
parse1([], "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]).
|
||||
|
||||
is_digesturi_valid(DigestURICase, JabberDomain,
|
||||
JabberFQDN) ->
|
||||
DigestURI = stringprep:tolower(DigestURICase),
|
||||
case catch str:tokens(DigestURI, <<"/">>) of
|
||||
[<<"xmpp">>, Host] ->
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
(Host == JabberDomain) or IsHostFqdn;
|
||||
[<<"xmpp">>, Host, ServName] ->
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
(ServName == JabberDomain) and IsHostFqdn;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
is_host_fqdn(_Host, []) ->
|
||||
false;
|
||||
is_host_fqdn(Host, [Fqdn | _FqdnTail]) when Host == Fqdn ->
|
||||
true;
|
||||
is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
|
||||
is_host_fqdn(Host, FqdnTail).
|
||||
|
||||
get_local_fqdn() ->
|
||||
case ejabberd_config:get_option(fqdn) of
|
||||
undefined ->
|
||||
{ok, Hostname} = inet:gethostname(),
|
||||
{ok, {hostent, Fqdn, _, _, _, _}} = inet:gethostbyname(Hostname),
|
||||
[list_to_binary(Fqdn)];
|
||||
Fqdn ->
|
||||
Fqdn
|
||||
end.
|
||||
|
||||
hex(S) ->
|
||||
str:to_hexlist(S).
|
||||
|
||||
proplists_get_bin_value(Key, Pairs, Default) ->
|
||||
case proplists:get_value(Key, Pairs, Default) of
|
||||
L when is_list(L) ->
|
||||
list_to_binary(L);
|
||||
L2 ->
|
||||
L2
|
||||
end.
|
||||
|
||||
response(KeyVals, User, Passwd, Nonce, AuthzId,
|
||||
A2Prefix) ->
|
||||
Realm = proplists_get_bin_value(<<"realm">>, KeyVals, <<>>),
|
||||
CNonce = proplists_get_bin_value(<<"cnonce">>, KeyVals, <<>>),
|
||||
DigestURI = proplists_get_bin_value(<<"digest-uri">>, KeyVals, <<>>),
|
||||
NC = proplists_get_bin_value(<<"nc">>, KeyVals, <<>>),
|
||||
QOP = proplists_get_bin_value(<<"qop">>, KeyVals, <<>>),
|
||||
MD5Hash = erlang:md5(<<User/binary, ":", Realm/binary, ":",
|
||||
Passwd/binary>>),
|
||||
A1 = case AuthzId of
|
||||
<<"">> ->
|
||||
<<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary>>;
|
||||
_ ->
|
||||
<<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary, ":",
|
||||
AuthzId/binary>>
|
||||
end,
|
||||
A2 = case QOP of
|
||||
<<"auth">> ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary>>;
|
||||
_ ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary,
|
||||
":00000000000000000000000000000000">>
|
||||
end,
|
||||
T = <<(hex((erlang:md5(A1))))/binary, ":", Nonce/binary,
|
||||
":", NC/binary, ":", CNonce/binary, ":", QOP/binary,
|
||||
":", (hex((erlang:md5(A2))))/binary>>,
|
||||
hex((erlang:md5(T))).
|
||||
|
||||
-spec opt_type(fqdn) -> fun((binary() | [binary()]) -> [binary()]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(fqdn) ->
|
||||
fun(FQDN) when is_binary(FQDN) ->
|
||||
[FQDN];
|
||||
(FQDNs) when is_list(FQDNs) ->
|
||||
[iolist_to_binary(FQDN) || FQDN <- FQDNs]
|
||||
end;
|
||||
opt_type(_) -> [fqdn].
|
@ -1,104 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_oauth.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : X-OAUTH2 SASL mechanism
|
||||
%%% Created : 17 Sep 2015 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl_oauth).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1, format_error/1]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {host}).
|
||||
-type error_reason() :: parser_failed | not_authorized.
|
||||
-export_type([error_reason/0]).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism(<<"X-OAUTH2">>, ?MODULE, plain).
|
||||
|
||||
stop() -> ok.
|
||||
|
||||
-spec format_error(error_reason()) -> {atom(), binary()}.
|
||||
format_error(parser_failed) ->
|
||||
{'bad-protocol', <<"Response decoding failed">>};
|
||||
format_error(not_authorized) ->
|
||||
{'not-authorized', <<"Invalid token">>}.
|
||||
|
||||
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{host = Host}}.
|
||||
|
||||
mech_step(State, ClientIn) ->
|
||||
case prepare(ClientIn) of
|
||||
[AuthzId, User, Token] ->
|
||||
case ejabberd_oauth:check_token(
|
||||
User, State#state.host, [<<"sasl_auth">>], Token) of
|
||||
true ->
|
||||
{ok,
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, ejabberd_oauth}]};
|
||||
_ ->
|
||||
{error, not_authorized, User}
|
||||
end;
|
||||
_ -> {error, parser_failed}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
[<<"">>, UserMaybeDomain, Token] ->
|
||||
case parse_domain(UserMaybeDomain) of
|
||||
%% <NUL>login@domain<NUL>pwd
|
||||
[User, _Domain] -> [User, User, Token];
|
||||
%% <NUL>login<NUL>pwd
|
||||
[User] -> [User, User, Token]
|
||||
end;
|
||||
%% login@domain<NUL>login<NUL>pwd
|
||||
[AuthzId, User, Token] ->
|
||||
case parse_domain(AuthzId) of
|
||||
%% login@domain<NUL>login<NUL>pwd
|
||||
[AuthzUser, _Domain] -> [AuthzUser, User, Token];
|
||||
%% login<NUL>login<NUL>pwd
|
||||
[AuthzUser] -> [AuthzUser, User, Token]
|
||||
end;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
parse(S) -> parse1(binary_to_list(S), "", []).
|
||||
|
||||
parse1([0 | Cs], S, T) ->
|
||||
parse1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
|
||||
parse1([C | Cs], S, T) -> parse1(Cs, [C | S], T);
|
||||
%parse1([], [], T) ->
|
||||
% lists:reverse(T);
|
||||
parse1([], S, T) ->
|
||||
lists:reverse([list_to_binary(lists:reverse(S)) | T]).
|
||||
|
||||
parse_domain(S) -> parse_domain1(binary_to_list(S), "", []).
|
||||
|
||||
parse_domain1([$@ | Cs], S, T) ->
|
||||
parse_domain1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
|
||||
parse_domain1([C | Cs], S, T) ->
|
||||
parse_domain1(Cs, [C | S], T);
|
||||
parse_domain1([], S, T) ->
|
||||
lists:reverse([list_to_binary(lists:reverse(S)) | T]).
|
@ -1,94 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_plain.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : PLAIN SASL mechanism
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl_plain).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1, format_error/1]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {check_password}).
|
||||
-type error_reason() :: parser_failed | not_authorized.
|
||||
-export_type([error_reason/0]).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism(<<"PLAIN">>, ?MODULE, plain).
|
||||
|
||||
stop() -> ok.
|
||||
|
||||
-spec format_error(error_reason()) -> {atom(), binary()}.
|
||||
format_error(parser_failed) ->
|
||||
{'bad-protocol', <<"Response decoding failed">>};
|
||||
format_error(not_authorized) ->
|
||||
{'not-authorized', <<"Invalid username or password">>}.
|
||||
|
||||
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{check_password = CheckPassword}}.
|
||||
|
||||
mech_step(State, ClientIn) ->
|
||||
case prepare(ClientIn) of
|
||||
[AuthzId, User, Password] ->
|
||||
case (State#state.check_password)(User, AuthzId, Password) of
|
||||
{true, AuthModule} ->
|
||||
{ok,
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
_ -> {error, not_authorized, User}
|
||||
end;
|
||||
_ -> {error, parser_failed}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
[<<"">>, UserMaybeDomain, Password] ->
|
||||
case parse_domain(UserMaybeDomain) of
|
||||
%% <NUL>login@domain<NUL>pwd
|
||||
[User, _Domain] -> [User, User, Password];
|
||||
%% <NUL>login<NUL>pwd
|
||||
[User] -> [User, User, Password]
|
||||
end;
|
||||
[AuthzId, User, Password] ->
|
||||
case parse_domain(AuthzId) of
|
||||
%% login@domain<NUL>login<NUL>pwd
|
||||
[AuthzUser, _Domain] -> [AuthzUser, User, Password];
|
||||
%% login<NUL>login<NUL>pwd
|
||||
[AuthzUser] -> [AuthzUser, User, Password]
|
||||
end;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
parse(S) ->
|
||||
binary:split(S, <<0>>, [global]).
|
||||
|
||||
parse_domain(S) -> parse_domain1(binary_to_list(S), "", []).
|
||||
|
||||
parse_domain1([$@ | Cs], S, T) ->
|
||||
parse_domain1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
|
||||
parse_domain1([C | Cs], S, T) ->
|
||||
parse_domain1(Cs, [C | S], T);
|
||||
parse_domain1([], S, T) ->
|
||||
lists:reverse([list_to_binary(lists:reverse(S)) | T]).
|
@ -1,249 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_scram.erl
|
||||
%%% Author : Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%% Purpose : SASL SCRAM authentication
|
||||
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl_scram).
|
||||
|
||||
-author('stephen.roettger@googlemail.com').
|
||||
|
||||
-protocol({rfc, 5802}).
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, format_error/1]).
|
||||
|
||||
-include("scram.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state,
|
||||
{step = 2 :: 2 | 4,
|
||||
stored_key = <<"">> :: binary(),
|
||||
server_key = <<"">> :: binary(),
|
||||
username = <<"">> :: binary(),
|
||||
auth_module :: module(),
|
||||
get_password :: fun((binary()) ->
|
||||
{false | ejabberd_auth:password(), module()}),
|
||||
auth_message = <<"">> :: binary(),
|
||||
client_nonce = <<"">> :: binary(),
|
||||
server_nonce = <<"">> :: binary()}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
-define(NONCE_LENGTH, 16).
|
||||
|
||||
-type error_reason() :: unsupported_extension | bad_username |
|
||||
not_authorized | saslprep_failed |
|
||||
parser_failed | bad_attribute |
|
||||
nonce_mismatch | bad_channel_binding.
|
||||
|
||||
-export_type([error_reason/0]).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism(<<"SCRAM-SHA-1">>, ?MODULE,
|
||||
scram).
|
||||
|
||||
stop() -> ok.
|
||||
|
||||
-spec format_error(error_reason()) -> {atom(), binary()}.
|
||||
format_error(unsupported_extension) ->
|
||||
{'bad-protocol', <<"Unsupported extension">>};
|
||||
format_error(bad_username) ->
|
||||
{'invalid-authzid', <<"Malformed username">>};
|
||||
format_error(not_authorized) ->
|
||||
{'not-authorized', <<"Invalid username or password">>};
|
||||
format_error(saslprep_failed) ->
|
||||
{'not-authorized', <<"SASLprep failed">>};
|
||||
format_error(parser_failed) ->
|
||||
{'bad-protocol', <<"Response decoding failed">>};
|
||||
format_error(bad_attribute) ->
|
||||
{'bad-protocol', <<"Malformed or unexpected attribute">>};
|
||||
format_error(nonce_mismatch) ->
|
||||
{'bad-protocol', <<"Nonce mismatch">>};
|
||||
format_error(bad_channel_binding) ->
|
||||
{'bad-protocol', <<"Invalid channel binding">>}.
|
||||
|
||||
mech_new(_Host, GetPassword, _CheckPassword,
|
||||
_CheckPasswordDigest) ->
|
||||
{ok, #state{step = 2, get_password = GetPassword}}.
|
||||
|
||||
mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
case re:split(ClientIn, <<",">>, [{return, binary}]) of
|
||||
[_CBind, _AuthorizationIdentity, _UserNameAttribute, _ClientNonceAttribute, ExtensionAttribute | _]
|
||||
when ExtensionAttribute /= <<"">> ->
|
||||
{error, unsupported_extension};
|
||||
[CBind, _AuthorizationIdentity, UserNameAttribute, ClientNonceAttribute | _]
|
||||
when (CBind == <<"y">>) or (CBind == <<"n">>) ->
|
||||
case parse_attribute(UserNameAttribute) of
|
||||
{error, Reason} -> {error, Reason};
|
||||
{_, EscapedUserName} ->
|
||||
case unescape_username(EscapedUserName) of
|
||||
error -> {error, bad_username};
|
||||
UserName ->
|
||||
case parse_attribute(ClientNonceAttribute) of
|
||||
{$r, ClientNonce} ->
|
||||
{Pass, AuthModule} = (State#state.get_password)(UserName),
|
||||
LPass = if is_binary(Pass) -> jid:resourceprep(Pass);
|
||||
true -> Pass
|
||||
end,
|
||||
if Pass == false ->
|
||||
{error, not_authorized, UserName};
|
||||
LPass == error ->
|
||||
{error, saslprep_failed, UserName};
|
||||
true ->
|
||||
{StoredKey, ServerKey, Salt, IterationCount} =
|
||||
if is_record(Pass, scram) ->
|
||||
{base64:decode(Pass#scram.storedkey),
|
||||
base64:decode(Pass#scram.serverkey),
|
||||
base64:decode(Pass#scram.salt),
|
||||
Pass#scram.iterationcount};
|
||||
true ->
|
||||
TempSalt =
|
||||
p1_rand:bytes(?SALT_LENGTH),
|
||||
SaltedPassword =
|
||||
scram:salted_password(Pass,
|
||||
TempSalt,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT),
|
||||
{scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
scram:server_key(SaltedPassword),
|
||||
TempSalt,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT}
|
||||
end,
|
||||
ClientFirstMessageBare =
|
||||
str:substr(ClientIn,
|
||||
str:str(ClientIn, <<"n=">>)),
|
||||
ServerNonce =
|
||||
base64:encode(p1_rand:bytes(?NONCE_LENGTH)),
|
||||
ServerFirstMessage =
|
||||
iolist_to_binary(
|
||||
["r=",
|
||||
ClientNonce,
|
||||
ServerNonce,
|
||||
",", "s=",
|
||||
base64:encode(Salt),
|
||||
",", "i=",
|
||||
integer_to_list(IterationCount)]),
|
||||
{continue, ServerFirstMessage,
|
||||
State#state{step = 4, stored_key = StoredKey,
|
||||
server_key = ServerKey,
|
||||
auth_module = AuthModule,
|
||||
auth_message =
|
||||
<<ClientFirstMessageBare/binary,
|
||||
",", ServerFirstMessage/binary>>,
|
||||
client_nonce = ClientNonce,
|
||||
server_nonce = ServerNonce,
|
||||
username = UserName}}
|
||||
end;
|
||||
_ -> {error, bad_attribute}
|
||||
end
|
||||
end
|
||||
end;
|
||||
_Else -> {error, parser_failed}
|
||||
end;
|
||||
mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
case str:tokens(ClientIn, <<",">>) of
|
||||
[GS2ChannelBindingAttribute, NonceAttribute,
|
||||
ClientProofAttribute] ->
|
||||
case parse_attribute(GS2ChannelBindingAttribute) of
|
||||
{$c, CVal} ->
|
||||
ChannelBindingSupport = try binary:first(base64:decode(CVal))
|
||||
catch _:badarg -> 0
|
||||
end,
|
||||
if (ChannelBindingSupport == $n)
|
||||
or (ChannelBindingSupport == $y) ->
|
||||
Nonce = <<(State#state.client_nonce)/binary,
|
||||
(State#state.server_nonce)/binary>>,
|
||||
case parse_attribute(NonceAttribute) of
|
||||
{$r, CompareNonce} when CompareNonce == Nonce ->
|
||||
case parse_attribute(ClientProofAttribute) of
|
||||
{$p, ClientProofB64} ->
|
||||
ClientProof = try base64:decode(ClientProofB64)
|
||||
catch _:badarg -> <<>>
|
||||
end,
|
||||
AuthMessage = iolist_to_binary(
|
||||
[State#state.auth_message,
|
||||
",",
|
||||
str:substr(ClientIn, 1,
|
||||
str:str(ClientIn, <<",p=">>)
|
||||
- 1)]),
|
||||
ClientSignature =
|
||||
scram:client_signature(State#state.stored_key,
|
||||
AuthMessage),
|
||||
ClientKey = scram:client_key(ClientProof,
|
||||
ClientSignature),
|
||||
CompareStoredKey = scram:stored_key(ClientKey),
|
||||
if CompareStoredKey == State#state.stored_key ->
|
||||
ServerSignature =
|
||||
scram:server_signature(State#state.server_key,
|
||||
AuthMessage),
|
||||
{ok, [{username, State#state.username},
|
||||
{auth_module, State#state.auth_module},
|
||||
{authzid, State#state.username}],
|
||||
<<"v=",
|
||||
(base64:encode(ServerSignature))/binary>>};
|
||||
true -> {error, not_authorized, State#state.username}
|
||||
end;
|
||||
_ -> {error, bad_attribute}
|
||||
end;
|
||||
{$r, _} -> {error, nonce_mismatch};
|
||||
_ -> {error, bad_attribute}
|
||||
end;
|
||||
true -> {error, bad_channel_binding}
|
||||
end;
|
||||
_ -> {error, bad_attribute}
|
||||
end;
|
||||
_ -> {error, parser_failed}
|
||||
end.
|
||||
|
||||
parse_attribute(<<Name, $=, Val/binary>>) when Val /= <<>> ->
|
||||
case is_alpha(Name) of
|
||||
true -> {Name, Val};
|
||||
false -> {error, bad_attribute}
|
||||
end;
|
||||
parse_attribute(_) ->
|
||||
{error, bad_attribute}.
|
||||
|
||||
unescape_username(<<"">>) -> <<"">>;
|
||||
unescape_username(EscapedUsername) ->
|
||||
Pos = str:str(EscapedUsername, <<"=">>),
|
||||
if Pos == 0 -> EscapedUsername;
|
||||
true ->
|
||||
Start = str:substr(EscapedUsername, 1, Pos - 1),
|
||||
End = str:substr(EscapedUsername, Pos),
|
||||
EndLen = byte_size(End),
|
||||
if EndLen < 3 -> error;
|
||||
true ->
|
||||
case str:substr(End, 1, 3) of
|
||||
<<"=2C">> ->
|
||||
<<Start/binary, ",",
|
||||
(unescape_username(str:substr(End, 4)))/binary>>;
|
||||
<<"=3D">> ->
|
||||
<<Start/binary, "=",
|
||||
(unescape_username(str:substr(End, 4)))/binary>>;
|
||||
_Else -> error
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
is_alpha(Char) when Char >= $a, Char =< $z -> true;
|
||||
is_alpha(Char) when Char >= $A, Char =< $Z -> true;
|
||||
is_alpha(_) -> false.
|
@ -524,9 +524,7 @@ handle_event({become_controller, C2SPid}, StateName,
|
||||
{next_state, StateName, State1};
|
||||
handle_event({change_shaper, Shaper}, StateName,
|
||||
State) ->
|
||||
NewShaperState = ejabberd_shaper:new(Shaper),
|
||||
{next_state, StateName,
|
||||
State#state{shaper_state = NewShaperState}};
|
||||
{next_state, StateName, State#state{shaper_state = Shaper}};
|
||||
handle_event(_Event, StateName, State) ->
|
||||
?ERROR_MSG("unexpected event in '~s': ~p",
|
||||
[StateName, _Event]),
|
||||
|
@ -33,9 +33,9 @@
|
||||
%% xmpp_stream_in callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
|
||||
-export([tls_options/1, tls_required/1, tls_enabled/1,
|
||||
compress_methods/1, bind/2, sasl_mechanisms/2,
|
||||
get_password_fun/1, check_password_fun/1, check_password_digest_fun/1,
|
||||
get_password_fun/2, check_password_fun/2, check_password_digest_fun/2,
|
||||
unauthenticated_stream_features/1, authenticated_stream_features/1,
|
||||
handle_stream_start/2, handle_stream_end/2,
|
||||
handle_unauthenticated_packet/2, handle_authenticated_packet/2,
|
||||
@ -339,9 +339,6 @@ tls_options(#{lserver := LServer, tls_options := DefaultOpts,
|
||||
tls_required(#{tls_required := TLSRequired}) ->
|
||||
TLSRequired.
|
||||
|
||||
tls_verify(#{tls_verify := TLSVerify}) ->
|
||||
TLSVerify.
|
||||
|
||||
tls_enabled(#{tls_enabled := TLSEnabled,
|
||||
tls_required := TLSRequired,
|
||||
tls_verify := TLSVerify}) ->
|
||||
@ -358,25 +355,41 @@ unauthenticated_stream_features(#{lserver := LServer}) ->
|
||||
authenticated_stream_features(#{lserver := LServer}) ->
|
||||
ejabberd_hooks:run_fold(c2s_post_auth_features, LServer, [], [LServer]).
|
||||
|
||||
sasl_mechanisms(Mechs, #{lserver := LServer}) ->
|
||||
sasl_mechanisms(Mechs, #{lserver := LServer} = State) ->
|
||||
Type = ejabberd_auth:store_type(LServer),
|
||||
Mechs1 = ejabberd_config:get_option({disable_sasl_mechanisms, LServer}, []),
|
||||
Mechs2 = case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer) of
|
||||
true -> Mechs1;
|
||||
false -> [<<"ANONYMOUS">>|Mechs1]
|
||||
end,
|
||||
Mechs -- Mechs2.
|
||||
%% I re-created it from cyrsasl ets magic, but I think it's wrong
|
||||
%% TODO: need to check before 18.09 release
|
||||
lists:filter(
|
||||
fun(<<"ANONYMOUS">>) ->
|
||||
ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer);
|
||||
(<<"DIGEST-MD5">>) -> Type == plain;
|
||||
(<<"SCRAM-SHA-1">>) -> Type /= external;
|
||||
(<<"PLAIN">>) -> true;
|
||||
(<<"X-OAUTH2">>) -> true;
|
||||
(<<"EXTERNAL">>) -> maps:get(tls_verify, State, false);
|
||||
(_) -> false
|
||||
end, Mechs -- Mechs1).
|
||||
|
||||
get_password_fun(#{lserver := LServer}) ->
|
||||
get_password_fun(_Mech, #{lserver := LServer}) ->
|
||||
fun(U) ->
|
||||
ejabberd_auth:get_password_with_authmodule(U, LServer)
|
||||
end.
|
||||
|
||||
check_password_fun(#{lserver := LServer}) ->
|
||||
check_password_fun(<<"X-OAUTH2">>, #{lserver := LServer}) ->
|
||||
fun(User, _AuthzId, Token) ->
|
||||
case ejabberd_oauth:check_token(
|
||||
User, LServer, [<<"sasl_auth">>], Token) of
|
||||
true -> {true, ejabberd_oauth};
|
||||
_ -> {false, ejabberd_oauth}
|
||||
end
|
||||
end;
|
||||
check_password_fun(_Mech, #{lserver := LServer}) ->
|
||||
fun(U, AuthzId, P) ->
|
||||
ejabberd_auth:check_password_with_authmodule(U, AuthzId, LServer, P)
|
||||
end.
|
||||
|
||||
check_password_digest_fun(#{lserver := LServer}) ->
|
||||
check_password_digest_fun(_Mech, #{lserver := LServer}) ->
|
||||
fun(U, AuthzId, P, D, DG) ->
|
||||
ejabberd_auth:check_password_with_authmodule(U, AuthzId, LServer, P, D, DG)
|
||||
end.
|
||||
@ -920,7 +933,7 @@ change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer,
|
||||
Shaper = acl:access_matches(ShaperName,
|
||||
#{usr => jid:split(JID), ip => IP},
|
||||
LServer),
|
||||
xmpp_stream_in:change_shaper(State, Shaper).
|
||||
xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)).
|
||||
|
||||
-spec format_reason(state(), term()) -> binary().
|
||||
format_reason(#{stop_reason := Reason}, _) ->
|
||||
|
@ -782,8 +782,13 @@ set_opts(State) ->
|
||||
fun(#local_config{key = Key, value = Val}) ->
|
||||
{Key, Val}
|
||||
end, Opts)),
|
||||
set_fqdn(),
|
||||
set_log_level().
|
||||
|
||||
set_fqdn() ->
|
||||
FQDNs = get_option(fqdn, []),
|
||||
xmpp:set_config([{fqdn, FQDNs}]).
|
||||
|
||||
set_log_level() ->
|
||||
Level = get_option(loglevel, 4),
|
||||
ejabberd_logger:set(Level).
|
||||
@ -1452,10 +1457,16 @@ opt_type(node_start) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I end;
|
||||
opt_type(validate_stream) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(fqdn) ->
|
||||
fun(Domain) when is_binary(Domain) ->
|
||||
[Domain];
|
||||
(Domains) ->
|
||||
[iolist_to_binary(Domain) || Domain <- Domains]
|
||||
end;
|
||||
opt_type(_) ->
|
||||
[hide_sensitive_log_data, hosts, language, max_fsm_queue,
|
||||
default_db, default_ram_db, queue_type, queue_dir, loglevel,
|
||||
use_cache, cache_size, cache_missed, cache_life_time,
|
||||
use_cache, cache_size, cache_missed, cache_life_time, fqdn,
|
||||
shared_key, node_start, validate_stream, negotiation_timeout].
|
||||
|
||||
-spec may_hide_data(any()) -> any().
|
||||
|
@ -1,224 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_idna.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Support for IDNA (RFC3490)
|
||||
%%% Created : 10 Apr 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_idna).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([domain_utf8_to_ascii/1,
|
||||
domain_ucs2_to_ascii/1,
|
||||
utf8_to_ucs2/1]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-endif.
|
||||
|
||||
-spec domain_utf8_to_ascii(binary()) -> false | binary().
|
||||
|
||||
domain_utf8_to_ascii(Domain) ->
|
||||
domain_ucs2_to_ascii(utf8_to_ucs2(Domain)).
|
||||
|
||||
utf8_to_ucs2(S) ->
|
||||
utf8_to_ucs2(binary_to_list(S), "").
|
||||
|
||||
utf8_to_ucs2([], R) -> lists:reverse(R);
|
||||
utf8_to_ucs2([C | S], R) when C < 128 ->
|
||||
utf8_to_ucs2(S, [C | R]);
|
||||
utf8_to_ucs2([C1, C2 | S], R) when C1 < 224 ->
|
||||
utf8_to_ucs2(S, [C1 band 31 bsl 6 bor C2 band 63 | R]);
|
||||
utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 240 ->
|
||||
utf8_to_ucs2(S,
|
||||
[C1 band 15 bsl 12 bor (C2 band 63 bsl 6) bor C3 band 63
|
||||
| R]).
|
||||
|
||||
-spec domain_ucs2_to_ascii(list()) -> false | binary().
|
||||
|
||||
domain_ucs2_to_ascii(Domain) ->
|
||||
case catch domain_ucs2_to_ascii1(Domain) of
|
||||
{'EXIT', _Reason} -> false;
|
||||
Res -> iolist_to_binary(Res)
|
||||
end.
|
||||
|
||||
domain_ucs2_to_ascii1(Domain) ->
|
||||
Parts = string:tokens(Domain,
|
||||
[46, 12290, 65294, 65377]),
|
||||
ASCIIParts = lists:map(fun (P) -> to_ascii(P) end,
|
||||
Parts),
|
||||
string:strip(lists:flatmap(fun (P) -> [$. | P] end,
|
||||
ASCIIParts),
|
||||
left, $.).
|
||||
|
||||
%% Domain names are already nameprep'ed in ejabberd, so we skiping this step
|
||||
to_ascii(Name) ->
|
||||
false = lists:any(fun (C)
|
||||
when (0 =< C) and (C =< 44) or
|
||||
(46 =< C) and (C =< 47)
|
||||
or (58 =< C) and (C =< 64)
|
||||
or (91 =< C) and (C =< 96)
|
||||
or (123 =< C) and (C =< 127) ->
|
||||
true;
|
||||
(_) -> false
|
||||
end,
|
||||
Name),
|
||||
case Name of
|
||||
[H | _] when H /= $- -> true = lists:last(Name) /= $-
|
||||
end,
|
||||
ASCIIName = case lists:any(fun (C) -> C > 127 end, Name)
|
||||
of
|
||||
true ->
|
||||
true = case Name of
|
||||
"xn--" ++ _ -> false;
|
||||
_ -> true
|
||||
end,
|
||||
"xn--" ++ punycode_encode(Name);
|
||||
false -> Name
|
||||
end,
|
||||
L = length(ASCIIName),
|
||||
true = (1 =< L) and (L =< 63),
|
||||
ASCIIName.
|
||||
|
||||
%%% PUNYCODE (RFC3492)
|
||||
|
||||
-define(BASE, 36).
|
||||
|
||||
-define(TMIN, 1).
|
||||
|
||||
-define(TMAX, 26).
|
||||
|
||||
-define(SKEW, 38).
|
||||
|
||||
-define(DAMP, 700).
|
||||
|
||||
-define(INITIAL_BIAS, 72).
|
||||
|
||||
-define(INITIAL_N, 128).
|
||||
|
||||
punycode_encode(Input) ->
|
||||
N = (?INITIAL_N),
|
||||
Delta = 0,
|
||||
Bias = (?INITIAL_BIAS),
|
||||
Basic = lists:filter(fun (C) -> C =< 127 end, Input),
|
||||
NonBasic = lists:filter(fun (C) -> C > 127 end, Input),
|
||||
L = length(Input),
|
||||
B = length(Basic),
|
||||
SNonBasic = lists:usort(NonBasic),
|
||||
Output1 = if B > 0 -> Basic ++ "-";
|
||||
true -> ""
|
||||
end,
|
||||
Output2 = punycode_encode1(Input, SNonBasic, B, B, L, N,
|
||||
Delta, Bias, ""),
|
||||
Output1 ++ Output2.
|
||||
|
||||
punycode_encode1(Input, [M | SNonBasic], B, H, L, N,
|
||||
Delta, Bias, Out)
|
||||
when H < L ->
|
||||
Delta1 = Delta + (M - N) * (H + 1),
|
||||
% let n = m
|
||||
{NewDelta, NewBias, NewH, NewOut} = lists:foldl(fun (C,
|
||||
{ADelta, ABias, AH,
|
||||
AOut}) ->
|
||||
if C < M ->
|
||||
{ADelta + 1,
|
||||
ABias, AH,
|
||||
AOut};
|
||||
C == M ->
|
||||
NewOut =
|
||||
punycode_encode_delta(ADelta,
|
||||
ABias,
|
||||
AOut),
|
||||
NewBias =
|
||||
adapt(ADelta,
|
||||
H +
|
||||
1,
|
||||
H
|
||||
==
|
||||
B),
|
||||
{0, NewBias,
|
||||
AH + 1,
|
||||
NewOut};
|
||||
true ->
|
||||
{ADelta,
|
||||
ABias, AH,
|
||||
AOut}
|
||||
end
|
||||
end,
|
||||
{Delta1, Bias, H, Out},
|
||||
Input),
|
||||
punycode_encode1(Input, SNonBasic, B, NewH, L, M + 1,
|
||||
NewDelta + 1, NewBias, NewOut);
|
||||
punycode_encode1(_Input, _SNonBasic, _B, _H, _L, _N,
|
||||
_Delta, _Bias, Out) ->
|
||||
lists:reverse(Out).
|
||||
|
||||
punycode_encode_delta(Delta, Bias, Out) ->
|
||||
punycode_encode_delta(Delta, Bias, Out, ?BASE).
|
||||
|
||||
punycode_encode_delta(Delta, Bias, Out, K) ->
|
||||
T = if K =< Bias -> ?TMIN;
|
||||
K >= Bias + (?TMAX) -> ?TMAX;
|
||||
true -> K - Bias
|
||||
end,
|
||||
if Delta < T -> [codepoint(Delta) | Out];
|
||||
true ->
|
||||
C = T + (Delta - T) rem ((?BASE) - T),
|
||||
punycode_encode_delta((Delta - T) div ((?BASE) - T),
|
||||
Bias, [codepoint(C) | Out], K + (?BASE))
|
||||
end.
|
||||
|
||||
adapt(Delta, NumPoints, FirstTime) ->
|
||||
Delta1 = if FirstTime -> Delta div (?DAMP);
|
||||
true -> Delta div 2
|
||||
end,
|
||||
Delta2 = Delta1 + Delta1 div NumPoints,
|
||||
adapt1(Delta2, 0).
|
||||
|
||||
adapt1(Delta, K) ->
|
||||
if Delta > ((?BASE) - (?TMIN)) * (?TMAX) div 2 ->
|
||||
adapt1(Delta div ((?BASE) - (?TMIN)), K + (?BASE));
|
||||
true ->
|
||||
K +
|
||||
((?BASE) - (?TMIN) + 1) * Delta div (Delta + (?SKEW))
|
||||
end.
|
||||
|
||||
codepoint(C) ->
|
||||
if (0 =< C) and (C =< 25) -> C + 97;
|
||||
(26 =< C) and (C =< 35) -> C + 22
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Unit tests
|
||||
%%%===================================================================
|
||||
-ifdef(TEST).
|
||||
|
||||
acsii_test() ->
|
||||
?assertEqual(<<"test.org">>, domain_utf8_to_ascii(<<"test.org">>)).
|
||||
|
||||
utf8_test() ->
|
||||
?assertEqual(
|
||||
<<"xn--d1acufc.xn--p1ai">>,
|
||||
domain_utf8_to_ascii(
|
||||
<<208,180,208,190,208,188,208,181,208,189,46,209,128,209,132>>)).
|
||||
|
||||
-endif.
|
@ -214,6 +214,10 @@ set(LogLevel) when is_integer(LogLevel) ->
|
||||
ok
|
||||
end, gen_event:which_handlers(lager_event))
|
||||
end,
|
||||
case LogLevel of
|
||||
5 -> xmpp:set_config([{debug, true}]);
|
||||
_ -> ok
|
||||
end,
|
||||
{module, lager};
|
||||
set({_LogLevel, _}) ->
|
||||
error_logger:error_msg("custom loglevels are not supported for 'lager'"),
|
||||
|
@ -133,7 +133,7 @@ get_certfile(Domain) ->
|
||||
|
||||
-spec get_certfile_no_default(binary()) -> {ok, binary()} | error.
|
||||
get_certfile_no_default(Domain) ->
|
||||
case ejabberd_idna:domain_utf8_to_ascii(Domain) of
|
||||
case xmpp_idna:domain_utf8_to_ascii(Domain) of
|
||||
false ->
|
||||
error;
|
||||
ASCIIDomain ->
|
||||
|
@ -30,8 +30,8 @@
|
||||
%% xmpp_stream_in callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
|
||||
compress_methods/1,
|
||||
-export([tls_options/1, tls_required/1, tls_enabled/1,
|
||||
compress_methods/1, sasl_mechanisms/2,
|
||||
unauthenticated_stream_features/1, authenticated_stream_features/1,
|
||||
handle_stream_start/2, handle_stream_end/2,
|
||||
handle_stream_established/1, handle_auth_success/4,
|
||||
@ -144,12 +144,15 @@ tls_options(#{tls_options := TLSOpts, server_host := LServer}) ->
|
||||
tls_required(#{server_host := LServer}) ->
|
||||
ejabberd_s2s:tls_required(LServer).
|
||||
|
||||
tls_verify(#{server_host := LServer}) ->
|
||||
ejabberd_s2s:tls_verify(LServer).
|
||||
|
||||
tls_enabled(#{server_host := LServer}) ->
|
||||
ejabberd_s2s:tls_enabled(LServer).
|
||||
|
||||
sasl_mechanisms(Mechs, #{server_host := LServer}) ->
|
||||
lists:filter(
|
||||
fun(<<"EXTERNAL">>) -> ejabberd_s2s:tls_verify(LServer);
|
||||
(_) -> false
|
||||
end, Mechs).
|
||||
|
||||
compress_methods(#{server_host := LServer}) ->
|
||||
case ejabberd_s2s:zlib_enabled(LServer) of
|
||||
true -> [<<"zlib">>];
|
||||
@ -344,7 +347,7 @@ set_idle_timeout(State) ->
|
||||
change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
|
||||
RServer) ->
|
||||
Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
|
||||
xmpp_stream_in:change_shaper(State, Shaper).
|
||||
xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)).
|
||||
|
||||
-spec listen_opt_type(shaper) -> fun((any()) -> any());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
|
@ -101,7 +101,7 @@ init([State, Opts]) ->
|
||||
end,
|
||||
GlobalRoutes = proplists:get_value(global_routes, Opts, true),
|
||||
Timeout = ejabberd_config:negotiation_timeout(),
|
||||
State1 = xmpp_stream_in:change_shaper(State, Shaper),
|
||||
State1 = xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)),
|
||||
State2 = xmpp_stream_in:set_timeout(State1, Timeout),
|
||||
State3 = State2#{access => Access,
|
||||
xmlns => ?NS_COMPONENT,
|
||||
|
@ -39,7 +39,6 @@ init([]) ->
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
[worker(ejabberd_hooks),
|
||||
worker(ejabberd_cluster),
|
||||
worker(cyrsasl),
|
||||
worker(translate),
|
||||
worker(ejabberd_access_permissions),
|
||||
worker(ejabberd_ctl),
|
||||
|
@ -1,81 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : scram.erl
|
||||
%%% Author : Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%% Purpose : SCRAM (RFC 5802)
|
||||
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(scram).
|
||||
|
||||
-author('stephen.roettger@googlemail.com').
|
||||
|
||||
%% External exports
|
||||
%% ejabberd doesn't implement SASLPREP, so we use the similar RESOURCEPREP instead
|
||||
-export([salted_password/3, stored_key/1, server_key/1,
|
||||
server_signature/2, client_signature/2, client_key/1,
|
||||
client_key/2]).
|
||||
|
||||
-spec salted_password(binary(), binary(), non_neg_integer()) -> binary().
|
||||
|
||||
salted_password(Password, Salt, IterationCount) ->
|
||||
hi(jid:resourceprep(Password), Salt, IterationCount).
|
||||
|
||||
-spec client_key(binary()) -> binary().
|
||||
|
||||
client_key(SaltedPassword) ->
|
||||
sha_mac(SaltedPassword, <<"Client Key">>).
|
||||
|
||||
-spec stored_key(binary()) -> binary().
|
||||
|
||||
stored_key(ClientKey) -> crypto:hash(sha, ClientKey).
|
||||
|
||||
-spec server_key(binary()) -> binary().
|
||||
|
||||
server_key(SaltedPassword) ->
|
||||
sha_mac(SaltedPassword, <<"Server Key">>).
|
||||
|
||||
-spec client_signature(binary(), binary()) -> binary().
|
||||
|
||||
client_signature(StoredKey, AuthMessage) ->
|
||||
sha_mac(StoredKey, AuthMessage).
|
||||
|
||||
-spec client_key(binary(), binary()) -> binary().
|
||||
|
||||
client_key(ClientProof, ClientSignature) ->
|
||||
crypto:exor(ClientProof, ClientSignature).
|
||||
|
||||
-spec server_signature(binary(), binary()) -> binary().
|
||||
|
||||
server_signature(ServerKey, AuthMessage) ->
|
||||
sha_mac(ServerKey, AuthMessage).
|
||||
|
||||
hi(Password, Salt, IterationCount) ->
|
||||
U1 = sha_mac(Password, <<Salt/binary, 0, 0, 0, 1>>),
|
||||
crypto:exor(U1, hi_round(Password, U1, IterationCount - 1)).
|
||||
|
||||
hi_round(Password, UPrev, 1) ->
|
||||
sha_mac(Password, UPrev);
|
||||
hi_round(Password, UPrev, IterationCount) ->
|
||||
U = sha_mac(Password, UPrev),
|
||||
crypto:exor(U, hi_round(Password, U, IterationCount - 1)).
|
||||
|
||||
sha_mac(Key, Data) ->
|
||||
crypto:hmac(sha, Key, Data).
|
@ -1,393 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : xmpp_socket.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Socket with zlib and TLS support library
|
||||
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(xmpp_socket).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%% API
|
||||
-export([start/4,
|
||||
connect/3,
|
||||
connect/4,
|
||||
connect/5,
|
||||
starttls/2,
|
||||
compress/1,
|
||||
compress/2,
|
||||
reset_stream/1,
|
||||
send_element/2,
|
||||
send_header/2,
|
||||
send_trailer/1,
|
||||
send/2,
|
||||
send_xml/2,
|
||||
recv/2,
|
||||
activate/1,
|
||||
change_shaper/2,
|
||||
monitor/1,
|
||||
get_sockmod/1,
|
||||
get_transport/1,
|
||||
get_peer_certificate/2,
|
||||
get_verify_result/1,
|
||||
close/1,
|
||||
pp/1,
|
||||
sockname/1, peername/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-type sockmod() :: ejabberd_bosh |
|
||||
ejabberd_http_ws |
|
||||
gen_tcp | fast_tls | ezlib.
|
||||
-type receiver() :: atom().
|
||||
-type socket() :: pid() | inet:socket() |
|
||||
fast_tls:tls_socket() |
|
||||
ezlib:zlib_socket() |
|
||||
ejabberd_bosh:bosh_socket() |
|
||||
ejabberd_http_ws:ws_socket().
|
||||
|
||||
-record(socket_state, {sockmod = gen_tcp :: sockmod(),
|
||||
socket :: socket(),
|
||||
max_stanza_size = infinity :: timeout(),
|
||||
xml_stream :: undefined | fxml_stream:xml_stream_state(),
|
||||
shaper = none :: none | ejabberd_shaper:shaper(),
|
||||
receiver :: receiver()}).
|
||||
|
||||
-type socket_state() :: #socket_state{}.
|
||||
|
||||
-export_type([socket/0, socket_state/0, sockmod/0]).
|
||||
|
||||
-callback start({module(), socket_state()},
|
||||
[proplists:property()]) -> {ok, pid()} | {error, term()} | ignore.
|
||||
-callback start_link({module(), socket_state()},
|
||||
[proplists:property()]) -> {ok, pid()} | {error, term()} | ignore.
|
||||
-callback socket_type() -> xml_stream | independent | raw.
|
||||
|
||||
-define(is_http_socket(S),
|
||||
(S#socket_state.sockmod == ejabberd_bosh orelse
|
||||
S#socket_state.sockmod == ejabberd_http_ws)).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
-spec start(atom(), sockmod(), socket(), [proplists:property()])
|
||||
-> {ok, pid() | independent} | {error, inet:posix() | any()} | ignore.
|
||||
start(Module, SockMod, Socket, Opts) ->
|
||||
try
|
||||
case Module:socket_type() of
|
||||
independent ->
|
||||
{ok, independent};
|
||||
xml_stream ->
|
||||
MaxStanzaSize = proplists:get_value(max_stanza_size, Opts, infinity),
|
||||
Receiver = proplists:get_value(receiver, Opts),
|
||||
SocketData = #socket_state{sockmod = SockMod,
|
||||
socket = Socket,
|
||||
receiver = Receiver,
|
||||
max_stanza_size = MaxStanzaSize},
|
||||
{ok, Pid} = Module:start({?MODULE, SocketData}, Opts),
|
||||
Receiver1 = if is_pid(Receiver) -> Receiver;
|
||||
true -> Pid
|
||||
end,
|
||||
ok = controlling_process(SocketData, Receiver1),
|
||||
ok = become_controller(SocketData, Pid),
|
||||
{ok, Receiver1};
|
||||
raw ->
|
||||
{ok, Pid} = Module:start({SockMod, Socket}, Opts),
|
||||
ok = SockMod:controlling_process(Socket, Pid),
|
||||
{ok, Pid}
|
||||
end
|
||||
catch
|
||||
_:{badmatch, {error, _} = Err} ->
|
||||
SockMod:close(Socket),
|
||||
Err;
|
||||
_:{badmatch, ignore} ->
|
||||
SockMod:close(Socket),
|
||||
ignore
|
||||
end.
|
||||
|
||||
connect(Addr, Port, Opts) ->
|
||||
connect(Addr, Port, Opts, infinity, self()).
|
||||
|
||||
connect(Addr, Port, Opts, Timeout) ->
|
||||
connect(Addr, Port, Opts, Timeout, self()).
|
||||
|
||||
connect(Addr, Port, Opts, Timeout, Owner) ->
|
||||
case gen_tcp:connect(Addr, Port, Opts, Timeout) of
|
||||
{ok, Socket} ->
|
||||
SocketData = #socket_state{sockmod = gen_tcp, socket = Socket},
|
||||
case controlling_process(SocketData, Owner) of
|
||||
ok ->
|
||||
activate_after(Socket, Owner, 0),
|
||||
{ok, SocketData};
|
||||
{error, _Reason} = Error ->
|
||||
gen_tcp:close(Socket),
|
||||
Error
|
||||
end;
|
||||
{error, _Reason} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
starttls(#socket_state{socket = Socket,
|
||||
receiver = undefined} = SocketData, TLSOpts) ->
|
||||
case fast_tls:tcp_to_tls(Socket, TLSOpts) of
|
||||
{ok, TLSSocket} ->
|
||||
SocketData1 = SocketData#socket_state{socket = TLSSocket,
|
||||
sockmod = fast_tls},
|
||||
SocketData2 = reset_stream(SocketData1),
|
||||
case fast_tls:recv_data(TLSSocket, <<>>) of
|
||||
{ok, TLSData} ->
|
||||
parse(SocketData2, TLSData);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
compress(SocketData) -> compress(SocketData, undefined).
|
||||
|
||||
compress(#socket_state{receiver = undefined,
|
||||
sockmod = SockMod,
|
||||
socket = Socket} = SocketData, Data) ->
|
||||
{ok, ZlibSocket} = ezlib:enable_zlib(SockMod, Socket),
|
||||
case Data of
|
||||
undefined -> ok;
|
||||
_ -> send(SocketData, Data)
|
||||
end,
|
||||
SocketData1 = SocketData#socket_state{socket = ZlibSocket,
|
||||
sockmod = ezlib},
|
||||
SocketData2 = reset_stream(SocketData1),
|
||||
case ezlib:recv_data(ZlibSocket, <<"">>) of
|
||||
{ok, ZlibData} ->
|
||||
parse(SocketData2, ZlibData);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
reset_stream(#socket_state{xml_stream = XMLStream,
|
||||
receiver = Receiver,
|
||||
sockmod = SockMod, socket = Socket,
|
||||
max_stanza_size = MaxStanzaSize} = SocketData) ->
|
||||
XMLStream1 = try fxml_stream:reset(XMLStream)
|
||||
catch error:_ ->
|
||||
close_stream(XMLStream),
|
||||
fxml_stream:new(self(), MaxStanzaSize)
|
||||
end,
|
||||
case Receiver of
|
||||
undefined ->
|
||||
SocketData#socket_state{xml_stream = XMLStream1};
|
||||
_ ->
|
||||
Socket1 = SockMod:reset_stream(Socket),
|
||||
SocketData#socket_state{xml_stream = XMLStream1, socket = Socket1}
|
||||
end.
|
||||
|
||||
-spec send_element(socket_state(), fxml:xmlel()) -> ok | {error, inet:posix()}.
|
||||
send_element(SocketData, El) when ?is_http_socket(SocketData) ->
|
||||
send_xml(SocketData, {xmlstreamelement, El});
|
||||
send_element(SocketData, El) ->
|
||||
send(SocketData, fxml:element_to_binary(El)).
|
||||
|
||||
-spec send_header(socket_state(), fxml:xmlel()) -> ok | {error, inet:posix()}.
|
||||
send_header(SocketData, El) when ?is_http_socket(SocketData) ->
|
||||
send_xml(SocketData, {xmlstreamstart, El#xmlel.name, El#xmlel.attrs});
|
||||
send_header(SocketData, El) ->
|
||||
send(SocketData, fxml:element_to_header(El)).
|
||||
|
||||
-spec send_trailer(socket_state()) -> ok | {error, inet:posix()}.
|
||||
send_trailer(SocketData) when ?is_http_socket(SocketData) ->
|
||||
send_xml(SocketData, {xmlstreamend, <<"stream:stream">>});
|
||||
send_trailer(SocketData) ->
|
||||
send(SocketData, <<"</stream:stream>">>).
|
||||
|
||||
-spec send(socket_state(), iodata()) -> ok | {error, closed | inet:posix()}.
|
||||
send(#socket_state{sockmod = SockMod, socket = Socket} = SocketData, Data) ->
|
||||
?DEBUG("(~s) Send XML on stream = ~p", [pp(SocketData), Data]),
|
||||
try SockMod:send(Socket, Data) of
|
||||
{error, einval} -> {error, closed};
|
||||
Result -> Result
|
||||
catch _:badarg ->
|
||||
%% Some modules throw badarg exceptions on closed sockets
|
||||
%% TODO: their code should be improved
|
||||
{error, closed}
|
||||
end.
|
||||
|
||||
-spec send_xml(socket_state(),
|
||||
{xmlstreamelement, fxml:xmlel()} |
|
||||
{xmlstreamstart, binary(), [{binary(), binary()}]} |
|
||||
{xmlstreamend, binary()} |
|
||||
{xmlstreamraw, iodata()}) -> term().
|
||||
send_xml(SocketData, El) ->
|
||||
(SocketData#socket_state.sockmod):send_xml(SocketData#socket_state.socket, El).
|
||||
|
||||
recv(#socket_state{xml_stream = undefined} = SocketData, Data) ->
|
||||
XMLStream = fxml_stream:new(self(), SocketData#socket_state.max_stanza_size),
|
||||
recv(SocketData#socket_state{xml_stream = XMLStream}, Data);
|
||||
recv(#socket_state{sockmod = SockMod, socket = Socket} = SocketData, Data) ->
|
||||
case SockMod of
|
||||
fast_tls ->
|
||||
case fast_tls:recv_data(Socket, Data) of
|
||||
{ok, TLSData} ->
|
||||
parse(SocketData, TLSData);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
ezlib ->
|
||||
case ezlib:recv_data(Socket, Data) of
|
||||
{ok, ZlibData} ->
|
||||
parse(SocketData, ZlibData);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
_ ->
|
||||
parse(SocketData, Data)
|
||||
end.
|
||||
|
||||
change_shaper(#socket_state{receiver = undefined} = SocketData, Shaper) ->
|
||||
ShaperState = ejabberd_shaper:new(Shaper),
|
||||
SocketData#socket_state{shaper = ShaperState};
|
||||
change_shaper(#socket_state{sockmod = SockMod,
|
||||
socket = Socket} = SocketData, Shaper) ->
|
||||
SockMod:change_shaper(Socket, Shaper),
|
||||
SocketData.
|
||||
|
||||
monitor(#socket_state{receiver = undefined}) ->
|
||||
make_ref();
|
||||
monitor(#socket_state{sockmod = SockMod, socket = Socket}) ->
|
||||
SockMod:monitor(Socket).
|
||||
|
||||
controlling_process(#socket_state{sockmod = SockMod,
|
||||
socket = Socket}, Pid) ->
|
||||
SockMod:controlling_process(Socket, Pid).
|
||||
|
||||
become_controller(#socket_state{receiver = Receiver,
|
||||
sockmod = SockMod,
|
||||
socket = Socket}, Pid) ->
|
||||
if is_pid(Receiver) ->
|
||||
SockMod:become_controller(Receiver, Pid);
|
||||
true ->
|
||||
activate_after(Socket, Pid, 0)
|
||||
end.
|
||||
|
||||
get_sockmod(SocketData) ->
|
||||
SocketData#socket_state.sockmod.
|
||||
|
||||
get_transport(#socket_state{sockmod = SockMod,
|
||||
socket = Socket}) ->
|
||||
case SockMod of
|
||||
gen_tcp -> tcp;
|
||||
fast_tls -> tls;
|
||||
ezlib ->
|
||||
case ezlib:get_sockmod(Socket) of
|
||||
gen_tcp -> tcp_zlib;
|
||||
fast_tls -> tls_zlib
|
||||
end;
|
||||
ejabberd_bosh -> http_bind;
|
||||
ejabberd_http_ws -> websocket
|
||||
end.
|
||||
|
||||
get_peer_certificate(SocketData, Type) ->
|
||||
fast_tls:get_peer_certificate(SocketData#socket_state.socket, Type).
|
||||
|
||||
get_verify_result(SocketData) ->
|
||||
fast_tls:get_verify_result(SocketData#socket_state.socket).
|
||||
|
||||
close(#socket_state{sockmod = SockMod, socket = Socket}) ->
|
||||
SockMod:close(Socket).
|
||||
|
||||
sockname(#socket_state{sockmod = SockMod,
|
||||
socket = Socket}) ->
|
||||
case SockMod of
|
||||
gen_tcp -> inet:sockname(Socket);
|
||||
_ -> SockMod:sockname(Socket)
|
||||
end.
|
||||
|
||||
peername(#socket_state{sockmod = SockMod,
|
||||
socket = Socket}) ->
|
||||
case SockMod of
|
||||
gen_tcp -> inet:peername(Socket);
|
||||
_ -> SockMod:peername(Socket)
|
||||
end.
|
||||
|
||||
activate(#socket_state{sockmod = SockMod, socket = Socket}) ->
|
||||
case SockMod of
|
||||
gen_tcp -> inet:setopts(Socket, [{active, once}]);
|
||||
_ -> SockMod:setopts(Socket, [{active, once}])
|
||||
end.
|
||||
|
||||
activate_after(Socket, Pid, Pause) ->
|
||||
if Pause > 0 ->
|
||||
erlang:send_after(Pause, Pid, {tcp, Socket, <<>>});
|
||||
true ->
|
||||
Pid ! {tcp, Socket, <<>>}
|
||||
end,
|
||||
ok.
|
||||
|
||||
pp(#socket_state{receiver = Receiver} = State) ->
|
||||
Transport = get_transport(State),
|
||||
Receiver1 = case Receiver of
|
||||
undefined -> self();
|
||||
_ -> Receiver
|
||||
end,
|
||||
io_lib:format("~s|~w", [Transport, Receiver1]).
|
||||
|
||||
parse(SocketData, Data) when Data == <<>>; Data == [] ->
|
||||
case activate(SocketData) of
|
||||
ok ->
|
||||
{ok, SocketData};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
parse(SocketData, [El | Els]) when is_record(El, xmlel) ->
|
||||
self() ! {'$gen_event', {xmlstreamelement, El}},
|
||||
parse(SocketData, Els);
|
||||
parse(SocketData, [El | Els]) when
|
||||
element(1, El) == xmlstreamstart;
|
||||
element(1, El) == xmlstreamelement;
|
||||
element(1, El) == xmlstreamend;
|
||||
element(1, El) == xmlstreamerror ->
|
||||
self() ! {'$gen_event', El},
|
||||
parse(SocketData, Els);
|
||||
parse(#socket_state{xml_stream = XMLStream,
|
||||
socket = Socket,
|
||||
shaper = ShaperState} = SocketData, Data)
|
||||
when is_binary(Data) ->
|
||||
?DEBUG("(~s) Received XML on stream = ~p", [pp(SocketData), Data]),
|
||||
XMLStream1 = fxml_stream:parse(XMLStream, Data),
|
||||
{ShaperState1, Pause} = ejabberd_shaper:update(ShaperState, byte_size(Data)),
|
||||
Ret = if Pause > 0 ->
|
||||
activate_after(Socket, self(), Pause);
|
||||
true ->
|
||||
activate(SocketData)
|
||||
end,
|
||||
case Ret of
|
||||
ok ->
|
||||
{ok, SocketData#socket_state{xml_stream = XMLStream1,
|
||||
shaper = ShaperState1}};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
close_stream(undefined) ->
|
||||
ok;
|
||||
close_stream(XMLStream) ->
|
||||
fxml_stream:close(XMLStream).
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,271 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% Created : 13 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(xmpp_stream_pkix).
|
||||
|
||||
%% API
|
||||
-export([authenticate/1, authenticate/2, get_cert_domains/1, format_error/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include_lib("public_key/include/public_key.hrl").
|
||||
-include("XmppAddr.hrl").
|
||||
|
||||
-type cert() :: #'OTPCertificate'{}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec authenticate(xmpp_stream_in:state() | xmpp_stream_out:state())
|
||||
-> {ok, binary()} | {error, atom(), binary()}.
|
||||
authenticate(State) ->
|
||||
authenticate(State, <<"">>).
|
||||
|
||||
-spec authenticate(xmpp_stream_in:state() | xmpp_stream_out:state(), binary())
|
||||
-> {ok, binary()} | {error, atom(), binary()}.
|
||||
authenticate(#{xmlns := ?NS_SERVER,
|
||||
socket := Socket} = State, Authzid) ->
|
||||
Peer = maps:get(remote_server, State, Authzid),
|
||||
case verify_cert(Socket) of
|
||||
{ok, Cert} ->
|
||||
case ejabberd_idna:domain_utf8_to_ascii(Peer) of
|
||||
false ->
|
||||
{error, idna_failed, Peer};
|
||||
AsciiPeer ->
|
||||
case lists:any(
|
||||
fun(D) -> match_domain(AsciiPeer, D) end,
|
||||
get_cert_domains(Cert)) of
|
||||
true ->
|
||||
{ok, Peer};
|
||||
false ->
|
||||
{error, hostname_mismatch, Peer}
|
||||
end
|
||||
end;
|
||||
{error, Reason} ->
|
||||
{error, Reason, Peer}
|
||||
end;
|
||||
authenticate(#{xmlns := ?NS_CLIENT,
|
||||
socket := Socket, lserver := LServer}, Authzid) ->
|
||||
JID = try jid:decode(Authzid)
|
||||
catch _:{bad_jid, <<>>} -> jid:make(LServer);
|
||||
_:{bad_jid, _} -> {error, invalid_authzid, Authzid}
|
||||
end,
|
||||
case JID of
|
||||
#jid{user = User} ->
|
||||
case verify_cert(Socket) of
|
||||
{ok, Cert} ->
|
||||
JIDs = get_xmpp_addrs(Cert),
|
||||
get_username(JID, JIDs, LServer);
|
||||
{error, Reason} ->
|
||||
{error, Reason, User}
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
format_error(idna_failed) ->
|
||||
{'bad-protocol', <<"Remote domain is not an IDN hostname">>};
|
||||
format_error(hostname_mismatch) ->
|
||||
{'not-authorized', <<"Certificate host name mismatch">>};
|
||||
format_error(jid_mismatch) ->
|
||||
{'not-authorized', <<"Certificate JID mismatch">>};
|
||||
format_error(get_cert_failed) ->
|
||||
{'bad-protocol', <<"Failed to get peer certificate">>};
|
||||
format_error(invalid_authzid) ->
|
||||
{'invalid-authzid', <<"Malformed JID">>};
|
||||
format_error(Other) ->
|
||||
{'not-authorized', erlang:atom_to_binary(Other, utf8)}.
|
||||
|
||||
-spec get_cert_domains(cert()) -> [binary()].
|
||||
get_cert_domains(Cert) ->
|
||||
TBSCert = Cert#'OTPCertificate'.tbsCertificate,
|
||||
{rdnSequence, Subject} = TBSCert#'OTPTBSCertificate'.subject,
|
||||
Extensions = TBSCert#'OTPTBSCertificate'.extensions,
|
||||
get_domain_from_subject(lists:flatten(Subject)) ++
|
||||
get_domains_from_san(Extensions).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-spec verify_cert(xmpp_socket:socket()) -> {ok, cert()} | {error, atom()}.
|
||||
verify_cert(Socket) ->
|
||||
case xmpp_socket:get_peer_certificate(Socket, otp) of
|
||||
{ok, Cert} ->
|
||||
case xmpp_socket:get_verify_result(Socket) of
|
||||
0 ->
|
||||
{ok, Cert};
|
||||
VerifyRes ->
|
||||
%% TODO: return atomic errors
|
||||
%% This should be improved in fast_tls
|
||||
Reason = fast_tls:get_cert_verify_string(VerifyRes, Cert),
|
||||
{error, erlang:binary_to_atom(Reason, utf8)}
|
||||
end;
|
||||
{error, _Reason} ->
|
||||
{error, get_cert_failed};
|
||||
error ->
|
||||
{error, get_cert_failed}
|
||||
end.
|
||||
|
||||
-spec get_domain_from_subject([#'AttributeTypeAndValue'{}]) -> [binary()].
|
||||
get_domain_from_subject(AttrVals) ->
|
||||
case lists:keyfind(?'id-at-commonName',
|
||||
#'AttributeTypeAndValue'.type,
|
||||
AttrVals) of
|
||||
#'AttributeTypeAndValue'{value = {_, S}} ->
|
||||
try jid:decode(iolist_to_binary(S)) of
|
||||
#jid{luser = <<"">>, lresource = <<"">>, lserver = Domain} ->
|
||||
[Domain];
|
||||
_ ->
|
||||
[]
|
||||
catch _:{bad_jid, _} ->
|
||||
[]
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec get_domains_from_san([#'Extension'{}] | asn1_NOVALUE) -> [binary()].
|
||||
get_domains_from_san(Extensions) when is_list(Extensions) ->
|
||||
case lists:keyfind(?'id-ce-subjectAltName',
|
||||
#'Extension'.extnID,
|
||||
Extensions) of
|
||||
#'Extension'{extnValue = Vals} ->
|
||||
lists:flatmap(
|
||||
fun({dNSName, S}) ->
|
||||
[iolist_to_binary(S)];
|
||||
({otherName, AnotherName}) ->
|
||||
case decode_xmpp_addr(AnotherName) of
|
||||
{ok, #jid{luser = <<"">>,
|
||||
lresource = <<"">>,
|
||||
lserver = Domain}} ->
|
||||
case ejabberd_idna:domain_utf8_to_ascii(Domain) of
|
||||
false ->
|
||||
[];
|
||||
ASCIIDomain ->
|
||||
[ASCIIDomain]
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
(_) ->
|
||||
[]
|
||||
end, Vals);
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
get_domains_from_san(_) ->
|
||||
[].
|
||||
|
||||
-spec decode_xmpp_addr(#'AnotherName'{}) -> {ok, jid()} | error.
|
||||
decode_xmpp_addr(#'AnotherName'{'type-id' = ?'id-on-xmppAddr',
|
||||
value = XmppAddr}) ->
|
||||
try 'XmppAddr':decode('XmppAddr', XmppAddr) of
|
||||
{ok, JIDStr} ->
|
||||
try {ok, jid:decode(iolist_to_binary(JIDStr))}
|
||||
catch _:{bad_jid, _} -> error
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
catch _:_ ->
|
||||
error
|
||||
end;
|
||||
decode_xmpp_addr(_) ->
|
||||
error.
|
||||
|
||||
-spec get_xmpp_addrs(cert()) -> [jid()].
|
||||
get_xmpp_addrs(Cert) ->
|
||||
TBSCert = Cert#'OTPCertificate'.tbsCertificate,
|
||||
case TBSCert#'OTPTBSCertificate'.extensions of
|
||||
Extensions when is_list(Extensions) ->
|
||||
case lists:keyfind(?'id-ce-subjectAltName',
|
||||
#'Extension'.extnID,
|
||||
Extensions) of
|
||||
#'Extension'{extnValue = Vals} ->
|
||||
lists:flatmap(
|
||||
fun({otherName, AnotherName}) ->
|
||||
case decode_xmpp_addr(AnotherName) of
|
||||
{ok, JID} -> [JID];
|
||||
_ -> []
|
||||
end;
|
||||
(_) ->
|
||||
[]
|
||||
end, Vals);
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
match_domain(Domain, Domain) -> true;
|
||||
match_domain(Domain, Pattern) ->
|
||||
DLabels = str:tokens(Domain, <<".">>),
|
||||
PLabels = str:tokens(Pattern, <<".">>),
|
||||
match_labels(DLabels, PLabels).
|
||||
|
||||
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,
|
||||
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.
|
||||
|
||||
-spec get_username(jid(), [jid()], binary()) ->
|
||||
{ok, binary()} | {error, jid_mismatch, binary()}.
|
||||
get_username(#jid{user = User, lserver = LS}, _, LServer) when LS /= LServer ->
|
||||
%% The user provided JID from different domain
|
||||
{error, jid_mismatch, User};
|
||||
get_username(#jid{user = <<>>}, [#jid{user = U, lserver = LS}], LServer)
|
||||
when U /= <<>> andalso LS == LServer ->
|
||||
%% The user didn't provide JID or username, and there is only
|
||||
%% one 'non-global' JID matching current domain
|
||||
{ok, U};
|
||||
get_username(#jid{user = User, luser = LUser}, JIDs, LServer) when User /= <<>> ->
|
||||
%% The user provided username
|
||||
lists:foldl(
|
||||
fun(_, {ok, _} = OK) ->
|
||||
OK;
|
||||
(#jid{user = <<>>, lserver = LS}, _) when LS == LServer ->
|
||||
%% Found "global" JID in the certficate
|
||||
%% (i.e. in the form of 'domain.com')
|
||||
%% within current domain, so we force matching
|
||||
{ok, User};
|
||||
(#jid{luser = LU, lserver = LS}, _) when LU == LUser, LS == LServer ->
|
||||
%% Found exact JID matching
|
||||
{ok, User};
|
||||
(_, Err) ->
|
||||
Err
|
||||
end, {error, jid_mismatch, User}, JIDs);
|
||||
get_username(#jid{user = User}, _, _) ->
|
||||
%% Nothing from above is true
|
||||
{error, jid_mismatch, User}.
|
@ -32,32 +32,32 @@ defmodule EjabberdCyrsaslTest do
|
||||
start_module(:jid)
|
||||
:ejabberd_hooks.start_link
|
||||
:ok = :ejabberd_config.start(["domain1"], [])
|
||||
{:ok, _} = :cyrsasl.start_link
|
||||
cyrstate = :cyrsasl.server_new("domain1", "domain1", "domain1", :ok, &get_password/1,
|
||||
{:ok, _} = :xmpp_sasl.start_link
|
||||
cyrstate = :xmpp_sasl.server_new("domain1", "domain1", "domain1", :ok, &get_password/1,
|
||||
&check_password/3, &check_password_digest/5)
|
||||
setup_anonymous_mocks()
|
||||
{:ok, cyrstate: cyrstate}
|
||||
end
|
||||
|
||||
test "Plain text (correct user and pass)", context do
|
||||
step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"user1",0,"pass">>)
|
||||
step1 = :xmpp_sasl.server_start(context[:cyrstate], "PLAIN", <<0,"user1",0,"pass">>)
|
||||
assert {:ok, _} = step1
|
||||
{:ok, kv} = step1
|
||||
assert kv[:authzid] == "user1", "got correct user"
|
||||
end
|
||||
|
||||
test "Plain text (correct user wrong pass)", context do
|
||||
step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"user1",0,"badpass">>)
|
||||
step1 = :xmpp_sasl.server_start(context[:cyrstate], "PLAIN", <<0,"user1",0,"badpass">>)
|
||||
assert step1 == {:error, :not_authorized, "user1"}
|
||||
end
|
||||
|
||||
test "Plain text (wrong user wrong pass)", context do
|
||||
step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"nouser1",0,"badpass">>)
|
||||
step1 = :xmpp_sasl.server_start(context[:cyrstate], "PLAIN", <<0,"nouser1",0,"badpass">>)
|
||||
assert step1 == {:error, :not_authorized, "nouser1"}
|
||||
end
|
||||
|
||||
test "Anonymous", context do
|
||||
step1 = :cyrsasl.server_start(context[:cyrstate], "ANONYMOUS", "domain1")
|
||||
step1 = :xmpp_sasl.server_start(context[:cyrstate], "ANONYMOUS", "domain1")
|
||||
assert {:ok, _} = step1
|
||||
end
|
||||
|
||||
@ -78,7 +78,7 @@ defmodule EjabberdCyrsaslTest do
|
||||
end
|
||||
|
||||
defp process_digest_md5(cyrstate, user, domain, pass) do
|
||||
assert {:continue, init_str, state1} = :cyrsasl.server_start(cyrstate, "DIGEST-MD5", "")
|
||||
assert {:continue, init_str, state1} = :xmpp_sasl.server_start(cyrstate, "DIGEST-MD5", "")
|
||||
assert [_, nonce] = Regex.run(~r/nonce="(.*?)"/, init_str)
|
||||
digest_uri = "xmpp/#{domain}"
|
||||
cnonce = "abcd"
|
||||
@ -87,8 +87,8 @@ defmodule EjabberdCyrsaslTest do
|
||||
response = "username=\"#{user}\",realm=\"#{domain}\",nonce=\"#{nonce}\",cnonce=\"#{cnonce}\"," <>
|
||||
"nc=\"#{nc}\",qop=auth,digest-uri=\"#{digest_uri}\",response=\"#{response_hash}\"," <>
|
||||
"charset=utf-8,algorithm=md5-sess"
|
||||
case :cyrsasl.server_step(state1, response) do
|
||||
{:continue, _calc_str, state2} -> :cyrsasl.server_step(state2, "")
|
||||
case :xmpp_sasl.server_step(state1, response) do
|
||||
{:continue, _calc_str, state2} -> :xmpp_sasl.server_step(state2, "")
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
@ -599,7 +599,7 @@ sasl_new(<<"ANONYMOUS">>, _) ->
|
||||
sasl_new(<<"DIGEST-MD5">>, {User, Server, Password}) ->
|
||||
{<<"">>,
|
||||
fun (ServerIn) ->
|
||||
case cyrsasl_digest:parse(ServerIn) of
|
||||
case xmpp_sasl_digest:parse(ServerIn) of
|
||||
bad -> {error, <<"Invalid SASL challenge">>};
|
||||
KeyVals ->
|
||||
Nonce = fxml:get_attr_s(<<"nonce">>, KeyVals),
|
||||
@ -625,7 +625,7 @@ sasl_new(<<"DIGEST-MD5">>, {User, Server, Password}) ->
|
||||
MyResponse/binary, "\"">>,
|
||||
{Resp,
|
||||
fun (ServerIn2) ->
|
||||
case cyrsasl_digest:parse(ServerIn2) of
|
||||
case xmpp_sasl_digest:parse(ServerIn2) of
|
||||
bad -> {error, <<"Invalid SASL challenge">>};
|
||||
_KeyVals2 ->
|
||||
{<<"">>,
|
||||
|
Loading…
Reference in New Issue
Block a user