diff options
Diffstat (limited to 'p11p-daemon/src/p11p_server.erl')
-rw-r--r-- | p11p-daemon/src/p11p_server.erl | 165 |
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)}. |