mirror of
https://github.com/processone/ejabberd.git
synced 2024-10-11 15:08:56 +02:00
fd8e07af47
The header consisted of too many unrelated stuff and macros misuse. Some stuff is moved into scram.hrl and type_compat.hrl. All macros have been replaced with the corresponding function calls. TODO: probably type_compat.hrl is not even needed anymore since we support only Erlang >= OTP 17.5
230 lines
7.4 KiB
Erlang
230 lines
7.4 KiB
Erlang
%%%----------------------------------------------------------------------
|
|
%%% 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.
|