% Copyright (c) Camden Dixie O'Brien % SPDX-License-Identifier: AGPL-3.0-only -module(tcp_server). -behaviour(gen_server). -export([start_link/2]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, {socket, acceptor}). start_link(Port, CertDir) -> gen_server:start_link({local, ?MODULE}, ?MODULE, [Port, CertDir], []). init([Port, CertDir]) -> TcpOpts = [binary, inet6, {active, false}, {reuseaddr, true}], SslOpts = [{certfile, filename:join([CertDir, "server.pem"])}, {cacertfile, filename:join([CertDir, "ca.pem"])}, {verify, verify_peer}, {fail_if_no_peer_cert, true}], {ok, Socket} = ssl:listen(Port, TcpOpts ++ SslOpts), self() ! accept, {ok, #state{socket = Socket}}. handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info(accept, State = #state{socket = Socket}) -> case ssl:transport_accept(Socket) of {ok, TlsSocket} -> self() ! {handshake, TlsSocket}, self() ! accept; {error, closed} -> ok end, {noreply, State}; handle_info({handshake, TlsSocket}, State) -> case ssl:handshake(TlsSocket, 5000) of {ok, ClientSocket} -> {ok, Pid} = session_sup:start_session(ClientSocket), ok = ssl:controlling_process(ClientSocket, Pid); {error, _Reason} -> ok end, {noreply, State}; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, #state{socket = Socket}) -> ssl:close(Socket), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}.