Support SASL GSSAPI authentication (thanks to Mikael Magnusson)(EJAB-831)

This commit is contained in:
Badlop 2010-04-15 17:20:16 +02:00
parent 17fc992ba9
commit aa791ad0c4
8 changed files with 244 additions and 41 deletions

View File

@ -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
View 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}).

View File

@ -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

View File

@ -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
View 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}]}.

View File

@ -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}}.

View File

@ -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(

View File

@ -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
%%====================================================================