Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

290 lines (244 sloc) 9.369 kb
%%%-------------------------------------------------------------------
%%% File : yaws_pam.erl
%%% Author : <klacke@hyber.org>
%%% Description :
%%%
%%% Created : 20 Dec 2005 by <klacke@hyber.org>
%%%-------------------------------------------------------------------
-module(yaws_pam).
-behaviour(gen_server).
%%--------------------------------------------------------------------
%% External exports
-export([start_link/0,
start_link/3
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([auth/2,
close/1]).
-define(TO, (1000 * 60 * 3)).
-record(user, {i, %% sid
from, %% pid
ref}).%% monitorref
-record(state, {i,
port,
mode,
sids = [], % active sessions [#user{}]
srv,
reqs = [] % outstandig requests [#user{}]
}).
%%====================================================================
%% External functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link/0
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
start_link(Service, UseAccounting, UseSess) ->
Args = [Service, UseAccounting, UseSess],
gen_server:start_link({local, ?MODULE}, ?MODULE, Args, []).
auth(User, Password) ->
gen_server:call(?MODULE, {auth, User, Password}, infinity).
%% yaws never use close, ... no session mgmt in yaws
close(Handle) ->
gen_server:call(?MODULE, {close, Handle}, infinity).
%%====================================================================
%% Server functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init/1
%% Description: Initiates the server
%% Returns: {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%%--------------------------------------------------------------------
init([]) ->
{Srv, Act,Sess} =
{okundef(application:get_env(pam_service)),
okundef(application:get_env(pam_use_acct)),
okundef(application:get_env(pam_use_sess))},
init([Srv, Act, Sess]);
init([undefined, _Act, _Sess]) ->
error_logger:format("pam: need service in pam environment\n", []),
{stop, noservice};
init([SRV, Act, Sess]) ->
%% we never want to use the accounting
%% in yaws
M1 = case Act of
undefined ->
"";
true ->
"A";
false ->
[]
end,
%% and we definitely never want to use the
%% the session capability in yaws since noone is never
%% ever going to close the session
M2 = case Sess of
undefined ->
"";
true ->
"S";
false ->
[]
end,
Mode = M1 ++ M2,
%% we're not starting the portprogram now, it's done
%% on demand.
{ok, #state{i = 0,
mode = Mode,
srv = SRV,
port = undefined,
sids = [],
reqs = []}}.
okundef({ok,Val}) ->
Val;
okundef(undefined) ->
undefined.
%%--------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
%% Returns: {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
handle_call({auth, User, Password}, From, State0) ->
State = ensure_port(State0),
I = integer_to_list(State#state.i),
port_command(State#state.port, [$a, User, 0, Password, 0,
State#state.mode,0, I, 0]),
Ref = erlang:monitor(process, element(1, From)),
U = #user{i = State#state.i,
ref = Ref,
from = From},
R = [U| State#state.reqs],
{noreply, State#state{i = State#state.i + 1,
reqs = R}, ?TO};
handle_call({close, _Sid}, _From, State = #state{port=undefined}) ->
{reply, ok, State};
handle_call({close, Sid}, _From, State = #state{port = Port}) ->
case lists:keysearch(Sid, #user.i, State#state.sids) of
{value, U} ->
erlang:demonitor(U#user.ref);
false ->
ok
end,
port_command(Port, [$c, integer_to_list(Sid), 0]),
{reply, ok, State#state{
sids = lists:keydelete(Sid, #user.i, State#state.sids)},?TO}.
%%--------------------------------------------------------------------
%% Function: handle_cast/2
%% Description: Handling cast messages
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State, ?TO}.
%%--------------------------------------------------------------------
%% Function: handle_info/2
%% Description: Handling all non call/cast messages
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
handle_info(timeout, State) when
State#state.port /= undefined,
State#state.sids == [],
State#state.reqs == [] ->
unlink(State#state.port),
port_close(State#state.port),
{noreply, State#state{port = undefined}};
handle_info(timeout, State) ->
{noreply, State, ?TO};
handle_info({'EXIT', Port, _}, State = #state{port = Port}) ->
error_logger:format("epam port program died \n",[]),
lists:foreach(
fun(U) -> gen_server:reply(U#user.from, {no, "epam died"}) end,
State#state.reqs),
{noreply, State#state{sids = [],
reqs = [],
port = undefined
}};
handle_info({'DOWN', MonitorRef, _Type, _Object, _Info}, State)
when State#state.port /= undefined ->
case lists:keysearch(MonitorRef, #user.ref, State#state.sids) of
{value, U} ->
port_command(State#state.port,
[$c, integer_to_list(U#user.i), 0]),
S2 = lists:keydelete(MonitorRef, #user.ref, State#state.sids),
{noreply, State#state{sids = S2}, ?TO};
false ->
{noreply, State, ?TO}
end;
handle_info({'DOWN', _MonitorRef, _Type, _Object, _Info}, State) ->
{noreply, State};
handle_info({_Port, {data, Str}}, State) ->
case string:tokens(Str, " \n") of
["pam", IntStr | Reply] ->
I = list_to_integer(IntStr),
{value, U} = lists:keysearch(I, #user.i, State#state.reqs),
R = case reply(U#user.from, I, Reply) of
yes ->
[U |State#state.sids];
no ->
State#state.sids
end,
{noreply,
State#state{reqs = lists:keydelete(I,#user.i,State#state.reqs),
sids = R}, ?TO};
_Other ->
error_logger:format("epam: ~s", [Str]),
{noreply, State, ?TO}
end.
%%--------------------------------------------------------------------
%% Function: terminate/2
%% Description: Shutdown the server
%% Returns: any (ignored by gen_server)
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change/3
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState}
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
reply(From, Sid, ["yes"]) ->
gen_server:reply(From, {yes, Sid}),
yes;
reply(From, _Sid, ["no", What |Reason ]) ->
gen_server:reply(From, {no, {What, fsp(Reason)}}),
no.
fsp([]) -> [];
fsp([X]) -> X;
fsp([H|T]) -> H ++ " " ++ fsp(T).
ensure_port(S = #state{port = undefined, srv = Srv}) ->
Prg0 = filename:dirname(code:which(?MODULE)) ++
"/../priv/epam ",
Prg = Prg0 ++ Srv,
P = open_port({spawn, Prg}, [{packet, 2}]),
receive
{P, {data, "ok"}} ->
S#state{port = P};
{P, {data, ErrStr}} ->
error_logger:format("epam: ~s~n", [ErrStr]),
exit(noepam);
{'EXIT', P, _} ->
error_logger:format("yaws_pam: Cannot start epam",[]),
exit(noepam)
end;
ensure_port(S)->
S.
Jump to Line
Something went wrong with that request. Please try again.