%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1999-2010. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% %%% Purpose : SSL broker -module(ssl_broker). -behaviour(gen_server). %% This module implements brokers for ssl. A broker is either a connector, %% an acceptor, or a listener. All brokers are children to ssl_broker_sup, %% to which they are linked. Each broker is also linked to ssl_server, and %% to its client. %% %% The purpose of the broker is to set up SSL connections through calls to %% ssl_server and gen_tcp. All control information goes to the server, %% while all data is exchanged directly between gen_tcp and the port program %% of the ssl_server. %% %% A broker is created by a call to start_broker/3 (do *not* use start_link/4 %% - it is for ssl_broker_sup to call that one), and then call listen/3, %% accept/4, or connect/5. %% %% The following table shows all functions dependency on status, active %% mode etc. %% %% Permitted status transitions: %% %% nil -> open %% open -> closing | closed (termination) %% closing -> closed (termination) %% %% We are rather sloppy about nil, and consider open/closing == !closed, %% open/closing/closed === any etc. %% %% %% function/ valid mode new %% message status state %% %% calls %% ----- %% recv open passive ditto %% send open any ditto %% transport_accept nil any open %% ssl_accept nil any open %% connect nil any open %% listen nil any open %% peername open/closing any ditto %% setopts open/closing any ditto %% getopts open/closing any ditto %% sockname open/closing any ditto %% peercert open/closing any ditto %% inhibit any any ditto %% release any any ditto %% close any any closed (1) %% %% info %% ---- %% tcp open active ditto %% tcp_closed open | closing active closing %% tcp_error open | closing active closing %% %% (1) We just terminate. %% %% TODO %% %% XXX Timeouts are not checked (integer or infinity). %% %% XXX The collector thing is not gen_server compliant. %% %% NOTE: There are three different "modes": (a) passive or active mode, %% specified as {active, bool()}, and (b) list or binary mode, specified %% as {mode, list | binary}, and (c) encrypted or clear mode %% -include("ssl_int.hrl"). %% External exports -export([start_broker/1, start_broker/2, start_link/3, transport_accept/3, ssl_accept/2, close/1, connect/5, connection_info/1, controlling_process/2, listen/3, recv/3, send/2, getopts/2, getopts/3, setopts/2, sockname/1, peername/1, peercert/1]). -export([listen_prim/5, connect_prim/8, transport_accept_prim/5, ssl_accept_prim/6]). %% Internal exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3, terminate/2, collector_init/1]). -include("ssl_broker_int.hrl"). %% start_broker(Type) -> {ok, Pid} | {error, Reason} %% start_broker(Type, GenOpts) -> {ok, Pid} | {error, Reason} %% Type = accept | connect | listen %% GenOpts = /standard gen_server options/ %% %% This is the function to be called from the interface module ssl.erl. %% Links to the caller. %% start_broker(Type) -> start_broker(Type, []). start_broker(Type, GenOpts) -> case lists:member(Type, [listener, acceptor, connector]) of true -> case supervisor:start_child(ssl_broker_sup, [self(), Type, GenOpts]) of {ok, Pid} -> link(Pid), {ok, Pid}; {error, Reason} -> {error, Reason} end; false -> {error, ebrokertype} end. %% start_link(Client, Type, GenOpts) -> {ok, Pid} | {error, Reason} %% %% Type = accept | connect | listen %% GenOpts = /standard gen_server options/ %% %% This function is called by ssl_broker_sup and must *not* be called %% from an interface module (ssl.erl). start_link(Client, Type, GenOpts) -> gen_server:start_link(?MODULE, [Client, Type], GenOpts). %% accept(Pid, ListenSocket, Timeout) -> {ok, Socket} | {error, Reason} %% %% Types: Pid = pid() of acceptor %% ListenSocket = Socket = sslsocket() %% Timeout = timeout() %% %% accept(Pid, ListenSocket, Timeout) %% when is_pid(Pid), is_record(ListenSocket, sslsocket) -> %% Req = {accept, self(), ListenSocket, Timeout}, %% gen_server:call(Pid, Req, infinity). %% transport_accept(Pid, ListenSocket, Timeout) -> {ok, Socket} | %% {error, Reason} %% %% Types: Pid = pid() of acceptor %% ListenSocket = Socket = sslsocket() %% Timeout = timeout() %% transport_accept(Pid, #sslsocket{} = ListenSocket, Timeout) when is_pid(Pid) -> Req = {transport_accept, self(), ListenSocket, Timeout}, gen_server:call(Pid, Req, infinity). %% ssl_accept(Pid, Socket, Timeout) -> {ok, Socket} | {error, Reason} %% %% Types: Pid = pid() of acceptor %% ListenSocket = Socket = sslsocket() %% Timeout = timeout() %% ssl_accept(#sslsocket{pid = Pid} = Socket, Timeout) -> Req = {ssl_accept, self(), Socket, Timeout}, gen_server:call(Pid, Req, infinity). %% close(Socket) -> ok | {error, Reason} %% %% Types: Socket = sslsocket() | pid() %% close(#sslsocket{pid = Pid}) -> close(Pid); close(Pid) when is_pid(Pid) -> gen_server:call(Pid, {close, self()}, infinity). %% connect(Pid, Address, Port, Opts, Timeout) -> {ok, Socket} | {error, Reason} %% %% Types: Pid = pid() of connector %% Address = string() | {byte(), byte(), byte(), byte()} %% Port = int() %% Opts = options() %% Timeout = timeout() %% Socket = sslsocket() %% connect(Pid, Address, Port, Opts, Timeout) when is_pid(Pid), is_list(Opts) -> case are_connect_opts(Opts) of true -> Req = {connect, self(), Address, Port, Opts, Timeout}, gen_server:call(Pid, Req, infinity); false -> {error, eoptions} end. %% %% connection_info(Socket) -> {ok, {Protocol, Cipher} | {error, Reason} %% connection_info(#sslsocket{pid = Pid}) -> Req = {connection_info, self()}, gen_server:call(Pid, Req, infinity). %% controlling_process(Socket, NewOwner) -> ok | {error, Reason} controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(NewOwner) -> case gen_server:call(Pid, {inhibit_msgs, self()}, infinity) of ok -> transfer_messages(Pid, NewOwner), gen_server:call(Pid, {release_msgs, self(), NewOwner}, infinity); Error -> Error end. %% listen(Pid, Port, Opts) -> {ok, ListenSocket} | {error, Reason} %% %% Types: Pid = pid() of listener %% Port = int() %% Opts = options() %% ListenSocket = sslsocket() %% listen(Pid, Port, Opts) when is_pid(Pid) -> case are_listen_opts(Opts) of true -> Req = {listen, self(), Port, Opts}, gen_server:call(Pid, Req, infinity); false -> {error, eoptions} end. %% %% peername(Socket) -> {ok, {Address, Port}} | {error, Reason} %% peername(#sslsocket{pid = Pid}) -> Req = {peername, self()}, gen_server:call(Pid, Req, infinity). %% recv(Socket, Length, Timeout) -> {ok, Data} | {error, Reason} %% %% Types: Socket = sslsocket() %% Length = Timeout = integer() %% Data = bytes() | binary() %% recv(#sslsocket{pid = Pid}, Length, Timeout) -> Req = {recv, self(), Length, Timeout}, gen_server:call(Pid, Req, infinity). %% send(Socket, Data) -> ok | {error, Reason} %% %% Types: Socket = sslsocket() %% send(#sslsocket{pid = Pid}, Data) -> gen_server:call(Pid, {send, self(), Data}, infinity). %% getopts(Socket, OptTags) -> {ok, Opts} | {error, einval} %% %% Types: Pid = pid() of broker %% Timeout = timeout() %% OptTags = option_tags() %% Opts = options() %% getopts(Socket, OptTags) -> getopts(Socket, OptTags, infinity). getopts(#sslsocket{pid = Pid}, OptTags, Timeout) when is_list(OptTags) -> Req = {getopts, self(), OptTags}, gen_server:call(Pid, Req, Timeout). %% %% setopts(Socket, Opts) -> ok | {error, Reason} %% setopts(#sslsocket{pid = Pid}, Opts) -> Req = {setopts, self(), Opts}, gen_server:call(Pid, Req, infinity). %% %% sockname(Socket) -> {ok, {Address, Port}} | {error, Reason} %% sockname(#sslsocket{pid = Pid}) -> Req = {sockname, self()}, gen_server:call(Pid, Req, infinity). %% %% peercert(Socket) -> {ok, Cert} | {error, Reason} %% peercert(#sslsocket{pid = Pid}) -> Req = {peercert, self()}, gen_server:call(Pid, Req, infinity). %% %% INIT %% %% init %% init([Client, Type]) -> process_flag(trap_exit, true), link(Client), Debug = case application:get_env(ssl, edebug) of {ok, true} -> true; _ -> case application:get_env(ssl, debug) of {ok, true} -> true; _ -> os:getenv("ERL_SSL_DEBUG") =/= false end end, Server = whereis(ssl_server), if is_pid(Server) -> link(Server), debug1(Debug, Type, "in start, client = ~w", [Client]), {ok, #st{brokertype = Type, server = Server, client = Client, collector = Client, debug = Debug}}; true -> {stop, no_ssl_server} end. %% %% HANDLE CALL %% %% recv - passive mode %% handle_call({recv, Client, Length, Timeout}, _From, #st{active = false, proxysock = Proxysock, status = Status} = St) -> debug(St, "recv: client = ~w~n", [Client]), if Status =/= open -> {reply, {error, closed}, St}; true -> case gen_tcp:recv(Proxysock, Length, Timeout) of {ok, Data} -> {reply, {ok, Data}, St}; {error, timeout} -> {reply, {error, timeout}, St}; {error, Reason} -> {reply, {error, Reason}, St#st{status = closing}} end end; %% send %% handle_call({send, Client, Data}, _From, St) -> debug(St, "send: client = ~w~n", [Client]), if St#st.status =/= open -> {reply, {error, closed}, St}; true -> case gen_tcp:send(St#st.proxysock, Data) of ok -> {reply, ok, St}; {error, _Reason} -> {reply, {error, closed}, St#st{status = closing}} end end; %% transport_accept %% %% Client = pid of client %% ListenSocket = sslsocket() %% handle_call({transport_accept, Client, ListenSocket, Timeout}, _From, St) -> debug(St, "transport_accept: client = ~w, listensocket = ~w~n", [Client, ListenSocket]), case getopts(ListenSocket, tcp_listen_opt_tags(), ?DEF_TIMEOUT) of {ok, LOpts} -> case transport_accept_prim( ssl_server, ListenSocket#sslsocket.fd, LOpts, Timeout, St) of {ok, ThisSocket, NSt} -> {reply, {ok, ThisSocket}, NSt}; {error, Reason, St} -> What = what(Reason), {stop, normal, {error, What}, St} end; {error, Reason} -> What = what(Reason), {stop, normal, {error, What}, St} end; %% ssl_accept %% %% Client = pid of client %% ListenSocket = sslsocket() %% handle_call({ssl_accept, Client, Socket, Timeout}, _From, St) -> debug(St, "ssl_accept: client = ~w, socket = ~w~n", [Client, Socket]), case ssl_accept_prim(ssl_server, gen_tcp, Client, St#st.opts, Timeout, St#st{thissock=Socket}) of {ok, Socket, NSt} -> {reply, ok, NSt}; {error, Reason, St} -> What = what(Reason), {stop, normal, {error, What}, St} end; %% connect %% %% Client = client pid %% Address = hostname | ipstring | IP %% Port = integer() %% Opts = options() %% handle_call({connect, Client, Address, Port, Opts, Timeout}, _From, St) -> debug(St, "connect: client = ~w, address = ~p, port = ~w~n", [Client, Address, Port]), case connect_prim(ssl_server, gen_tcp, Client, Address, Port, Opts, Timeout, St) of {ok, Res, NSt} -> {reply, {ok, Res}, NSt}; {error, Reason, NSt} -> What = what(Reason), {stop, normal, {error, What}, NSt} end; %% connection_info %% handle_call({connection_info, Client}, _From, St) -> debug(St, "connection_info: client = ~w~n", [Client]), Reply = ssl_server:connection_info(St#st.fd), {reply, Reply, St}; %% close from client %% handle_call({close, Client}, _From, St) -> debug(St, "close: client = ~w~n", [Client]), %% Terminate {stop, normal, ok, St#st{status = closed}}; %% listen %% %% Client = pid of client %% Port = int() %% Opts = options() %% handle_call({listen, Client, Port, Opts}, _From, St) -> debug(St, "listen: client = ~w, port = ~w~n", [Client, Port]), case listen_prim(ssl_server, Client, Port, Opts, St) of {ok, Res, NSt} -> {reply, {ok, Res}, NSt}; {error, Reason, NSt} -> What = what(Reason), {stop, normal, {error, What}, NSt} end; %% peername %% handle_call({peername, Client}, _From, St) -> debug(St, "peername: client = ~w~n", [Client]), Reply = case ssl_server:peername(St#st.fd) of {ok, {Address, Port}} -> {ok, At} = inet_parse:ipv4_address(Address), {ok, {At, Port}}; Error -> Error end, {reply, Reply, St}; %% setopts %% handle_call({setopts, Client, Opts0}, _From, St0) -> debug(St0, "setopts: client = ~w~n", [Client]), OptsOK = case St0#st.brokertype of listener -> are_opts(fun is_tcp_listen_opt/1, Opts0); acceptor -> are_opts(fun is_tcp_accept_opt/1, Opts0); connector -> are_opts(fun is_tcp_connect_opt/1, Opts0) end, if OptsOK =:= false -> {reply, {error, eoptions}, St0}; true -> Opts1 = lists:keydelete(nodelay, 1, Opts0), case inet:setopts(St0#st.proxysock, Opts1) of ok -> Opts2 = replace_opts(Opts1, St0#st.opts), Active = get_active(Opts2), St2 = St0#st{opts = Opts2, active = Active}, case get_nodelay(Opts0) of empty -> {reply, ok, St2}; Bool -> case setnodelay(ssl_server, St0, Bool) of ok -> Opts3 = replace_opts([{nodelay, Bool}], Opts2), St3 = St0#st{opts = Opts3, active = Active}, {reply, ok, St3}; {error, Reason} -> {reply, {error, Reason}, St2} end end; {error, Reason} -> {reply, {error, Reason}, St0} end end; %% sockname %% handle_call({sockname, Client}, _From, St) -> debug(St, "sockname: client = ~w~n", [Client]), Reply = case ssl_server:sockname(St#st.fd) of {ok, {Address, Port}} -> {ok, At} = inet_parse:ipv4_address(Address), {ok, {At, Port}}; Error -> Error end, {reply, Reply, St}; %% peercert %% handle_call({peercert, Client}, _From, St) -> debug(St, "peercert: client = ~w~n", [Client]), Reply = ssl_server:peercert(St#st.fd), {reply, Reply, St}; %% inhibit msgs %% handle_call({inhibit_msgs, Client}, _From, #st{client = Client} = St) -> debug(St, "inhibit_msgs: client = ~w~n", [Client]), {ok, Collector} = start_collector(), {reply, ok, St#st{collector = Collector}}; %% release msgs %% handle_call({release_msgs, Client, NewClient}, _From, #st{client = Client, collector = Collector} = St) -> debug(St, "release_msgs: client = ~w~n", [Client]), unlink(Client), link(NewClient), release_collector(Collector, NewClient), NSt = St#st{client = NewClient, collector = NewClient}, {reply, ok, NSt}; %% getopts %% handle_call({getopts, Client, OptTags}, _From, St) -> debug(St, "getopts: client = ~w~n", [Client]), Reply = case are_opt_tags(St#st.brokertype, OptTags) of true -> {ok, extract_opts(OptTags, St#st.opts)}; _ -> {error, einval} end, {reply, Reply, St}; %% bad call %% handle_call(Request, _From, St) -> debug(St, "++++ ssl_broker: bad call: ~w~n", [Request]), {reply, {error, {badcall, Request}}, St}. %% %% HANDLE CAST %% handle_cast(Request, St) -> debug(St, "++++ ssl_broker: bad cast: ~w~n", [Request]), {stop, {error, {badcast, Request}}, St}. %% %% HANDLE INFO %% %% tcp - active mode %% %% The collector is different from client only during change of %% controlling process. %% handle_info({tcp, Socket, Data}, #st{active = Active, collector = Collector, status = open, proxysock = Socket, thissock = Thissock} = St) when Active =/= false -> debug(St, "tcp: socket = ~w~n", [Socket]), Msg = {ssl, Thissock, Data}, Collector ! Msg, if Active =:= once -> {noreply, St#st{active = false}}; true -> {noreply, St} end; %% tcp_closed - from proxy socket, active mode %% %% handle_info({tcp_closed, Socket}, #st{active = Active, collector = Collector, proxysock = Socket, thissock = Thissock} = St) when Active =/= false -> debug(St, "tcp_closed: socket = ~w~n", [Socket]), Msg = {ssl_closed, Thissock}, Collector ! Msg, if Active =:= once -> {noreply, St#st{status = closing, active = false}}; true -> {noreply, St#st{status = closing}} end; %% tcp_error - from proxy socket, active mode %% %% handle_info({tcp_error, Socket, Reason}, #st{active = Active, collector = Collector, proxysock = Socket} = St) when Active =/= false -> debug(St, "tcp_error: socket = ~w, reason = ~w~n", [Socket, Reason]), Msg = {ssl_error, Socket, Reason}, Collector ! Msg, if Active =:= once -> {noreply, St#st{status = closing, active = false}}; true -> {noreply, St#st{status = closing}} end; %% EXIT - from client %% %% handle_info({'EXIT', Client, Reason}, #st{client = Client} = St) -> debug(St, "exit client: client = ~w, reason = ~w~n", [Client, Reason]), {stop, normal, St#st{status = closed}}; % do not make noise %% EXIT - from server %% %% handle_info({'EXIT', Server, Reason}, #st{server = Server} = St) -> debug(St, "exit server: reason = ~w~n", [Reason]), {stop, Reason, St}; %% handle info catch all %% handle_info(Info, St) -> debug(St, " bad info: ~w~n", [Info]), {stop, {error, {badinfo, Info}}, St}. %% terminate %% %% terminate(Reason, St) -> debug(St, "in terminate reason: ~w, state: ~w~n", [Reason, St]), ok. %% code_change %% %% code_change(_OldVsn, State, _Extra) -> {ok, State}. %% %% Primitive interface %% listen_prim(ServerName, Client, Port, Opts, St) -> LOpts = get_tcp_listen_opts(Opts), SSLOpts = get_ssl_opts(Opts), FlagStr =mk_ssl_optstr(SSLOpts), BackLog = get_backlog(LOpts), IP = get_ip(LOpts), case ssl_server:listen_prim(ServerName, IP, Port, FlagStr, BackLog) of {ok, ListenFd, _Port0} -> ThisSocket = #sslsocket{fd = ListenFd, pid = self()}, StOpts = add_default_tcp_listen_opts(LOpts) ++ add_default_ssl_opts(SSLOpts), NSt = St#st{fd = ListenFd, active = get_active(LOpts), % irrelevant for listen opts = StOpts, thissock = ThisSocket, status = open}, debug(St, "listen: ok: client = ~w, listenfd = ~w~n", [Client, ListenFd]), {ok, ThisSocket, NSt}; {error, Reason} -> {error, Reason, St} end. connect_prim(ServerName, TcpModule, Client, FAddress, FPort, Opts, Timeout, St) -> COpts = get_tcp_connect_opts(Opts), SSLOpts = get_ssl_opts(Opts), FlagStr = mk_ssl_optstr(SSLOpts), case inet:getaddr(FAddress, inet) of {ok, FIP} -> %% Timeout is gen_server timeout - hence catch LIP = get_ip(COpts), LPort = get_port(COpts), case (catch ssl_server:connect_prim(ServerName, LIP, LPort, FIP, FPort, FlagStr, Timeout)) of {ok, Fd, ProxyPort} -> case connect_proxy(ServerName, TcpModule, Fd, ProxyPort, COpts, Timeout) of {ok, Socket} -> ThisSocket = #sslsocket{fd = Fd, pid = self()}, StOpts = add_default_tcp_connect_opts(COpts) ++ add_default_ssl_opts(SSLOpts), NSt = St#st{fd = Fd, active = get_active(COpts), opts = StOpts, thissock = ThisSocket, proxysock = Socket, status = open}, case get_nodelay(COpts) of true -> setnodelay(ServerName, NSt, true); _ -> ok end, debug(St, "connect: ok: client = ~w, fd = ~w~n", [Client, Fd]), {ok, ThisSocket, NSt}; {error, Reason} -> {error, Reason, St} end; {'EXIT', Reason} -> {error, Reason, St}; {error, Reason} -> {error, Reason, St} end; {error, Reason} -> {error, Reason, St} end. transport_accept_prim(ServerName, ListenFd, LOpts, Timeout, St) -> AOpts = get_tcp_accept_opts(LOpts), FlagStr = "", %% Timeout is gen_server timeout - hence catch. case (catch ssl_server:transport_accept_prim(ServerName, ListenFd, FlagStr, Timeout)) of {ok, Fd, ProxyPort} -> ThisSocket = #sslsocket{fd = Fd, pid = self()}, NSt = St#st{fd = Fd, active = get_active(AOpts), opts = AOpts, thissock = ThisSocket, proxyport = ProxyPort, encrypted = false}, debug(St, "transport_accept: ok: fd = ~w~n", [Fd]), {ok, ThisSocket, NSt}; {'EXIT', Reason} -> debug(St, "transport_accept: EXIT: Reason = ~w~n", [Reason]), {error, Reason, St}; {error, Reason} -> debug(St, "transport_accept: error: Reason = ~w~n", [Reason]), {error, Reason, St} end. ssl_accept_prim(ServerName, TcpModule, Client, LOpts, Timeout, St) -> FlagStr = [], SSLOpts = [], AOpts = get_tcp_accept_opts(LOpts), %% Timeout is gen_server timeout - hence catch. debug(St, "ssl_accept_prim: self() ~w Client ~w~n", [self(), Client]), Socket = St#st.thissock, Fd = Socket#sslsocket.fd, A = (catch ssl_server:ssl_accept_prim(ServerName, Fd, FlagStr, Timeout)), debug(St, "ssl_accept_prim: ~w~n", [A]), case A of ok -> B = connect_proxy(ServerName, TcpModule, Fd, St#st.proxyport, AOpts, Timeout), debug(St, "ssl_accept_prim: connect_proxy ~w~n", [B]), case B of {ok, Socket2} -> StOpts = add_default_tcp_accept_opts(AOpts) ++ add_default_ssl_opts(SSLOpts), NSt = St#st{opts = StOpts, proxysock = Socket2, encrypted = true, status = open}, case get_nodelay(AOpts) of true -> setnodelay(ServerName, NSt, true); _ -> ok end, debug(St, "transport_accept: ok: client = ~w, fd = ~w~n", [Client, Fd]), {ok, St#st.thissock, NSt}; {error, Reason} -> {error, Reason, St} end; {'EXIT', Reason} -> {error, Reason, St}; {error, Reason} -> {error, Reason, St} end. %% %% LOCAL FUNCTIONS %% %% %% connect_proxy(Fd, ProxyPort, TOpts, Timeout) -> {ok, Socket} | %% {error, Reason} %% connect_proxy(ServerName, TcpModule, Fd, ProxyPort, TOpts, Timeout) -> case TcpModule:connect({127, 0, 0, 1}, ProxyPort, TOpts, Timeout) of {ok, Socket} -> {ok, Port} = inet:port(Socket), A = ssl_server:proxy_join_prim(ServerName, Fd, Port), case A of ok -> {ok, Socket}; Error -> Error end; Error -> Error end. setnodelay(ServerName, St, Bool) -> case ssl_server:setnodelay_prim(ServerName, St#st.fd, Bool) of ok -> case inet:setopts(St#st.proxysock, [{nodelay, Bool}]) of ok -> ok; {error, Reason} -> {error, Reason} end; {error, Reason} -> {error, Reason} end. %% %% start_collector() %% %% A collector is a little process that keeps messages during change of %% controlling process. %% XXX This is not gen_server compliant :-(. %% start_collector() -> Pid = spawn_link(?MODULE, collector_init, [self()]), {ok, Pid}. %% %% release_collector(Collector, NewOwner) %% release_collector(Collector, NewOwner) -> Collector ! {release, self(), NewOwner}, receive %% Reap collector {'EXIT', Collector, normal} -> ok end. %% %% collector_init(Broker) -> void() %% collector_init(Broker) -> receive {release, Broker, NewOwner} -> transfer_messages(Broker, NewOwner) end. %% %% transfer_messages(Pid, NewOwner) -> void() %% transfer_messages(Pid, NewOwner) -> receive {ssl, Sock, Data} -> NewOwner ! {ssl, Sock, Data}, transfer_messages(Pid, NewOwner); {ssl_closed, Sock} -> NewOwner ! {ssl_closed, Sock}, transfer_messages(Pid, NewOwner); {ssl_error, Sock, Reason} -> NewOwner ! {ssl_error, Sock, Reason}, transfer_messages(Pid, NewOwner) after 0 -> ok end. %% %% debug(St, Format, Args) -> void() - printouts %% debug(St, Format, Args) -> debug1(St#st.debug, St#st.brokertype, Format, Args). debug1(true, Type, Format0, Args) -> {_MS, S, MiS} = erlang:now(), Secs = S rem 100, MiSecs = MiS div 1000, Format = "++++ ~3..0w:~3..0w ssl_broker (~w)[~w]: " ++ Format0, io:format(Format, [Secs, MiSecs, self(), Type| Args]); debug1(_, _, _, _) -> ok. %% %% what(Reason) -> What %% what(Reason) when is_atom(Reason) -> Reason; what({'EXIT', Reason}) -> what(Reason); what({What, _Where}) when is_atom(What) -> What; what(Reason) -> Reason. %% %% OPTIONS %% %% Note that `accept' has no options when invoked, but get all its options %% by inheritance from `listen'. %% are_opt_tags(listener, OptTags) -> is_subset(OptTags, listen_opt_tags()); are_opt_tags(acceptor, OptTags) -> is_subset(OptTags, accept_opt_tags()); are_opt_tags(connector, OptTags) -> is_subset(OptTags, connect_opt_tags()). listen_opt_tags() -> tcp_listen_opt_tags() ++ ssl_opt_tags(). accept_opt_tags() -> tcp_gen_opt_tags(). connect_opt_tags() -> tcp_gen_opt_tags() ++ ssl_opt_tags(). tcp_listen_opt_tags() -> tcp_gen_opt_tags() ++ tcp_listen_only_opt_tags(). tcp_gen_opt_tags() -> %% All except `reuseaddr' and `deliver'. [nodelay, active, packet, mode, header]. tcp_listen_only_opt_tags() -> [ip, backlog]. ssl_opt_tags() -> %% XXX Should remove cachetimeout. [verify, depth, certfile, password, cacertfile, ciphers, cachetimeout]. %% Options %% %% are_*_opts(Opts) -> boolean() %% are_connect_opts(Opts) -> are_opts(fun is_connect_opt/1, Opts). are_listen_opts(Opts) -> are_opts(fun is_listen_opt/1, Opts). are_opts(F, Opts) -> lists:all(F, transform_opts(Opts)). %% %% get_*_opts(Opts) -> Value %% get_tcp_accept_opts(Opts) -> [O || O <- transform_opts(Opts), is_tcp_accept_opt(O)]. get_tcp_connect_opts(Opts) -> [O || O <- transform_opts(Opts), is_tcp_connect_opt(O)]. get_tcp_listen_opts(Opts) -> [O || O <- transform_opts(Opts), is_tcp_listen_opt(O)]. get_ssl_opts(Opts) -> [O || O <- transform_opts(Opts), is_ssl_opt(O)]. get_active(Opts) -> get_tagged_opt(active, Opts, true). get_backlog(Opts) -> get_tagged_opt(backlog, Opts, ?DEF_BACKLOG). get_ip(Opts) -> get_tagged_opt(ip, Opts, {0, 0, 0, 0}). get_port(Opts) -> get_tagged_opt(port, Opts, 0). get_nodelay(Opts) -> get_tagged_opt(nodelay, Opts, empty). %% %% add_default_*_opts(Opts) -> NOpts %% add_default_tcp_accept_opts(Opts) -> add_default_opts(Opts, default_tcp_accept_opts()). add_default_tcp_connect_opts(Opts) -> add_default_opts(Opts, default_tcp_connect_opts()). add_default_tcp_listen_opts(Opts) -> add_default_opts(Opts, default_tcp_listen_opts()). add_default_ssl_opts(Opts) -> add_default_opts(Opts, default_ssl_opts()). add_default_opts(Opts, DefOpts) -> TOpts = transform_opts(Opts), TOpts ++ [DP || {DTag, _DVal} = DP <- DefOpts, not lists:keymember(DTag, 1, TOpts)]. default_tcp_accept_opts() -> [O || O <- default_opts(), is_tcp_accept_opt(O)]. default_tcp_connect_opts() -> [O || O <- default_opts(), is_tcp_connect_opt(O)]. default_tcp_listen_opts() -> [O || O <- default_opts(), is_tcp_listen_opt(O)]. default_ssl_opts() -> [O || O <- default_opts(), is_ssl_opt(O)]. default_opts() -> [{mode, list}, {packet, 0}, {nodelay, false}, {active, true}, {backlog, ?DEF_BACKLOG}, {ip, {0, 0, 0, 0}}, {verify, 0}, {depth, 1}]. %% Transform from old to new options, and also from old gen_tcp %% options to new ones. All returned options are tagged options. %% transform_opts(Opts) -> lists:flatmap(fun transform_opt/1, Opts). transform_opt(binary) -> [{mode, binary}]; transform_opt(list) -> [{mode, list}]; transform_opt({packet, raw}) -> [{packet, 0}]; transform_opt(raw) -> []; transform_opt(Opt) -> [Opt]. %% NOTE: The is_*_opt/1 functions must be applied on transformed options %% only. is_connect_opt(Opt) -> is_tcp_connect_opt(Opt) or is_ssl_opt(Opt). is_listen_opt(Opt) -> is_tcp_listen_opt(Opt) or is_ssl_opt(Opt). is_tcp_accept_opt(Opt) -> is_tcp_gen_opt(Opt). is_tcp_connect_opt(Opt) -> is_tcp_gen_opt(Opt) or is_tcp_connect_only_opt(Opt). is_tcp_listen_opt(Opt) -> is_tcp_gen_opt(Opt) or is_tcp_listen_only_opt(Opt). %% General options supported by gen_tcp: All except `reuseaddr' and %% `deliver'. is_tcp_gen_opt({mode, list}) -> true; is_tcp_gen_opt({mode, binary}) -> true; is_tcp_gen_opt({header, Sz}) when is_integer(Sz), 0 =< Sz -> true; is_tcp_gen_opt({packet, Sz}) when is_integer(Sz), 0 =< Sz, Sz =< 4-> true; is_tcp_gen_opt({packet, sunrm}) -> true; is_tcp_gen_opt({packet, asn1}) -> true; is_tcp_gen_opt({packet, cdr}) -> true; is_tcp_gen_opt({packet, fcgi}) -> true; is_tcp_gen_opt({packet, line}) -> true; is_tcp_gen_opt({packet, tpkt}) -> true; is_tcp_gen_opt({packet, http}) -> true; is_tcp_gen_opt({packet, httph}) -> true; is_tcp_gen_opt({nodelay, true}) -> true; is_tcp_gen_opt({nodelay, false}) -> true; is_tcp_gen_opt({active, true}) -> true; is_tcp_gen_opt({active, false}) -> true; is_tcp_gen_opt({active, once}) -> true; is_tcp_gen_opt({keepalive, true}) -> true; is_tcp_gen_opt({keepalive, false}) -> true; is_tcp_gen_opt({ip, Addr}) -> is_ip_address(Addr); is_tcp_gen_opt(_Opt) -> false. is_tcp_listen_only_opt({backlog, Size}) when is_integer(Size), 0 =< Size -> true; is_tcp_listen_only_opt({reuseaddr, Bool}) when is_boolean(Bool) -> true; is_tcp_listen_only_opt(_Opt) -> false. is_tcp_connect_only_opt({port, Port}) when is_integer(Port), 0 =< Port -> true; is_tcp_connect_only_opt(_Opt) -> false. %% SSL options is_ssl_opt({verify, Code}) when 0 =< Code, Code =< 2 -> true; is_ssl_opt({depth, Depth}) when 0 =< Depth -> true; is_ssl_opt({certfile, String}) -> is_string(String); is_ssl_opt({keyfile, String}) -> is_string(String); is_ssl_opt({password, String}) -> is_string(String); is_ssl_opt({cacertfile, String}) -> is_string(String); is_ssl_opt({ciphers, String}) -> is_string(String); is_ssl_opt({cachetimeout, Timeout}) when Timeout >= 0 -> true; is_ssl_opt(_Opt) -> false. %% Various types is_string(String) when is_list(String) -> lists:all(fun (C) when is_integer(C), 0 =< C, C =< 255 -> true; (_C) -> false end, String); is_string(_) -> false. is_ip_address(Addr) when tuple_size(Addr) =:= 4 -> is_string(tuple_to_list(Addr)); is_ip_address(Addr) when is_list(Addr) -> is_string(Addr); is_ip_address(_) -> false. get_tagged_opt(Tag, Opts, Default) -> case lists:keysearch(Tag, 1, Opts) of {value, {_, Value}} -> Value; _Other -> Default end. %% %% mk_ssl_optstr(Opts) -> string() %% %% Makes a "command line" string of SSL options %% mk_ssl_optstr(Opts) -> lists:flatten([mk_one_ssl_optstr(O) || O <- Opts]). mk_one_ssl_optstr({verify, Code}) -> [" -verify ", integer_to_list(Code)]; mk_one_ssl_optstr({depth, Depth}) -> [" -depth ", integer_to_list(Depth)]; mk_one_ssl_optstr({certfile, String}) -> [" -certfile ", String]; mk_one_ssl_optstr({keyfile, String}) -> [" -keyfile ", String]; mk_one_ssl_optstr({password, String}) -> [" -password ", String]; mk_one_ssl_optstr({cacertfile, String}) -> [" -cacertfile ", String]; mk_one_ssl_optstr({ciphers, String}) -> [" -ciphers ", String]; mk_one_ssl_optstr({cachetimeout, Timeout}) -> [" -cachetimeout ", integer_to_list(Timeout)]; mk_one_ssl_optstr(_) -> "". extract_opts(OptTags, Opts) -> [O || O = {Tag,_} <- Opts, lists:member(Tag, OptTags)]. replace_opts(NOpts, Opts) -> lists:foldl(fun({Key, Val}, Acc) -> lists:keyreplace(Key, 1, Acc, {Key, Val}); %% XXX Check. Patch from Chandrashekhar Mullaparthi. (binary, Acc) -> lists:keyreplace(mode, 1, Acc, {mode, binary}) end, Opts, NOpts). %% Misc is_subset(A, B) -> [] =:= A -- B.