From aa791ad0c4d640f111d97e770ddd0c6328472593 Mon Sep 17 00:00:00 2001 From: Badlop Date: Thu, 15 Apr 2010 17:20:16 +0200 Subject: [PATCH] Support SASL GSSAPI authentication (thanks to Mikael Magnusson)(EJAB-831) --- src/cyrsasl.erl | 36 ++++---- src/cyrsasl.hrl | 15 ++++ src/cyrsasl_anonymous.erl | 12 +-- src/cyrsasl_digest.erl | 12 +-- src/cyrsasl_gssapi.erl | 167 ++++++++++++++++++++++++++++++++++++++ src/cyrsasl_plain.erl | 10 +-- src/ejabberd_c2s.erl | 14 +++- src/ejabberd_socket.erl | 19 +++++ 8 files changed, 244 insertions(+), 41 deletions(-) create mode 100644 src/cyrsasl.hrl create mode 100644 src/cyrsasl_gssapi.erl diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl index 312d32bed..f0737d352 100644 --- a/src/cyrsasl.erl +++ b/src/cyrsasl.erl @@ -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); diff --git a/src/cyrsasl.hrl b/src/cyrsasl.hrl new file mode 100644 index 000000000..b4cc3e300 --- /dev/null +++ b/src/cyrsasl.hrl @@ -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}). diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl index 5d302c051..c20973256 100644 --- a/src/cyrsasl_anonymous.erl +++ b/src/cyrsasl_anonymous.erl @@ -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 diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index eeef4c150..a4564951f 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -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, diff --git a/src/cyrsasl_gssapi.erl b/src/cyrsasl_gssapi.erl new file mode 100644 index 000000000..5fdf071a5 --- /dev/null +++ b/src/cyrsasl_gssapi.erl @@ -0,0 +1,167 @@ +%%%---------------------------------------------------------------------- +%%% File : cyrsasl_gssapi.erl +%%% Author : Mikael Magnusson +%%% Purpose : GSSAPI SASL mechanism +%%% Created : 1 June 2007 by Mikael Magnusson +%%% Id : $Id: $ +%%%---------------------------------------------------------------------- +%%% +%%% Copyright (C) 2007-2009 Mikael Magnusson +%%% +%%% 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, ""}. +%%% +%%% 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}]}. diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl index 0b1c873c3..690e99281 100644 --- a/src/cyrsasl_plain.erl +++ b/src/cyrsasl_plain.erl @@ -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}}. diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 010fb177f..01d3c92c1 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -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( diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl index 8eeda452d..6de915b35 100644 --- a/src/ejabberd_socket.erl +++ b/src/ejabberd_socket.erl @@ -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 %%====================================================================