summaryrefslogtreecommitdiff
path: root/p11p-daemon/src/p11p_server.erl
diff options
context:
space:
mode:
Diffstat (limited to 'p11p-daemon/src/p11p_server.erl')
-rw-r--r--p11p-daemon/src/p11p_server.erl165
1 files changed, 114 insertions, 51 deletions
diff --git a/p11p-daemon/src/p11p_server.erl b/p11p-daemon/src/p11p_server.erl
index cbc00df..c27d825 100644
--- a/p11p-daemon/src/p11p_server.erl
+++ b/p11p-daemon/src/p11p_server.erl
@@ -1,8 +1,14 @@
%%% Copyright (c) 2019, Sunet.
%%% See LICENSE for licensing information.
-%% Create an AF_UNIX socket and accept connections. On connect, spawn
-%% another p11p_server process.
+%% Create an AF_UNIX socket and accept connections from a P11 app. On
+%% connect, spawn another p11p_server process.
+
+%% Recevie PKCS#11 requests on the socket and forward them to a
+%% p11p-client.
+
+%% Receive responses from our p11p-client and forward them to the P11
+%% app.
-module(p11p_server).
-behaviour(gen_server).
@@ -11,7 +17,8 @@
%% API.
-export([start_link/1]).
--export([reply/2]).
+-export([reply/2, % Replies from p11p-client.
+ token_gone/2]). % p11p-client disappeared.
%% Genserver callbacks.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
@@ -19,13 +26,13 @@
%% Records and types.
-record(state, {
- tokname :: string(),
- client :: pid() | undefined,
- socket :: gen_tcp:socket(),
- msg :: p11rpc_msg() | undefined,
- recv_count = 0 :: non_neg_integer(),
- send_count = 0 :: non_neg_integer()
- %%clientbuf = <<>> :: binary()
+ tokname :: string(), % Virtual token being served.
+ socket :: gen_tcp:socket(), % AF_UNIX socket.
+ client :: pid() | undefined, % Our p11p-client.
+ req_in :: p11rpc_msg() | undefined, % Request received from P11 app.
+ req_out :: p11rpc_msg() | undefined, % Request sent to p11p-client.
+ recv_count = 0 :: non_neg_integer(), % Counting requests from P11 app.
+ send_count = 0 :: non_neg_integer() % Conting requests to p11p-client.
}).
%% API.
@@ -37,22 +44,26 @@ start_link(Args) ->
reply(Pid, Response) ->
gen_server:call(Pid, {respond, Response}).
+-spec token_gone(pid(), boolean()) -> ok.
+token_gone(Pid, Hangup) ->
+ case process_info(Pid) of undefined -> error(bad_server_pid); _ -> nop end,
+ gen_server:cast(Pid, {token_gone, Hangup}).
+
+
%% Genserver callbacks.
init([Token, Socket]) ->
- lager:debug("~p: p11p_server:init", [self()]),
- process_flag(trap_exit, true), % Need terminate/2.
- gen_server:cast(self(), accept), % Invoke accept, returning a socket in state.
+ lager:debug("~p: p11p_server starting for ~s", [self(), Token]),
+ process_flag(trap_exit, true), % Call terminate/2 on exit.
+ %% Invoking gen_tcp:accept(), updating state with a new socket.
+ gen_server:cast(self(), accept),
{ok, #state{tokname = Token, socket = Socket}}.
-handle_call({respond, R}, _, #state{socket = Sock, send_count = Sent} = S) ->
- D = p11p_rpc:serialise(R),
- Buf = case Sent of
- 0 -> <<?RPC_VERSION:8, D/binary>>;
- _ -> D
- end,
- %%lager:debug("~p: sending ~B octets as response", [self(), size(Buf)]),
- ok = gen_tcp:send(Sock, Buf), % TODO: what about short writes?
- {reply, {ok, size(Buf)}, S#state{send_count = Sent + 1}};
+%% FIXME: make this a cast?
+handle_call({respond, Resp}, _, State = #state{send_count = Sent}) ->
+ {reply,
+ {ok, send_response(State#state.socket, p11p_rpc:serialise(Resp), Sent)},
+ State#state{req_out = undefined,
+ send_count = Sent + 1}};
handle_call(Call, _, S) ->
lager:debug("~p: Unhandled call: ~p~n", [self(), Call]),
@@ -60,13 +71,13 @@ handle_call(Call, _, S) ->
%% Wait for new connection.
handle_cast(accept, State = #state{tokname = TokName, socket = ListenSocket}) ->
- %% Blocking until client connects or timeout fires.
+ %% Blocking until P11 app connects or the timeout fires.
%% Without a timeout our supervisor cannot terminate us.
%% On timeout, just invoke ourselves again.
case gen_tcp:accept(ListenSocket, 900) of
{ok, Sock} ->
lager:debug("~p: ~p: new connection accepted", [self(), Sock]),
- %% Start a new acceptor and return with new socket in state.
+ %% Start a new acceptor and return with the new socket in state.
p11p_server_sup:start_server([TokName, ListenSocket]),
{noreply, State#state{socket = Sock}};
{error, timeout} ->
@@ -77,44 +88,67 @@ handle_cast(accept, State = #state{tokname = TokName, socket = ListenSocket}) ->
{stop, normal, State}
end;
+handle_cast({token_gone, Hangup}, State = #state{send_count = Sent}) ->
+ Resp = p11p_rpc:msg_error(p11p_rpc:call_code(State#state.req_out),
+ ?CKR_DEVICE_ERROR),
+ {ok, _} = send_response(State#state.socket, p11p_rpc:serialise(Resp), Sent),
+ NewState = State#state{req_out = undefined,
+ send_count = Sent + 1},
+ case Hangup of
+ true ->
+ lager:info("~p: Token reported gone, no more retries, closing.",
+ [self()]),
+ {stop, normal, NewState}; %FIXME: no need to update state, i think
+ false ->
+ lager:info("~p: Token reported gone, retrying with new token.",
+ [self()]),
+ NewClient = p11p_manager:client_for_token(State#state.tokname),
+ {noreply, NewState#state{client = NewClient}}
+ end;
+
handle_cast(Cast, State) ->
lager:debug("~p: Unhandled cast: ~p~n", [self(), Cast]),
{noreply, State}.
-%% First packet from P11 client.
-handle_info({tcp, Port, DataIn}, #state{tokname = TokName} = S)
- when S#state.client == undefined ->
- %%lager:debug("~p: received ~B octets from client on socket ~p, from new client", [self(), size(Data), Port]),
+%% First chunk from P11 app.
+handle_info({tcp, Port, DataIn}, State)
+ when State#state.client == undefined ->
+ lager:debug("~p: received ~B octets from client on socket ~p, from new client", [self(), size(DataIn), Port]),
<<RPCVersion:8, Data/binary>> = DataIn,
case RPCVersion of
?RPC_VERSION ->
- {noreply,
- p11_client_data(
- S#state{client = p11p_manager:client_for_token(TokName)},
- p11p_rpc:new(),
- Data)};
+ NewClient = p11p_manager:client_for_token(State#state.tokname),
+ NewState = request_in(State#state{client = NewClient},
+ p11p_rpc:new(), Data),
+ {noreply, NewState};
BadVersion ->
lager:info("~p: ~p: invalid RPC version: ~p", [self(), Port,
BadVersion]),
- {stop, bad_proto, S}
+ {stop, bad_proto, State}
end;
-%% Subsequent packages from P11 client.
-handle_info({tcp, _Port, DataIn}, #state{msg = Msg} = S) ->
- %%lager:debug("~p: received ~B octets from client on socket ~p, with ~B octets already in buffer", [self(), size(Data), Port, size(Msg#p11rpc_msg.buffer)]),
- {noreply, p11_client_data(S, Msg, DataIn)};
+%% Subsequent packages from P11 app.
+handle_info({tcp, Port, DataIn}, State) ->
+ Msg = State#state.req_in,
+ lager:debug("~p: received ~B octets from client on socket ~p, with ~B octets already in buffer", [self(), size(DataIn), Port, size(Msg#p11rpc_msg.buffer)]),
+ NewState = request_in(State, State#state.req_in, DataIn),
+ {noreply, NewState};
-handle_info({tcp_closed, Port}, S) ->
+handle_info({tcp_closed, Port}, State) ->
lager:debug("~p: socket ~p closed", [self(), Port]),
- {stop, normal, S};
+ {stop, normal, State};
-handle_info(Info, S) ->
+handle_info(Info, State) ->
lager:debug("~p: Unhandled info: ~p~n", [self(), Info]),
- {noreply, S}.
+ {noreply, State}.
+
+terminate(Reason, #state{socket = Sock, tokname = TokName}) ->
+ ok = gen_tcp:close(Sock),
+
+ %% Let manager know, so that the client can be stopped. We don't
+ %% want to risk that another P11 app uses it.
+ p11p_manager:server_event(server_gone, TokName),
-terminate(Reason, #state{socket = Sock, tokname = TokName, client = Client}) ->
- gen_tcp:close(Sock),
- p11p_manager:client_event(client_gone, [TokName, Client]),
lager:debug("~p: terminated with reason ~p", [self(), Reason]),
ignored.
@@ -122,13 +156,42 @@ code_change(_OldVersion, State, _Extra) ->
{ok, State}.
%% Private functions.
-p11_client_data(#state{client = Client, recv_count = Recv} = S, MsgIn,
- DataIn) ->
+request_in(S, MsgIn, DataIn) ->
case p11p_rpc:parse(MsgIn, DataIn) of
{needmore, Msg} ->
- S#state{msg = Msg};
+ S#state{req_in = Msg};
{done, Msg} ->
- {ok, _BytesSent} = p11p_client:request(Client, Msg),
- S#state{msg = p11p_rpc:new(Msg#p11rpc_msg.buffer),
- recv_count = Recv + 1}
+ lager:debug("~p: -> ~s", [self(), p11p_rpc:dump(Msg)]),
+ case p11p_client:request(S#state.client, Msg) of
+ ack ->
+ lager:debug("~p: acking request", [self()]),
+ Resp = p11p_rpc:msg_ok(p11p_rpc:call_code(Msg)),
+ {ok, _} = send_response(S#state.socket,
+ p11p_rpc:serialise(Resp),
+ S#state.send_count),
+ S#state{req_in = p11p_rpc:new(Msg#p11rpc_msg.buffer),
+ send_count = S#state.send_count + 1};
+ nack ->
+ lager:debug("~p: nacking request", [self()]),
+ Resp = p11p_rpc:msg_error(p11p_rpc:call_code(Msg),
+ ?CKR_DEVICE_ERROR),
+ {ok, _} = send_response(S#state.socket,
+ p11p_rpc:serialise(Resp),
+ S#state.send_count),
+ S#state{req_in = p11p_rpc:new(Msg#p11rpc_msg.buffer),
+ send_count = S#state.send_count + 1};
+ {ok, _BytesSent} ->
+ S#state{req_out = Msg,
+ req_in = p11p_rpc:new(Msg#p11rpc_msg.buffer),
+ recv_count = S#state.recv_count + 1}
+ end
end.
+
+send_response(Sock, Inbuf, Sent) ->
+ Outbuf = case Sent of
+ 0 -> <<?RPC_VERSION:8, Inbuf/binary>>;
+ _ -> Inbuf
+ end,
+ lager:debug("~p: sending ~B octets as response", [self(), size(Outbuf)]),
+ ok = gen_tcp:send(Sock, Outbuf),
+ {ok, size(Outbuf)}.