mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-22 16:20:52 +01: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,
|
||||
register_mechanism/3,
|
||||
listmech/1,
|
||||
server_new/7,
|
||||
server_new/8,
|
||||
server_start/3,
|
||||
server_step/2]).
|
||||
|
||||
-include("cyrsasl.hrl").
|
||||
|
||||
%% @type saslmechanism() = {sasl_mechanism, Mechanism, Module, Require_Plain}
|
||||
%% Mechanism = string()
|
||||
%% Module = atom()
|
||||
@ -53,16 +55,15 @@
|
||||
%% Mech_State = term().
|
||||
%% State of this process.
|
||||
|
||||
-record(sasl_state, {service, myname, realm,
|
||||
get_password, check_password, check_password_digest,
|
||||
mech_mod, mech_state}).
|
||||
-record(sasl_state, {service, myname,
|
||||
mech_mod, mech_state, params}).
|
||||
|
||||
-export([behaviour_info/1]).
|
||||
|
||||
%% @hidden
|
||||
|
||||
behaviour_info(callbacks) ->
|
||||
[{mech_new, 4}, {mech_step, 2}];
|
||||
[{mech_new, 1}, {mech_step, 2}];
|
||||
behaviour_info(_Other) ->
|
||||
undefined.
|
||||
|
||||
@ -72,6 +73,7 @@ start() ->
|
||||
ets:new(sasl_mechanism, [named_table,
|
||||
public,
|
||||
{keypos, #sasl_mechanism.mechanism}]),
|
||||
cyrsasl_gssapi:start([]),
|
||||
cyrsasl_plain:start([]),
|
||||
cyrsasl_digest:start([]),
|
||||
cyrsasl_anonymous:start([]),
|
||||
@ -158,13 +160,20 @@ listmech(Host) ->
|
||||
%% CheckPassword = function()
|
||||
|
||||
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
|
||||
GetPassword, CheckPassword, CheckPasswordDigest) ->
|
||||
GetPassword, CheckPassword, CheckPasswordDigest, Socket) ->
|
||||
Params = #sasl_params{
|
||||
host = ServerFQDN,
|
||||
realm = UserRealm,
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPassword,
|
||||
check_password_digest= CheckPasswordDigest,
|
||||
socket = Socket
|
||||
},
|
||||
|
||||
#sasl_state{service = Service,
|
||||
myname = ServerFQDN,
|
||||
realm = UserRealm,
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPassword,
|
||||
check_password_digest= CheckPasswordDigest}.
|
||||
params = Params}.
|
||||
|
||||
|
||||
%% @spec (State, Mech, ClientIn) -> Ok | Continue | Error
|
||||
%% State = saslstate()
|
||||
@ -187,11 +196,8 @@ server_start(State, Mech, ClientIn) ->
|
||||
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),
|
||||
{ok, MechState} =
|
||||
Module:mech_new(State#sasl_state.params),
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_state = MechState},
|
||||
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).
|
||||
|
||||
-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).
|
||||
|
||||
@ -48,13 +50,7 @@ start(_Opts) ->
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
%% @spec (Host, GetPassword, CheckPassword, CheckPasswordDigest) -> {ok, State}
|
||||
%% Host = string()
|
||||
%% GetPassword = function()
|
||||
%% CheckPassword = function()
|
||||
%% State = mechstate()
|
||||
|
||||
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
mech_new(#sasl_params{host=Host}) ->
|
||||
{ok, #state{server = Host}}.
|
||||
|
||||
%% @spec (State, ClientIn) -> Ok | Error
|
||||
|
@ -29,10 +29,11 @@
|
||||
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/4,
|
||||
mech_new/1,
|
||||
mech_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("cyrsasl.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
@ -59,13 +60,8 @@ start(_Opts) ->
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
%% @spec (Host, GetPassword, CheckPassword, CheckPasswordDigest) -> {ok, State}
|
||||
%% Host = string()
|
||||
%% GetPassword = function()
|
||||
%% CheckPassword = function()
|
||||
%% State = mechstate()
|
||||
|
||||
mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
|
||||
mech_new(#sasl_params{host=Host, get_password=GetPassword,
|
||||
check_password_digest=CheckPasswordDigest}) ->
|
||||
{ok, #state{step = 1,
|
||||
nonce = randoms:get_string(),
|
||||
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).
|
||||
-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).
|
||||
|
||||
@ -48,12 +50,6 @@ start(_Opts) ->
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
%% @spec (Host, GetPassword, CheckPassword, CheckPasswordDigest) -> {ok, State}
|
||||
%% Host = string()
|
||||
%% GetPassword = function()
|
||||
%% CheckPassword = function()
|
||||
%% State = mechstate()
|
||||
|
||||
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
|
||||
{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),
|
||||
case StateData#state.authenticated of
|
||||
false ->
|
||||
Realm =
|
||||
case ejabberd_config:get_local_option({sasl_realm, Server}) of
|
||||
undefined ->
|
||||
"";
|
||||
Realm0 ->
|
||||
Realm0
|
||||
end,
|
||||
SASLState =
|
||||
cyrsasl:server_new(
|
||||
"jabber", Server, "", [],
|
||||
"jabber", Server, Realm, [],
|
||||
fun(U) ->
|
||||
ejabberd_auth:get_password_with_authmodule(
|
||||
U, Server)
|
||||
@ -339,8 +346,9 @@ wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) ->
|
||||
fun(U, P, D, DG) ->
|
||||
ejabberd_auth:check_password_with_authmodule(
|
||||
U, Server, P, D, DG)
|
||||
end),
|
||||
SASL_Mechs = [exmpp_server_sasl:feature(
|
||||
end,
|
||||
StateData#state.socket),
|
||||
Mechs = [exmpp_server_sasl:feature(
|
||||
cyrsasl:listmech(Server))],
|
||||
SockMod =
|
||||
(StateData#state.sockmod):get_sockmod(
|
||||
|
@ -45,9 +45,11 @@
|
||||
get_verify_result/1,
|
||||
close/1,
|
||||
change_controller/2,
|
||||
gethostname/1,
|
||||
sockname/1, peername/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include_lib("kernel/include/inet.hrl").
|
||||
|
||||
-record(socket_state, {sockmod, socket, receiver}).
|
||||
|
||||
@ -228,6 +230,23 @@ peername(#socket_state{sockmod = SockMod, socket = Socket}) ->
|
||||
SockMod:peername(Socket)
|
||||
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
|
||||
%%====================================================================
|
||||
|
Loading…
Reference in New Issue
Block a user