%%% Copyright (c) 2019, Sunet. %%% See LICENSE for licensing information. %% 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). -include("p11p_rpc.hrl"). %% API. -export([start_link/1]). -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, code_change/3]). %% Records and types. -record(state, { 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. -spec start_link(gen_tcp:socket()) -> {ok, pid()} | {error, term()}. start_link(Args) -> gen_server:start_link(?MODULE, Args, []). -spec reply(pid(), p11rpc_msg()) -> {ok, non_neg_integer()}. 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 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}}. %% 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]), {reply, unhandled, S}. %% Wait for new connection. handle_cast(accept, State = #state{tokname = TokName, socket = ListenSocket}) -> %% 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 the new socket in state. p11p_server_sup:start_server([TokName, ListenSocket]), {noreply, State#state{socket = Sock}}; {error, timeout} -> gen_server:cast(self(), accept), {noreply, State}; {error, closed} -> lager:debug("~p: listening socket closed", [self()]), {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 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]), <> = DataIn, case RPCVersion of ?RPC_VERSION -> 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, State} end; %% 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}, State) -> lager:debug("~p: socket ~p closed", [self(), Port]), {stop, normal, State}; handle_info(Info, State) -> lager:debug("~p: Unhandled info: ~p~n", [self(), Info]), {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), lager:debug("~p: terminated with reason ~p", [self(), Reason]), ignored. code_change(_OldVersion, State, _Extra) -> {ok, State}. %% Private functions. request_in(S, MsgIn, DataIn) -> case p11p_rpc:parse(MsgIn, DataIn) of {needmore, Msg} -> S#state{req_in = Msg}; {done, Msg} -> 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 -> <>; _ -> Inbuf end, lager:debug("~p: sending ~B octets as response", [self(), size(Outbuf)]), ok = gen_tcp:send(Sock, Outbuf), {ok, size(Outbuf)}.