mirror of
https://github.com/processone/ejabberd.git
synced 2024-06-08 21:43:07 +02:00
Support SASL GSSAPI authentication (thanks to Mikael Magnusson)(EJAB-831)
This commit is contained in:
parent
17fc992ba9
commit
aa791ad0c4
|
@ -30,10 +30,12 @@
|
||||||
-export([start/0,
|
-export([start/0,
|
||||||
register_mechanism/3,
|
register_mechanism/3,
|
||||||
listmech/1,
|
listmech/1,
|
||||||
server_new/7,
|
server_new/8,
|
||||||
server_start/3,
|
server_start/3,
|
||||||
server_step/2]).
|
server_step/2]).
|
||||||
|
|
||||||
|
-include("cyrsasl.hrl").
|
||||||
|
|
||||||
%% @type saslmechanism() = {sasl_mechanism, Mechanism, Module, Require_Plain}
|
%% @type saslmechanism() = {sasl_mechanism, Mechanism, Module, Require_Plain}
|
||||||
%% Mechanism = string()
|
%% Mechanism = string()
|
||||||
%% Module = atom()
|
%% Module = atom()
|
||||||
|
@ -53,16 +55,15 @@
|
||||||
%% Mech_State = term().
|
%% Mech_State = term().
|
||||||
%% State of this process.
|
%% State of this process.
|
||||||
|
|
||||||
-record(sasl_state, {service, myname, realm,
|
-record(sasl_state, {service, myname,
|
||||||
get_password, check_password, check_password_digest,
|
mech_mod, mech_state, params}).
|
||||||
mech_mod, mech_state}).
|
|
||||||
|
|
||||||
-export([behaviour_info/1]).
|
-export([behaviour_info/1]).
|
||||||
|
|
||||||
%% @hidden
|
%% @hidden
|
||||||
|
|
||||||
behaviour_info(callbacks) ->
|
behaviour_info(callbacks) ->
|
||||||
[{mech_new, 4}, {mech_step, 2}];
|
[{mech_new, 1}, {mech_step, 2}];
|
||||||
behaviour_info(_Other) ->
|
behaviour_info(_Other) ->
|
||||||
undefined.
|
undefined.
|
||||||
|
|
||||||
|
@ -72,6 +73,7 @@ start() ->
|
||||||
ets:new(sasl_mechanism, [named_table,
|
ets:new(sasl_mechanism, [named_table,
|
||||||
public,
|
public,
|
||||||
{keypos, #sasl_mechanism.mechanism}]),
|
{keypos, #sasl_mechanism.mechanism}]),
|
||||||
|
cyrsasl_gssapi:start([]),
|
||||||
cyrsasl_plain:start([]),
|
cyrsasl_plain:start([]),
|
||||||
cyrsasl_digest:start([]),
|
cyrsasl_digest:start([]),
|
||||||
cyrsasl_anonymous:start([]),
|
cyrsasl_anonymous:start([]),
|
||||||
|
@ -158,13 +160,20 @@ listmech(Host) ->
|
||||||
%% CheckPassword = function()
|
%% CheckPassword = function()
|
||||||
|
|
||||||
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
|
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
|
||||||
GetPassword, CheckPassword, CheckPasswordDigest) ->
|
GetPassword, CheckPassword, CheckPasswordDigest, Socket) ->
|
||||||
#sasl_state{service = Service,
|
Params = #sasl_params{
|
||||||
myname = ServerFQDN,
|
host = ServerFQDN,
|
||||||
realm = UserRealm,
|
realm = UserRealm,
|
||||||
get_password = GetPassword,
|
get_password = GetPassword,
|
||||||
check_password = CheckPassword,
|
check_password = CheckPassword,
|
||||||
check_password_digest= CheckPasswordDigest}.
|
check_password_digest= CheckPasswordDigest,
|
||||||
|
socket = Socket
|
||||||
|
},
|
||||||
|
|
||||||
|
#sasl_state{service = Service,
|
||||||
|
myname = ServerFQDN,
|
||||||
|
params = Params}.
|
||||||
|
|
||||||
|
|
||||||
%% @spec (State, Mech, ClientIn) -> Ok | Continue | Error
|
%% @spec (State, Mech, ClientIn) -> Ok | Continue | Error
|
||||||
%% State = saslstate()
|
%% State = saslstate()
|
||||||
|
@ -187,11 +196,8 @@ server_start(State, Mech, ClientIn) ->
|
||||||
true ->
|
true ->
|
||||||
case ets:lookup(sasl_mechanism, Mech) of
|
case ets:lookup(sasl_mechanism, Mech) of
|
||||||
[#sasl_mechanism{module = Module}] ->
|
[#sasl_mechanism{module = Module}] ->
|
||||||
{ok, MechState} = Module:mech_new(
|
{ok, MechState} =
|
||||||
State#sasl_state.myname,
|
Module:mech_new(State#sasl_state.params),
|
||||||
State#sasl_state.get_password,
|
|
||||||
State#sasl_state.check_password,
|
|
||||||
State#sasl_state.check_password_digest),
|
|
||||||
server_step(State#sasl_state{mech_mod = Module,
|
server_step(State#sasl_state{mech_mod = Module,
|
||||||
mech_state = MechState},
|
mech_state = MechState},
|
||||||
ClientIn);
|
ClientIn);
|
||||||
|
|
15
src/cyrsasl.hrl
Normal file
15
src/cyrsasl.hrl
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
%% @type saslparams() = {sasl_params, Host, Realm, GetPassword, CheckPassword, CheckPasswordDigest}
|
||||||
|
%% Host = string()
|
||||||
|
%% Realm = string()
|
||||||
|
%% GetPassword = function()
|
||||||
|
%% CheckPassword = function()
|
||||||
|
%% CheckPasswordDigest = any().
|
||||||
|
%% Parameters for SASL.
|
||||||
|
|
||||||
|
-record(sasl_params, {
|
||||||
|
host,
|
||||||
|
realm,
|
||||||
|
get_password,
|
||||||
|
check_password,
|
||||||
|
check_password_digest,
|
||||||
|
socket}).
|
|
@ -27,7 +27,9 @@
|
||||||
|
|
||||||
-module(cyrsasl_anonymous).
|
-module(cyrsasl_anonymous).
|
||||||
|
|
||||||
-export([start/1, stop/0, mech_new/4, mech_step/2]).
|
-export([start/1, stop/0, mech_new/1, mech_step/2]).
|
||||||
|
|
||||||
|
-include("cyrsasl.hrl").
|
||||||
|
|
||||||
-behaviour(cyrsasl).
|
-behaviour(cyrsasl).
|
||||||
|
|
||||||
|
@ -48,13 +50,7 @@ start(_Opts) ->
|
||||||
stop() ->
|
stop() ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%% @spec (Host, GetPassword, CheckPassword, CheckPasswordDigest) -> {ok, State}
|
mech_new(#sasl_params{host=Host}) ->
|
||||||
%% Host = string()
|
|
||||||
%% GetPassword = function()
|
|
||||||
%% CheckPassword = function()
|
|
||||||
%% State = mechstate()
|
|
||||||
|
|
||||||
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
|
||||||
{ok, #state{server = Host}}.
|
{ok, #state{server = Host}}.
|
||||||
|
|
||||||
%% @spec (State, ClientIn) -> Ok | Error
|
%% @spec (State, ClientIn) -> Ok | Error
|
||||||
|
|
|
@ -29,10 +29,11 @@
|
||||||
|
|
||||||
-export([start/1,
|
-export([start/1,
|
||||||
stop/0,
|
stop/0,
|
||||||
mech_new/4,
|
mech_new/1,
|
||||||
mech_step/2]).
|
mech_step/2]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
|
-include("cyrsasl.hrl").
|
||||||
|
|
||||||
-behaviour(cyrsasl).
|
-behaviour(cyrsasl).
|
||||||
|
|
||||||
|
@ -59,13 +60,8 @@ start(_Opts) ->
|
||||||
stop() ->
|
stop() ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%% @spec (Host, GetPassword, CheckPassword, CheckPasswordDigest) -> {ok, State}
|
mech_new(#sasl_params{host=Host, get_password=GetPassword,
|
||||||
%% Host = string()
|
check_password_digest=CheckPasswordDigest}) ->
|
||||||
%% GetPassword = function()
|
|
||||||
%% CheckPassword = function()
|
|
||||||
%% State = mechstate()
|
|
||||||
|
|
||||||
mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
|
|
||||||
{ok, #state{step = 1,
|
{ok, #state{step = 1,
|
||||||
nonce = randoms:get_string(),
|
nonce = randoms:get_string(),
|
||||||
host = Host,
|
host = Host,
|
||||||
|
|
167
src/cyrsasl_gssapi.erl
Normal file
167
src/cyrsasl_gssapi.erl
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% File : cyrsasl_gssapi.erl
|
||||||
|
%%% Author : Mikael Magnusson <mikma@users.sourceforge.net>
|
||||||
|
%%% Purpose : GSSAPI SASL mechanism
|
||||||
|
%%% Created : 1 June 2007 by Mikael Magnusson <mikma@users.sourceforge.net>
|
||||||
|
%%% Id : $Id: $
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2007-2009 Mikael Magnusson <mikma@users.sourceforge.net>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person
|
||||||
|
%%% obtaining a copy of this software and associated documentation
|
||||||
|
%%% files (the "Software"), to deal in the Software without
|
||||||
|
%%% restriction, including without limitation the rights to use, copy,
|
||||||
|
%%% modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
%%% of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be
|
||||||
|
%%% included in all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
%%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
%%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
%%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
%%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
%%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
%%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
%%% SOFTWARE.
|
||||||
|
%%%
|
||||||
|
|
||||||
|
%%%
|
||||||
|
%%% configuration options:
|
||||||
|
%%% {sasl_realm, "<Kerberos realm>"}.
|
||||||
|
%%%
|
||||||
|
%%% environment variables:
|
||||||
|
%%% KRB5_KTNAME
|
||||||
|
%%%
|
||||||
|
|
||||||
|
-module(cyrsasl_gssapi).
|
||||||
|
-author('mikma@users.sourceforge.net').
|
||||||
|
-vsn('$Revision: $ ').
|
||||||
|
|
||||||
|
-export([start/1,
|
||||||
|
stop/0,
|
||||||
|
mech_new/1,
|
||||||
|
mech_step/2]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("cyrsasl.hrl").
|
||||||
|
|
||||||
|
-behaviour(cyrsasl).
|
||||||
|
|
||||||
|
-define(SERVER, ?MODULE).
|
||||||
|
-define(MSG, ?DEBUG).
|
||||||
|
-define(SERVICE, "xmpp").
|
||||||
|
|
||||||
|
-record(state, {sasl,
|
||||||
|
needsmore=true,
|
||||||
|
step=0,
|
||||||
|
host,
|
||||||
|
realm,
|
||||||
|
authid,
|
||||||
|
authzid,
|
||||||
|
authrealm,
|
||||||
|
error}).
|
||||||
|
|
||||||
|
start(_Opts) ->
|
||||||
|
ChildSpec =
|
||||||
|
{?SERVER,
|
||||||
|
{esasl, start_link, [{local, ?SERVER}]},
|
||||||
|
transient,
|
||||||
|
1000,
|
||||||
|
worker,
|
||||||
|
[esasl]},
|
||||||
|
|
||||||
|
case supervisor:start_child(ejabberd_sup, ChildSpec) of
|
||||||
|
{ok, _Pid} ->
|
||||||
|
cyrsasl:register_mechanism("GSSAPI", ?MODULE, false);
|
||||||
|
{error, Error} = E ->
|
||||||
|
?ERROR_MSG("esasl failed: ~p", [Error]),
|
||||||
|
E
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
stop() ->
|
||||||
|
catch esasl:stop(?SERVER),
|
||||||
|
supervisor:terminate_child(ejabberd_sup, ?SERVER),
|
||||||
|
supervisor:delete_child(ejabberd_sup, ?SERVER).
|
||||||
|
|
||||||
|
mech_new(#sasl_params{host=Host, realm=Realm, socket=Socket}) ->
|
||||||
|
case ejabberd_socket:gethostname(Socket) of
|
||||||
|
{ok, FQDN} ->
|
||||||
|
?MSG("mech_new ~p ~p ~p~n", [Host, Realm, FQDN]),
|
||||||
|
case esasl:server_start(?SERVER, "GSSAPI", ?SERVICE, FQDN) of
|
||||||
|
{ok, Sasl} ->
|
||||||
|
{ok, #state{sasl=Sasl,host=Host,realm=Realm}};
|
||||||
|
{error, {gsasl_error, Error}} ->
|
||||||
|
{ok, Str} = esasl:str_error(?SERVER, Error),
|
||||||
|
?MSG("esasl error: ~p", [Str]),
|
||||||
|
{ok, #state{needsmore=error,error="internal-server-error"}};
|
||||||
|
{error, Error} ->
|
||||||
|
?MSG("esasl error: ~p", [Error]),
|
||||||
|
{ok, #state{needsmore=error,error="internal-server-error"}}
|
||||||
|
end;
|
||||||
|
{error, Error} ->
|
||||||
|
?MSG("gethostname error: ~p", [Error]),
|
||||||
|
{ok, #state{needsmore=error,error="internal-server-error"}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
mech_step(State, ClientIn) when is_list(ClientIn) ->
|
||||||
|
catch do_step(State, ClientIn).
|
||||||
|
|
||||||
|
do_step(#state{needsmore=error,error=Error}=State, _) ->
|
||||||
|
{error, Error};
|
||||||
|
do_step(#state{needsmore=false}=State, _) ->
|
||||||
|
check_user(State);
|
||||||
|
do_step(#state{needsmore=true,sasl=Sasl,step=Step}=State, ClientIn) ->
|
||||||
|
?MSG("mech_step~n", []),
|
||||||
|
case esasl:step(Sasl, list_to_binary(ClientIn)) of
|
||||||
|
{ok, RspAuth} ->
|
||||||
|
?MSG("ok~n", []),
|
||||||
|
{ok, Display_name} = esasl:property_get(Sasl, gssapi_display_name),
|
||||||
|
{ok, Authzid} = esasl:property_get(Sasl, authzid),
|
||||||
|
{Authid, [$@ | Auth_realm]} =
|
||||||
|
lists:splitwith(fun(E)->E =/= $@ end, Display_name),
|
||||||
|
State1 = State#state{authid=Authid,
|
||||||
|
authzid=Authzid,
|
||||||
|
authrealm=Auth_realm},
|
||||||
|
handle_step_ok(State1, binary_to_list(RspAuth));
|
||||||
|
{needsmore, RspAuth} ->
|
||||||
|
?MSG("needsmore~n", []),
|
||||||
|
if (Step > 0) and (ClientIn =:= []) and (RspAuth =:= <<>>) ->
|
||||||
|
{error, "not-authorized"};
|
||||||
|
true ->
|
||||||
|
{continue, binary_to_list(RspAuth),
|
||||||
|
State#state{step=Step+1}}
|
||||||
|
end;
|
||||||
|
{error, _} ->
|
||||||
|
{error, "not-authorized"}
|
||||||
|
end.
|
||||||
|
|
||||||
|
handle_step_ok(State, []) ->
|
||||||
|
check_user(State);
|
||||||
|
handle_step_ok(#state{step=Step}=State, RspAuth) ->
|
||||||
|
?MSG("continue~n", []),
|
||||||
|
{continue, RspAuth, State#state{needsmore=false,step=Step+1}}.
|
||||||
|
|
||||||
|
check_user(#state{authid=Authid,authzid=Authzid,
|
||||||
|
authrealm=Auth_realm,host=Host,realm=Realm}) ->
|
||||||
|
if Realm =/= Auth_realm ->
|
||||||
|
?MSG("bad realm ~p (expected ~p)~n",[Auth_realm, Realm]),
|
||||||
|
throw({error, "not-authorized"});
|
||||||
|
true ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
|
||||||
|
case ejabberd_auth:is_user_exists(Authid, Host) of
|
||||||
|
false ->
|
||||||
|
?MSG("bad user ~p~n",[Authid]),
|
||||||
|
throw({error, "not-authorized"});
|
||||||
|
true ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
|
||||||
|
?MSG("GSSAPI authenticated ~p ~p~n", [Authid, Authzid]),
|
||||||
|
{ok, [{username, Authid}, {authzid, Authzid}]}.
|
|
@ -27,7 +27,9 @@
|
||||||
-module(cyrsasl_plain).
|
-module(cyrsasl_plain).
|
||||||
-author('alexey@process-one.net').
|
-author('alexey@process-one.net').
|
||||||
|
|
||||||
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
|
-export([start/1, stop/0, mech_new/1, mech_step/2, parse/1]).
|
||||||
|
|
||||||
|
-include("cyrsasl.hrl").
|
||||||
|
|
||||||
-behaviour(cyrsasl).
|
-behaviour(cyrsasl).
|
||||||
|
|
||||||
|
@ -48,12 +50,6 @@ start(_Opts) ->
|
||||||
stop() ->
|
stop() ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%% @spec (Host, GetPassword, CheckPassword, CheckPasswordDigest) -> {ok, State}
|
|
||||||
%% Host = string()
|
|
||||||
%% GetPassword = function()
|
|
||||||
%% CheckPassword = function()
|
|
||||||
%% State = mechstate()
|
|
||||||
|
|
||||||
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
|
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
|
||||||
{ok, #state{check_password = CheckPassword}}.
|
{ok, #state{check_password = CheckPassword}}.
|
||||||
|
|
||||||
|
|
|
@ -325,9 +325,16 @@ wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) ->
|
||||||
send_header(StateData, Server, "1.0", DefaultLang),
|
send_header(StateData, Server, "1.0", DefaultLang),
|
||||||
case StateData#state.authenticated of
|
case StateData#state.authenticated of
|
||||||
false ->
|
false ->
|
||||||
|
Realm =
|
||||||
|
case ejabberd_config:get_local_option({sasl_realm, Server}) of
|
||||||
|
undefined ->
|
||||||
|
"";
|
||||||
|
Realm0 ->
|
||||||
|
Realm0
|
||||||
|
end,
|
||||||
SASLState =
|
SASLState =
|
||||||
cyrsasl:server_new(
|
cyrsasl:server_new(
|
||||||
"jabber", Server, "", [],
|
"jabber", Server, Realm, [],
|
||||||
fun(U) ->
|
fun(U) ->
|
||||||
ejabberd_auth:get_password_with_authmodule(
|
ejabberd_auth:get_password_with_authmodule(
|
||||||
U, Server)
|
U, Server)
|
||||||
|
@ -339,8 +346,9 @@ wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) ->
|
||||||
fun(U, P, D, DG) ->
|
fun(U, P, D, DG) ->
|
||||||
ejabberd_auth:check_password_with_authmodule(
|
ejabberd_auth:check_password_with_authmodule(
|
||||||
U, Server, P, D, DG)
|
U, Server, P, D, DG)
|
||||||
end),
|
end,
|
||||||
SASL_Mechs = [exmpp_server_sasl:feature(
|
StateData#state.socket),
|
||||||
|
Mechs = [exmpp_server_sasl:feature(
|
||||||
cyrsasl:listmech(Server))],
|
cyrsasl:listmech(Server))],
|
||||||
SockMod =
|
SockMod =
|
||||||
(StateData#state.sockmod):get_sockmod(
|
(StateData#state.sockmod):get_sockmod(
|
||||||
|
|
|
@ -45,9 +45,11 @@
|
||||||
get_verify_result/1,
|
get_verify_result/1,
|
||||||
close/1,
|
close/1,
|
||||||
change_controller/2,
|
change_controller/2,
|
||||||
|
gethostname/1,
|
||||||
sockname/1, peername/1]).
|
sockname/1, peername/1]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
|
-include_lib("kernel/include/inet.hrl").
|
||||||
|
|
||||||
-record(socket_state, {sockmod, socket, receiver}).
|
-record(socket_state, {sockmod, socket, receiver}).
|
||||||
|
|
||||||
|
@ -228,6 +230,23 @@ peername(#socket_state{sockmod = SockMod, socket = Socket}) ->
|
||||||
SockMod:peername(Socket)
|
SockMod:peername(Socket)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
gethostname(#socket_state{socket = Socket} = State) ->
|
||||||
|
?DEBUG("gethostname ~p~n", [Socket]),
|
||||||
|
|
||||||
|
case sockname(State) of
|
||||||
|
{ok, {Addr, _Port}} ->
|
||||||
|
case inet:gethostbyaddr(Addr) of
|
||||||
|
{ok, HostEnt} when is_record(HostEnt, hostent) ->
|
||||||
|
?DEBUG("gethostname result ~p~n",
|
||||||
|
[HostEnt#hostent.h_name]),
|
||||||
|
{ok, HostEnt#hostent.h_name};
|
||||||
|
{error, Reason} = E ->
|
||||||
|
E
|
||||||
|
end;
|
||||||
|
{error, Reason} = E ->
|
||||||
|
E
|
||||||
|
end.
|
||||||
|
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
|
|
Loading…
Reference in New Issue
Block a user