commit 010be0d97ba0388bb8f3a9ef350a651de433f05a Author: Badlop Date: Wed Feb 11 12:51:52 2009 +0100 SASL GSSAPI authentication for ejabberd 2.0.3 How to apply: cd ejabberd-2.0.3 patch -p1 < gssapi-2.0.3.diff Requires esasl and egssapi: http://www.hem.za.org/ Patch homepage: http://www.ejabberd.im/cyrsasl_gssapi diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl index fb47b03..3eba8d4 100644 --- a/src/cyrsasl.erl +++ b/src/cyrsasl.erl @@ -30,19 +30,20 @@ -export([start/0, register_mechanism/3, listmech/1, - server_new/6, + server_new/7, server_start/3, server_step/2]). -record(sasl_mechanism, {mechanism, module, require_plain_password}). --record(sasl_state, {service, myname, realm, - get_password, check_password, - mech_mod, mech_state}). +-record(sasl_state, {service, myname, + mech_mod, mech_state, ctx}). + +-include("ejabberd.hrl"). -export([behaviour_info/1]). behaviour_info(callbacks) -> - [{mech_new, 3}, {mech_step, 2}]; + [{mech_new, 1}, {mech_step, 2}]; behaviour_info(_Other) -> undefined. @@ -50,6 +51,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([]), @@ -113,22 +115,25 @@ listmech(Host) -> filter_anonymous(Host, Mechs). server_new(Service, ServerFQDN, UserRealm, _SecFlags, - GetPassword, CheckPassword) -> + GetPassword, CheckPassword, FQDN) -> + Ctx = #sasl_ctx{ + host = ServerFQDN, + realm = UserRealm, + get_password = GetPassword, + check_password = CheckPassword, + fqdn = FQDN + }, + #sasl_state{service = Service, myname = ServerFQDN, - realm = UserRealm, - get_password = GetPassword, - check_password = CheckPassword}. + ctx = Ctx}. 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), + {ok, MechState} = Module:mech_new(State#sasl_state.ctx), server_step(State#sasl_state{mech_mod = Module, mech_state = MechState}, ClientIn); diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl index 9fa5176..af93207 100644 --- a/src/cyrsasl_anonymous.erl +++ b/src/cyrsasl_anonymous.erl @@ -27,12 +27,14 @@ -module(cyrsasl_anonymous). --export([start/1, stop/0, mech_new/3, mech_step/2]). +-export([start/1, stop/0, mech_new/1, mech_step/2]). -behaviour(cyrsasl). -record(state, {server}). +-include("ejabberd.hrl"). + start(_Opts) -> cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, false), ok. @@ -40,7 +42,7 @@ start(_Opts) -> stop() -> ok. -mech_new(Host, _GetPassword, _CheckPassword) -> +mech_new(#sasl_ctx{host=Host}) -> {ok, #state{server = Host}}. mech_step(State, _ClientIn) -> diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index 1e40e14..077055a 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -11,7 +11,7 @@ -export([start/1, stop/0, - mech_new/3, + mech_new/1, mech_step/2]). -include("ejabberd.hrl"). @@ -27,7 +27,7 @@ start(_Opts) -> stop() -> ok. -mech_new(Host, GetPassword, _CheckPassword) -> +mech_new(#sasl_ctx{host=Host, get_password=GetPassword}) -> {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 0000000..d292565 --- /dev/null +++ b/src/cyrsasl_gssapi.erl @@ -0,0 +1,143 @@ +%%%---------------------------------------------------------------------- +%%% File : cyrsasl_gssapi.erl +%%% Author : Mikael Magnusson +%%% Purpose : GSSAPI SASL mechanism +%%% Created : 1 June 2007 by Mikael Magnusson +%%% Id : $Id: $ +%%%---------------------------------------------------------------------- +%%% +%%% Copyright (C) 2007 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: $ '). + +-include("ejabberd.hrl"). + +-export([start/1, + stop/0, + mech_new/1, + mech_step/2]). + +-behaviour(cyrsasl). + +-define(SERVER, cyrsasl_gssapi). +-define(MSG, ?DEBUG). + +-record(state, {sasl, + needsmore=true, + step=0, + host, + authid, + authzid, + authrealm}). + +start(_Opts) -> + ChildSpec = + {?SERVER, + {esasl, start_link, [{local, ?SERVER}]}, + transient, + 1000, + worker, + [esasl]}, + + {ok, _Pid} = supervisor:start_child(ejabberd_sup, ChildSpec), + + cyrsasl:register_mechanism("GSSAPI", ?MODULE, false). + +stop() -> + esasl:stop(?SERVER), + supervisor:terminate_child(ejabberd_sup, ?SERVER), + supervisor:delete_child(ejabberd_sup, ?SERVER). + +mech_new(#sasl_ctx{host=Host, fqdn=FQDN}) -> + ?MSG("mech_new ~p ~p~n", [Host, FQDN]), + {ok, Sasl} = esasl:server_start(?SERVER, "GSSAPI", "xmpp", FQDN), + {ok, #state{sasl=Sasl,host=Host}}. + +mech_step(State, ClientIn) when is_list(ClientIn) -> + catch do_step(State, ClientIn). + +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 = ejabberd_config:get_local_option({sasl_realm, Host}), + + 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 d9c7f1a..5187665 100644 --- a/src/cyrsasl_plain.erl +++ b/src/cyrsasl_plain.erl @@ -27,10 +27,11 @@ -module(cyrsasl_plain). -author('alexey@process-one.net'). --export([start/1, stop/0, mech_new/3, mech_step/2, parse/1]). +-export([start/1, stop/0, mech_new/1, mech_step/2, parse/1]). -behaviour(cyrsasl). +-include("ejabberd.hrl"). -record(state, {check_password}). start(_Opts) -> @@ -40,7 +41,7 @@ start(_Opts) -> stop() -> ok. -mech_new(_Host, _GetPassword, CheckPassword) -> +mech_new(#sasl_ctx{check_password=CheckPassword}) -> {ok, #state{check_password = CheckPassword}}. mech_step(State, ClientIn) -> diff --git a/src/ejabberd.hrl b/src/ejabberd.hrl index 717496f..772ec72 100644 --- a/src/ejabberd.hrl +++ b/src/ejabberd.hrl @@ -59,3 +59,9 @@ -define(CRITICAL_MSG(Format, Args), ejabberd_logger:critical_msg(?MODULE,?LINE,Format, Args)). +-record(sasl_ctx, { + host, + realm, + get_password, + check_password, + fqdn}). diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 8407d97..7a4021f 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -66,6 +66,7 @@ -record(state, {socket, sockmod, socket_monitor, + fqdn, streamid, sasl_state, access, @@ -197,9 +198,11 @@ init([{SockMod, Socket}, Opts]) -> Socket end, SocketMonitor = SockMod:monitor(Socket1), + {ok, FQDN} = ejabberd_net:gethostname(Socket), {ok, wait_for_stream, #state{socket = Socket1, sockmod = SockMod, socket_monitor = SocketMonitor, + fqdn = FQDN, zlib = Zlib, tls = TLS, tls_required = StartTLSRequired, @@ -246,6 +249,8 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> send_text(StateData, Header), case StateData#state.authenticated of false -> + FQDN = StateData#state.fqdn, + ?INFO_MSG("FQDN: ~p~n", [FQDN]), SASLState = cyrsasl:server_new( "jabber", Server, "", [], @@ -256,7 +261,8 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> fun(U, P) -> ejabberd_auth:check_password_with_authmodule( U, Server, P) - end), + end, + FQDN), Mechs = lists:map( fun(S) -> {xmlelement, "mechanism", [], diff --git a/src/ejabberd_net.erl b/src/ejabberd_net.erl new file mode 100644 index 0000000..ee2af5c --- /dev/null +++ b/src/ejabberd_net.erl @@ -0,0 +1,35 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_net.erl +%%% Author : Mikael Magnusson +%%% Purpose : Serve C2S connection +%%% Created : 6 June 2007 by Mikael Magnusson +%%% Id : $Id: $ +%%%---------------------------------------------------------------------- + +-module(ejabberd_net). +-author('mikma@users.sourceforge.net'). +%% -update_info({update, 0}). + +-export([gethostname/1]). + +-include("ejabberd.hrl"). +-include_lib("kernel/include/inet.hrl"). + +%% Copied from ejabberd_socket.erl of ejabberd 2.0.3 +-record(socket_state, {sockmod, socket, receiver}). + +%% +%% gethostname(Socket) +%% +gethostname(Socket) -> + ?INFO_MSG("gethostname ~p~n", [Socket]), +%% {ok, "skinner.hem.za.org"}. + + {ok, {Addr, _Port}} = inet:sockname(Socket#socket_state.socket), + case inet:gethostbyaddr(Addr) of + {ok, HostEnt} when is_record(HostEnt, hostent) -> + {ok, HostEnt#hostent.h_name}; + {error, What} -> + ?ERROR_MSG("Error in gethostname:~nSocket: ~p~nError: ~p", [What]), + error + end.