diff options
Diffstat (limited to 'p11p-daemon/src/p11p_client.erl')
-rw-r--r-- | p11p-daemon/src/p11p_client.erl | 94 |
1 files changed, 58 insertions, 36 deletions
diff --git a/p11p-daemon/src/p11p_client.erl b/p11p-daemon/src/p11p_client.erl index 87c2949..d6c73ac 100644 --- a/p11p-daemon/src/p11p_client.erl +++ b/p11p-daemon/src/p11p_client.erl @@ -1,20 +1,31 @@ %%% Copyright (c) 2019, Sunet. %%% See LICENSE for licensing information. -%% A client spawns an Erlang port running a proxy app, i.e. the -%% 'remote' program from p11-kit. - -%% Receive p11 requests from p11p_server, forward them to the proxy app, -%% wait for a reply. If a reply is received within a timeout period, -%% proxy the reply to the requesting p11p_server. If the request -%% times out, inform the manager (our parent). +%% Spawn an Erlang port running a proxy app. We use the 'remote' +%% program from p11-kit as the proxy app. + +%% Receive PKCS#11 requests from a p11p_server, forward them to the +%% proxy app, wait for a reply. If a reply is received within a +%% timeout period, proxy the reply to the requesting p11p_server. If +%% the request times out, inform the manager (our parent) and exit. + +%% Track a subset of the PKCS#11 state in order to handle token +%% restarts. We start in state 'started'. While in 'started', we allow +%% only a few "opening" calls (Initialize, OpenSession and Login) +%% through to the token. Corresponding "closing" calls (Finalize, +%% CloseSession and Logout) are sent an immediate OK response without +%% forwarding them to the token. Any other call is rejected by +%% responding with an error. This should make well behaving P11 +%% applications be able to deal with us switching the token under +%% their feet. -module(p11p_client). -behaviour(gen_server). %% API. -export([start_link/6]). --export([request/2, stop/2]). +-export([request/2, % Request from p11p-server. + stop/2]). % Manager stopping us. -include("p11p_rpc.hrl"). @@ -23,7 +34,12 @@ code_change/3]). %% Records and types. --type token_state() :: started | initialized | session | loggedin | opact | finalized. +-type token_state() :: started | + initialized | + session | + loggedin | + opact | + finalized. -record(state, { token :: string(), % Token name. @@ -40,8 +56,8 @@ }). %% API. --spec start_link(atom(), string(), pid(), string(), list(), non_neg_integer()) -> - {ok, pid()} | {error, term()}. +-spec start_link(atom(), string(), pid(), string(), list(), + non_neg_integer()) -> {ok, pid()} | {error, term()}. start_link(ServName, TokName, Server, ModPath, ModEnv, Timeout) -> lager:info("~p: starting p11p_client for ~s", [self(), TokName]), gen_server:start_link({local, ServName}, ?MODULE, @@ -51,12 +67,13 @@ start_link(ServName, TokName, Server, ModPath, ModEnv, Timeout) -> request(Client, Request) -> gen_server:call(Client, {request, Request}). -%% Use stop/1 instead of gen_server:stop/1 if you're uncertain whether -%% we (Pid) are alive or not. An example of when that can happen is -%% when the manager receives a server_event about a lost p11 app. If -%% the server process terminated on request from us because we timed -%% out on an rpc call, chances are that we have already terminated by -%% the time the manager acts on the information about the lost app. +%% You should invoke stop/1 instead of gen_server:stop/1 if you're +%% uncertain whether we (Pid) are alive or not. An example of when +%% that can happen is when the manager receives a server_event about a +%% lost P11 app -- if the server process terminated on request from us +%% because we timed out on an RPC call, chances are that we have +%% already terminated by the time the manager acts on the information +%% about the lost app. stop(Pid, Reason) -> gen_server:cast(Pid, {stop, Reason}). @@ -72,13 +89,16 @@ init([TokName, Server, ModPath, ModEnv, Timeout]) -> true = is_port(Port), lager:debug("~p: ~s: new proxy app port: ~p", [self(), ProxyAppBinPath, Port]), lager:debug("~p: ~s: module: ~s, env: ~p", [self(), ProxyAppBinPath, ModPath, ModEnv]), - {ok, #state{port = Port, token = TokName, replyto = Server, timeout = Timeout}}. + {ok, #state{port = Port, + token = TokName, + replyto = Server, + timeout = Timeout}}. handle_call({request, Request}, {FromPid, _Tag}, - S = #state{port = Port, send_count = Sent}) -> + State = #state{port = Port, send_count = Sent}) -> case - case S#state.p11state of + case State#state.p11state of started -> case p11p_rpc:req_id(Request) of ?P11_RPC_CALL_C_Logout -> ack; @@ -96,9 +116,9 @@ handle_call({request, Request}, end of ack -> - {reply, ack, S}; + {reply, ack, State}; nack -> - {reply, nack, S}; + {reply, nack, State}; pass -> lager:debug("~p: sending request from ~p to prxoy app ~p", [self(), FromPid, Port]), D = p11p_rpc:serialise(Request), @@ -110,9 +130,9 @@ handle_call({request, Request}, {reply, {ok, size(Buf)}, - S#state{replyto = FromPid, - timer = start_timer(S#state.timeout, Port), - send_count = Sent + 1}} + State#state{replyto = FromPid, + timer = start_timer(State#state.timeout, Port), + send_count = Sent + 1}} end; handle_call(Call, _From, State) -> @@ -126,12 +146,13 @@ handle_cast(Cast, State) -> lager:debug("~p: unhandled cast: ~p~n", [self(), Cast]), {noreply, State}. -%% Receiving the very first response from proxy app since it was started. +%% Receiving the very first octets from proxy app since it was started. handle_info({Port, {data, Data}}, State) when Port == State#state.port, State#state.response == undefined -> case hd(Data) of % First octet is RPC protocol version. ?RPC_VERSION -> - {noreply, response_in(State, p11p_rpc:new(), tl(Data))}; + NewState = response_in(State, p11p_rpc:new(), tl(Data)), + {noreply, NewState}; BadVersion -> lager:info("~p: ~p: invalid RPC version: ~p", [self(), Port, BadVersion]), @@ -139,17 +160,18 @@ handle_info({Port, {data, Data}}, State) end; %% Receiving more data from proxy app. -handle_info({Port, {data, Data}}, #state{response = Msg} = State) +handle_info({Port, {data, Data}}, State) when Port == State#state.port -> - {noreply, response_in(State, Msg, Data)}; + NewState = response_in(State, State#state.response, Data), + {noreply, NewState}; %% Proxy app timed out. -handle_info({timeout, Timer, Port}, S = #state{token = Tok}) - when Port == S#state.port, Timer == S#state.timer -> - lager:info("~p: rpc request for ~s timed out, exiting", [self(), Tok]), - p11p_manager:client_event(timeout, Tok), - State = S#state{timer = undefined}, - {stop, normal, State}; +handle_info({timeout, Timer, Port}, State) + when Port == State#state.port, Timer == State#state.timer -> + lager:info("~p: rpc request for ~s timed out, exiting", [self(), State#state.token]), + p11p_manager:client_event(timeout, State#state.token), + NewState = State#state{timer = undefined}, + {stop, normal, NewState}; handle_info(Info, State) -> lager:debug("~p: Unhandled info: ~p~n", [self(), Info]), @@ -176,7 +198,7 @@ do_send(Port, Buf) -> end, {ok, size(Buf)}. -response_in(#state{replyto = Pid, timer = Timer, recv_count = Recv} = S, +response_in(S = #state{replyto = Pid, timer = Timer, recv_count = Recv}, MsgIn, DataIn) -> case p11p_rpc:parse(MsgIn, list_to_binary(DataIn)) of {needmore, Msg} -> |