summaryrefslogtreecommitdiff
path: root/p11p-daemon/src/p11p_client.erl
diff options
context:
space:
mode:
Diffstat (limited to 'p11p-daemon/src/p11p_client.erl')
-rw-r--r--p11p-daemon/src/p11p_client.erl227
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.