diff options
Diffstat (limited to 'p11p-daemon/src/p11p_client.erl')
| -rw-r--r-- | p11p-daemon/src/p11p_client.erl | 227 |
1 files changed, 162 insertions, 65 deletions
diff --git a/p11p-daemon/src/p11p_client.erl b/p11p-daemon/src/p11p_client.erl index 1222505..5fd3ff1 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. +%% Spawn an Erlang port running a proxy app. We use the 'remote' +%% program from p11-kit as the proxy app. -%% 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, -%% forward the reply to the requesting p11p_server. If the request -%% times out, inform the manager (our parent). +%% 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/4]). --export([request/2, stop/2]). +-export([start_link/6]). +-export([request/2, % Request from p11p-server. + stop/2]). % Manager stopping us. -include("p11p_rpc.hrl"). @@ -23,39 +34,51 @@ code_change/3]). %% Records and types. +-type token_state() :: started | + initialized | + session | + loggedin | + opact | + finalized. + -record(state, { + token :: string(), % Token name. + timeout :: non_neg_integer(), + port :: port(), replyto :: pid() | undefined, + + p11state = started :: token_state(), timer :: reference() | undefined, - token :: string(), % Token name. - msg :: p11rpc:msg() | undefined, + response :: p11rpc:msg() | undefined, recv_count = 0 :: non_neg_integer(), send_count = 0 :: non_neg_integer() }). %% API. --spec start_link(atom(), string(), string(), list()) -> - {ok, pid()} | {error, term()}. -start_link(ServName, TokName, ModPath, ModEnv) -> - lager:info("~p: p11p_client starting for ~s", [ServName, ModPath]), +-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, - [TokName, ModPath, ModEnv], []). + [TokName, Server, ModPath, ModEnv, Timeout], []). --spec request(pid(), p11rpc_msg()) -> {ok, non_neg_integer()}. +-spec request(pid(), p11rpc_msg()) -> ack | nack | {ok, non_neg_integer()}. 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 is to act on 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}). %% Genserver callbacks. -init([TokName, ModPath, ModEnv]) -> +init([TokName, Server, ModPath, ModEnv, Timeout]) -> ProxyAppBinPath = p11p_config:proxyapp_bin_path(), Port = open_port({spawn_executable, ProxyAppBinPath}, [stream, @@ -63,21 +86,51 @@ init([TokName, ModPath, ModEnv]) -> {env, ModEnv}, {args, [ModPath, "-v"]} % FIXME: Remove -v ]), + 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}}. - -handle_call({request, Request}, {FromPid, _Tag}, - #state{port = Port, send_count = Sent} = S) -> - %%lager:debug("~p: sending request from ~p to prxoy app ~p", [self(), FromPid, Port]), - D = p11p_rpc:serialise(Request), - Buf = case Sent of - 0 -> <<?RPC_VERSION:8, D/binary>>; - _ -> D - end, - ok = do_send(Port, Buf), - {reply, {ok, sizeBuf}, S#state{replyto = FromPid, timer = start_timer(Port), - send_count = Sent + 1}}; + {ok, #state{port = Port, + token = TokName, + replyto = Server, + timeout = Timeout}}. + +handle_call({request, Request}, + {FromPid, _Tag}, + State = #state{port = Port, send_count = Sent}) -> + case + case State#state.p11state of + started -> + case p11p_rpc:req_id(Request) of + ?P11_RPC_CALL_C_Logout -> ack; + ?P11_RPC_CALL_C_CloseSession -> ack; + ?P11_RPC_CALL_C_Finalize -> ack; + + ?P11_RPC_CALL_C_Initialize -> pass; + ?P11_RPC_CALL_C_OpenSession -> pass; + ?P11_RPC_CALL_C_Login -> pass; + + _ -> nack + end; + _ -> + pass + end + of + pass -> + lager:debug("~p: sending request from ~p to prxoy app ~p", [self(), FromPid, Port]), + D = p11p_rpc:serialise(Request), + Buf = case Sent of + 0 -> <<?RPC_VERSION:8, D/binary>>; + _ -> D + end, + {ok, _} = send_request(Port, Buf), + {reply, + {ok, size(Buf)}, + State#state{replyto = FromPid, + timer = start_timer(State#state.timeout, Port), + send_count = Sent + 1}}; + Ret -> + {reply, Ret, State} + end; handle_call(Call, _From, State) -> lager:debug("~p: Unhandled call: ~p~n", [self(), Call]), @@ -90,12 +143,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.msg == undefined -> + when Port == State#state.port, State#state.response == undefined -> case hd(Data) of % First octet is RPC protocol version. ?RPC_VERSION -> - {noreply, handle_proxy_app_data(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]), @@ -103,17 +157,18 @@ handle_info({Port, {data, Data}}, State) end; %% Receiving more data from proxy app. -handle_info({Port, {data, Data}}, #state{msg = Msg} = State) +handle_info({Port, {data, Data}}, State) when Port == State#state.port -> - {noreply, handle_proxy_app_data(State, Msg, Data)}; + NewState = response_in(State, State#state.response, Data), + {noreply, NewState}; %% Proxy app timed out. -handle_info({timeout, Timer, Port}, #state{token = Tok, replyto = Server} = S) - when Port == S#state.port, Timer == S#state.timer -> - lager:info("~p: rpc request timed out, exiting", [self()]), - p11p_manager:server_event(timeout, [Tok, Server]), - 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]), @@ -128,36 +183,78 @@ code_change(_OldVersion, State, _Extra) -> {ok, State}. %% Private -do_send(Port, Buf) -> - %%lager:debug("~p: sending ~B octets to proxy app", [self(), size(Buf)]), - - %% case rand:uniform(15) of - %% 1 -> - %% lager:debug("~p: faking unresponsive proxy app (~p) by not sending it any.", [self(), Port]); - %% _ -> - %% port_command(Port, Buf) - %% end, +send_request(Port, Buf) -> + Rand = rand:uniform(100), + Prob = p11p_config:testing_drop_prob(), + if + Rand =< Prob -> + lager:debug("~p: faking unresponsive token (~p) by not sending", + [self(), Port]); + true -> + lager:debug("~p: sending ~B octets to token", [self(), size(Buf)]), + true = port_command(Port, Buf) + end, + {ok, size(Buf)}. - true = port_command(Port, Buf), - ok. - -handle_proxy_app_data(#state{replyto = Pid, timer = Timer, recv_count = Recv} = S, - MsgIn, DataIn) -> +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} -> - S#state{msg = Msg}; + S#state{response = Msg}; {done, Msg} -> cancel_timer(Timer), + lager:debug("~p: <- ~s", [self(), p11p_rpc:dump(Msg)]), {ok, _BytesSent} = p11p_server:reply(Pid, Msg), %% Saving potential data not consumed by parse/2 in new message. - S#state{msg = p11p_rpc:new(Msg#p11rpc_msg.buffer), + S#state{response = p11p_rpc:new(Msg#p11rpc_msg.buffer), + p11state = runstate(S#state.p11state, p11p_rpc:req_id(Msg)), recv_count = Recv + 1} end. -start_timer(Port) -> +start_timer(Timeout, Port) -> %%lager:debug("~p: starting timer", [self()]), - erlang:start_timer(3000, self(), Port). + erlang:start_timer(Timeout, self(), Port). cancel_timer(Timer) -> %%lager:debug("~p: canceling timer", [self()]), erlang:cancel_timer(Timer, [{async, true}, {info, false}]). + +-spec runstate(token_state(), non_neg_integer()) -> token_state(). +runstate(started, ReqId) -> + case ReqId of + ?P11_RPC_CALL_C_Initialize -> + initialized; + _ -> + started + end; +runstate(initialized, ReqId) -> + case ReqId of + ?P11_RPC_CALL_C_OpenSession -> + session; + ?P11_RPC_CALL_C_Finalize -> + finalized; + _ -> + initialized + end; +runstate(session, ReqId) -> + case ReqId of + ?P11_RPC_CALL_C_Login -> + loggedin; + ?P11_RPC_CALL_C_CloseSession -> + initialized; + ?P11_RPC_CALL_C_Finalize -> + finalized; + _ -> + session + end; +runstate(loggedin, ReqId) -> + case ReqId of + ?P11_RPC_CALL_C_Logout -> + session; + ?P11_RPC_CALL_C_CloseSession -> + initialized; + ?P11_RPC_CALL_C_Finalize -> + finalized; + _ -> + loggedin + end. |
