%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1999-2009. 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 server
%%
%% TODO
%%
%% XXX The ip option in listen is not general enough. It is assumed
%% to be a tuple, which is not always the case.
-module(ssl_server).
-behaviour(gen_server).
%% External exports
-export([start_link/0]).
-export([transport_accept/2, transport_accept/3, ssl_accept/2, ssl_accept/3,
ciphers/0, connect/5, connect/6,
connection_info/1, close/1, listen/3, listen/4, peercert/1,
peername/1, proxy_join/2, seed/1, setnodelay/2, sockname/1,
version/0]).
-export([start_link_prim/0]).
-export([ssl_accept_prim/4, transport_accept_prim/4,
connect_prim/7, close_prim/2,
listen_prim/5, proxy_join_prim/3, peername_prim/2, setnodelay_prim/3,
sockname_prim/2]).
-export([dump/0, dump/1]).
-export([enable_debug/0, disable_debug/0, set_debug/1]).
-export([enable_debugmsg/0, disable_debugmsg/0, set_debugmsg/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
-include("ssl_int.hrl").
-record(st, {
port = [], % port() of port program
progpid = [], % OS pid of port program
debug = false, % debug printout flag
cons = [], % All brokers except pending accepts
paccepts = [], % Pending accept brokers
proxylsport = [], % proxy listen socket port
intref = 0, % internal reference counter
compvsn = "", % ssl compile library version
libvsn = "", % ssl library version
ciphers = [] % available ciphers
}).
%% In all functions below IP is a four tuple, e.g. {192, 236, 52, 7}.
%% Port, Fd and ListenFd are integers; Flags is a string of characters.
%%
%% The prefixes F and L mean foreign and local, respectively.
%% Example: FIP (IP address for foreign end).
%%
%% start_link() -> {ok, Pid} | {error, Reason}
%%
start_link() ->
gen_server:start_link({local, ssl_server}, ssl_server, [], []).
start_link_prim() ->
gen_server:start_link({local, ssl_server_prim}, ssl_server, [], []).
%%
%% transport_accept(ListenFd, Flags) -> {ok, Fd, ProxyLLPort} |
%% {error, Reason}
%%
transport_accept(ListenFd, Flags) ->
transport_accept(ListenFd, Flags, infinity).
transport_accept(ListenFd, Flags, Timeout) ->
transport_accept_prim(ssl_server,ListenFd, Flags, Timeout).
transport_accept_prim(ServerName, ListenFd, Flags, Timeout) ->
Req = {transport_accept, self(), ListenFd, Flags},
gen_server:call(ServerName, Req, Timeout).
%%
%% ssl_accept(ListenFd, Flags) -> {ok, Fd, ProxyLLPort} |
%% {error, Reason}
%%
ssl_accept(ListenFd, Flags) ->
ssl_accept(ListenFd, Flags, infinity).
ssl_accept(ListenFd, Flags, Timeout) ->
ssl_accept_prim(ssl_server, ListenFd, Flags, Timeout).
ssl_accept_prim(ServerName, Fd, Flags, Timeout) ->
Req = {ssl_accept, Fd, Flags},
gen_server:call(ServerName, Req, Timeout).
%%
%% ciphers() -> {ok, Ciphers}
%%
ciphers() ->
gen_server:call(ssl_server, ciphers, infinity).
%%
%% close(Fd) -> ok
%%
close(Fd) ->
close_prim(ssl_server, Fd).
close_prim(ServerName, Fd) ->
gen_server:call(ServerName, {close, self(), Fd}, infinity),
ok.
%%
%% connect(LIP, LPort, FIP, FPort, Flags) -> {ok, Fd, ProxyLFPort} |
%% {error, Reason}
%%
connect(LIP, LPort, FIP, FPort, Flags) ->
connect(LIP, LPort, FIP, FPort, Flags, infinity).
connect(LIP, LPort, FIP, FPort, Flags, Timeout) ->
connect_prim(ssl_server, LIP, LPort, FIP, FPort, Flags, Timeout).
connect_prim(ServerName, LIP, LPort, FIP, FPort, Flags, Timeout) ->
Req = {connect, self(), LIP, LPort, FIP, FPort, Flags},
gen_server:call(ServerName, Req, Timeout).
%%
%% connection_info(Fd) -> {ok, {Protocol, Cipher}} | {error, Reason}
%%
connection_info(Fd) ->
Req = {connection_info, self(), Fd},
gen_server:call(ssl_server, Req, infinity).
%%
%% listen(IP, LPort, Flags),
%% listen(IP, LPort, Flags, BackLog) -> {ok, ListenFd, LPort0} |
%% {error, Reason}
%%
listen(IP, LPort, Flags) ->
listen(IP, LPort, Flags, ?DEF_BACKLOG).
listen(IP, LPort, Flags, BackLog) ->
listen_prim(ssl_server, IP, LPort, Flags, BackLog).
listen_prim(ServerName, IP, LPort, Flags, BackLog) ->
Req = {listen, self(), IP, LPort, Flags, BackLog},
gen_server:call(ServerName, Req, infinity).
%%
%% peercert(Fd) -> {ok, Cert} | {error, Reason}
%%
peercert(Fd) ->
Req = {peercert, self(), Fd},
gen_server:call(ssl_server, Req, infinity).
%%
%% peername(Fd) -> {ok, {Address, Port}} | {error, Reason}
%%
peername(Fd) ->
peername_prim(ssl_server, Fd).
peername_prim(ServerName, Fd) ->
Req = {peername, self(), Fd},
gen_server:call(ServerName, Req, infinity).
%%
%% proxy_join(Fd, LPort) -> ok | {error, Reason}
%%
proxy_join(Fd, LPort) ->
proxy_join_prim(ssl_server, Fd, LPort).
proxy_join_prim(ServerName, Fd, LPort) ->
Req = {proxy_join, self(), Fd, LPort},
gen_server:call(ServerName, Req, infinity).
%%
%% seed(Data)
%%
seed(Data) ->
Req = {seed, Data},
gen_server:call(ssl_server, Req, infinity).
%%
%% set_nodelay(Fd, Boolean)
%%
setnodelay(Fd, Boolean) ->
setnodelay_prim(ssl_server, Fd, Boolean).
setnodelay_prim(ServerName, Fd, Boolean) ->
Req = {setnodelay, self(), Fd, Boolean},
gen_server:call(ServerName, Req, infinity).
%%
%% sockname(Fd) -> {ok, {Address, Port}} | {error, Reason}
%%
sockname(Fd) ->
sockname_prim(ssl_server, Fd).
sockname_prim(ServerName, Fd) ->
Req = {sockname, self(), Fd},
gen_server:call(ServerName, Req, infinity).
%%
%% version() -> {ok, {CompVsn, LibVsn}}
%%
version() ->
gen_server:call(ssl_server, version, infinity).
enable_debug() ->
set_debug(true).
disable_debug() ->
set_debug(false).
set_debug(Bool) ->
set_debug(Bool, infinity).
set_debug(Bool, Timeout) when is_boolean(Bool) ->
Req = {set_debug, Bool, self()},
gen_server:call(ssl_server, Req, Timeout).
enable_debugmsg() ->
set_debugmsg(true).
disable_debugmsg() ->
set_debugmsg(false).
set_debugmsg(Bool) ->
set_debugmsg(Bool, infinity).
set_debugmsg(Bool, Timeout) when is_boolean(Bool) ->
Req = {set_debugmsg, Bool, self()},
gen_server:call(ssl_server, Req, Timeout).
dump() ->
dump(infinity).
dump(Timeout) ->
Req = {dump, self()},
gen_server:call(ssl_server, Req, Timeout).
%%
%% init
%%
init([]) ->
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,
ProgDir =
case init:get_argument(ssl_portprogram_dir) of
{ok, [[D]]} ->
D;
_ ->
find_priv_bin()
end,
{Program, Flags} = mk_cmd_line("ssl_esock"),
Cmd = filename:join(ProgDir, Program) ++ " " ++ Flags,
debug1(Debug, " start, Cmd = ~s~n", [Cmd]),
case (catch open_port({spawn, Cmd}, [binary, {packet, 4}])) of
Port when is_port(Port) ->
process_flag(trap_exit, true),
receive
{Port, {data, Bin}} ->
{ProxyLLPort, ProgPid, CompVsn, LibVsn, Ciphers} =
decode_msg(Bin, [int16, int32, string, string,
string]),
debug1(Debug, "port program pid = ~w~n",
[ProgPid]),
{ok, #st{port = Port,
proxylsport = ProxyLLPort,
progpid = ProgPid,
debug = Debug,
compvsn = CompVsn,
libvsn = LibVsn,
ciphers = Ciphers}};
{'EXIT', Port, Reason} ->
{stop, Reason}
end;
{'EXIT', Reason} ->
{stop, Reason}
end.
%%
%% transport_accept
%%
handle_call({transport_accept, Broker, ListenFd, Flags}, From, St) ->
debug(St, "transport_accept: broker = ~w, listenfd = ~w~n",
[Broker, ListenFd]),
case get_by_fd(ListenFd, St#st.cons) of
{ok, {ListenFd, _, _}} ->
send_cmd(St#st.port, ?TRANSPORT_ACCEPT, [int32(ListenFd), Flags, 0]),
PAccepts = add({ListenFd, Broker, From}, St#st.paccepts),
%%
%% We reply when we get TRANSPORT_ACCEPT_REP or ASYNC_ACCEPT_ERR
%%
{noreply, St#st{paccepts = PAccepts}};
_Other ->
{reply, {error, ebadf}, St}
end;
%%
%% ssl_accept
%%
handle_call({ssl_accept, Fd, Flags}, From, St) ->
case replace_from_by_fd(Fd, St#st.cons, From) of
{ok, _, Cons} = _Rep ->
send_cmd(St#st.port, ?SSL_ACCEPT, [int32(Fd), Flags, 0]),
%% We reply when we get SSL_ACCEPT_REP or ASYNC_ACCEPT_ERR
{noreply, St#st{cons = Cons}};
_Other ->
{reply, {error, ebadf}, St}
end;
%%
%% version
%%
handle_call(ciphers, From, St) ->
debug(St, "ciphers: from = ~w~n", [From]),
{reply, {ok, St#st.ciphers}, St};
%%
%% connect
%%
handle_call({connect, Broker, LIP, LPort, FIP, FPort, Flags}, From, St) ->
debug(St, "connect: broker = ~w, ip = ~w, "
"sport = ~w~n", [Broker, FIP, FPort]),
Port = St#st.port,
LIPStr = ip_to_string(LIP),
FIPStr = ip_to_string(FIP),
IntRef = new_intref(St),
send_cmd(Port, ?CONNECT, [int32(IntRef),
int16(LPort), LIPStr, 0,
int16(FPort), FIPStr, 0,
Flags, 0]),
Cons = add({{intref, IntRef}, Broker, From}, St#st.cons),
%% We reply when we have got CONNECT_SYNC_ERR, or CONNECT_WAIT
%% and CONNECT_REP, or CONNECT_ERR.
{noreply, St#st{cons = Cons, intref = IntRef}};
%%
%% connection_info
%%
handle_call({connection_info, Broker, Fd}, From, St) ->
debug(St, "connection_info: broker = ~w, fd = ~w~n",
[Broker, Fd]),
case replace_from_by_fd(Fd, St#st.cons, From) of
{ok, _, Cons} ->
send_cmd(St#st.port, ?GETCONNINFO, [int32(Fd)]),
%% We reply when we get GETCONNINFO_REP or GETCONNINFO_ERR.
{noreply, St#st{cons = Cons}};
_Other ->
{reply, {error, ebadf}, St}
end;
%%
%% close
%%
handle_call({close, Broker, Fd}, _From, St) ->
debug(St, "close: broker = ~w, fd = ~w~n",
[Broker, Fd]),
#st{port = Port, cons = Cons0, paccepts = PAccepts0} = St,
case delete_by_fd(Fd, Cons0) of
%% Must match Broker pid; fd may be reused already.
{ok, {Fd, Broker, _}, Cons} ->
send_cmd(Port, ?CLOSE, int32(Fd)),
%% If Fd is a listen socket fd, there might be pending
%% accepts for that fd.
case delete_all_by_fd(Fd, PAccepts0) of
{ok, DelAccepts, RemAccepts} ->
%% Reply {error, closed} to all pending accepts
lists:foreach(fun({_, _, AccFrom}) ->
gen_server:reply(AccFrom,
{error, closed})
end, DelAccepts),
{reply, ok,
St#st{cons = Cons, paccepts = RemAccepts}};
_ ->
{reply, ok, St#st{cons = Cons}}
end;
_ ->
{reply, ok, St}
end;
%%
%% listen
%%
handle_call({listen, Broker, IP, LPort, Flags, BackLog}, From, St) ->
debug(St, "listen: broker = ~w, IP = ~w, "
"sport = ~w~n", [Broker, IP, LPort]),
Port = St#st.port,
IPStr = ip_to_string(IP),
IntRef = new_intref(St),
send_cmd(Port, ?LISTEN, [int32(IntRef), int16(LPort), IPStr, 0,
int16(BackLog), Flags, 0]),
Cons = add({{intref, IntRef}, Broker, From}, St#st.cons),
%% We reply when we have got LISTEN_REP.
{noreply, St#st{cons = Cons, intref = IntRef}};
%%
%% peercert
%%
handle_call({peercert, Broker, Fd}, From, St) ->
debug(St, "peercert: broker = ~w, fd = ~w~n",
[Broker, Fd]),
case replace_from_by_fd(Fd, St#st.cons, From) of
{ok, _, Cons} ->
send_cmd(St#st.port, ?GETPEERCERT, [int32(Fd)]),
%% We reply when we get GETPEERCERT_REP or GETPEERCERT_ERR.
{noreply, St#st{cons = Cons}};
_Other ->
{reply, {error, ebadf}, St}
end;
%%
%% peername
%%
handle_call({peername, Broker, Fd}, From, St) ->
debug(St, "peername: broker = ~w, fd = ~w~n",
[Broker, Fd]),
case replace_from_by_fd(Fd, St#st.cons, From) of
{ok, _, Cons} ->
send_cmd(St#st.port, ?GETPEERNAME, [int32(Fd)]),
%% We reply when we get GETPEERNAME_REP or GETPEERNAME_ERR.
{noreply, St#st{cons = Cons}};
_Other ->
{reply, {error, ebadf}, St}
end;
%%
%% proxy join
%%
handle_call({proxy_join, Broker, Fd, LPort}, From, St) ->
debug(St, "proxy_join: broker = ~w, fd = ~w, "
"sport = ~w~n", [Broker, Fd, LPort]),
case replace_from_by_fd(Fd, St#st.cons, From) of
{ok, _, Cons} ->
send_cmd(St#st.port, ?PROXY_JOIN, [int32(Fd),
int16(LPort)]),
%% We reply when we get PROXY_JOIN_REP, or PROXY_JOIN_ERR.
{noreply, St#st{cons = Cons}};
_Other ->
{reply, {error, ebadf}, St}
end;
%%
%% seed
%%
handle_call({seed, Data}, _From, St) when is_binary(Data) ->
send_cmd(St#st.port, ?SET_SEED, [int32(byte_size(Data)), Data]),
{reply, ok, St};
handle_call({seed, Data}, From, St) ->
case catch list_to_binary(Data) of
{'EXIT', _} ->
{reply, {error, edata}, St};
Bin ->
handle_call({seed, Bin}, From, St)
end;
%%
%% setnodelay
%%
handle_call({setnodelay, Broker, Fd, Boolean}, From, St) ->
debug(St, "setnodelay: broker = ~w, fd = ~w, "
"boolean = ~w~n", [Broker, Fd, Boolean]),
case replace_from_by_fd(Fd, St#st.cons, From) of
{ok, _, Cons} ->
Val = if Boolean == true -> 1; true -> 0 end,
send_cmd(St#st.port, ?SET_SOCK_OPT,
[int32(Fd), ?SET_TCP_NODELAY, Val]),
%% We reply when we get IOCTL_OK or IOCTL_ERR.
{noreply, St#st{cons = Cons}};
_Other ->
{reply, {error, ebadf}, St}
end;
%%
%% sockname
%%
handle_call({sockname, Broker, Fd}, From, St) ->
debug(St, "sockname: broker = ~w, fd = ~w~n",
[Broker, Fd]),
case replace_from_by_fd(Fd, St#st.cons, From) of
{ok, _, Cons} ->
send_cmd(St#st.port, ?GETSOCKNAME, [int32(Fd)]),
%% We reply when we get GETSOCKNAME_REP or GETSOCKNAME_ERR.
{noreply, St#st{cons = Cons}};
_Other ->
{reply, {error, ebadf}, St}
end;
%%
%% version
%%
handle_call(version, From, St) ->
debug(St, "version: from = ~w~n", [From]),
{reply, {ok, {St#st.compvsn, St#st.libvsn}}, St};
%%
%% dump
%%
handle_call({dump, Broker}, _From, St) ->
debug(St, "dump: broker = ~w", [Broker]),
Port = St#st.port,
send_cmd(Port, ?DUMP_CMD, []),
{reply, ok, St};
%%
%% set_debug
%%
handle_call({set_debug, Bool, Broker}, _From, St) ->
debug(St, "set_debug: broker = ~w", [Broker]),
Value = case Bool of
true ->
1;
false ->
0
end,
Port = St#st.port,
send_cmd(Port, ?DEBUG_CMD, [Value]),
{reply, ok, St};
%%
%% set_debugmsg
%%
handle_call({set_debugmsg, Bool, Broker}, _From, St) ->
debug(St, "set_debugmsg: broker = ~w", [Broker]),
Value = case Bool of
true ->
1;
false ->
0
end,
Port = St#st.port,
send_cmd(Port, ?DEBUGMSG_CMD, [Value]),
{reply, ok, St};
handle_call(Request, _From, St) ->
debug(St, "unexpected call: ~w~n", [Request]),
Reply = {error, {badcall, Request}},
{reply, Reply, St}.
%%
%% handle_cast(Msg, St)
%%
handle_cast(Msg, St) ->
debug(St, "unexpected cast: ~w~n", [Msg]),
{noreply, St}.
%%
%% handle_info(Info, St)
%%
%% Data from port
%%
handle_info({Port, {data, Bin}},
#st{cons = StCons, paccepts = Paccepts,
port = Port, proxylsport = Proxylsport} = St)
when is_binary(Bin) ->
%% io:format("++++ ssl_server got from port: ~w~n", [Bin]),
<<OpCode:8, _/binary>> = Bin,
case OpCode of
%%
%% transport_accept
%%
?TRANSPORT_ACCEPT_ERR when byte_size(Bin) >= 5 ->
{ListenFd, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "transport_accept_err: listenfd = ~w, "
"reason = ~w~n", [ListenFd, Reason]),
case delete_last_by_fd(ListenFd, Paccepts) of
{ok, {_, _, From}, PAccepts} ->
gen_server:reply(From, {error, Reason}),
{noreply, St#st{paccepts = PAccepts}};
_Other ->
%% Already closed
{noreply, St}
end;
?TRANSPORT_ACCEPT_REP when byte_size(Bin) >= 9 ->
{ListenFd, Fd} = decode_msg(Bin, [int32, int32]),
debug(St, "transport_accept_rep: listenfd = ~w, "
"fd = ~w~n", [ListenFd, Fd]),
case delete_last_by_fd(ListenFd, Paccepts) of
{ok, {_, Broker, From}, PAccepts} ->
Reply = {ok, Fd, Proxylsport},
gen_server:reply(From, Reply),
debug(St, "transport_accept_rep: From = ~w\n", [From]),
Cons = add({Fd, Broker, From}, StCons),
{noreply, St#st{cons = Cons, paccepts = PAccepts}};
_Other ->
%% Already closed
{noreply, St}
end;
%%
%% ssl_accept
%%
?SSL_ACCEPT_ERR when byte_size(Bin) >= 5 ->
{Fd, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "ssl_accept_err: listenfd = ~w, "
"reason = ~w~n", [Fd, Reason]),
%% JC: remove this?
case delete_last_by_fd(Fd, StCons) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {error, Reason}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
?SSL_ACCEPT_REP when byte_size(Bin) >= 5 ->
Fd = decode_msg(Bin, [int32]),
debug(St, "ssl_accept_rep: Fd = ~w\n", [Fd]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, ok),
{noreply, St#st{cons = Cons}};
_ ->
{noreply, St}
end;
%%
%% connect
%%
?CONNECT_SYNC_ERR when byte_size(Bin) >= 5 ->
{IntRef, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "connect_sync_err: intref = ~w, "
"reason = ~w~n", [IntRef, Reason]),
case delete_by_intref(IntRef, StCons) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {error, Reason}),
{noreply, St#st{cons = Cons}};
_Other ->
{noreply, St}
end;
?CONNECT_WAIT when byte_size(Bin) >= 9 ->
{IntRef, Fd} = decode_msg(Bin, [int32, int32]),
debug(St, "connect_wait: intref = ~w, "
"fd = ~w~n", [IntRef, Fd]),
case replace_fd_by_intref(IntRef, StCons, Fd) of
{ok, _, Cons} ->
%% We reply when we get CONNECT_REP or CONNECT_ERR
{noreply, St#st{cons = Cons}};
_Other ->
%% We have a new Fd which must be closed
send_cmd(Port, ?CLOSE, int32(Fd)),
{noreply, St}
end;
?CONNECT_REP when byte_size(Bin) >= 5 ->
%% after CONNECT_WAIT
Fd = decode_msg(Bin, [int32]),
debug(St, "connect_rep: fd = ~w~n", [Fd]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {ok, Fd, Proxylsport}),
{noreply, St#st{cons = Cons}};
_Other ->
{noreply, St}
end;
?CONNECT_ERR when byte_size(Bin) >= 5 ->
{Fd, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "connect_err: fd = ~w, "
"reason = ~w~n", [Fd, Reason]),
case delete_by_fd(Fd, StCons) of
{ok, {_, _, From}, Cons} ->
%% Fd not yet published - hence close ourselves
send_cmd(Port, ?CLOSE, int32(Fd)),
gen_server:reply(From, {error, Reason}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
%%
%% connection_info
%%
?GETCONNINFO_REP when byte_size(Bin) >= 5 ->
{Fd, Protocol, Cipher} = decode_msg(Bin, [int32, string, string]),
debug(St, "connection_info_rep: fd = ~w, "
"protcol = ~p, ip = ~p~n", [Fd, Protocol, Cipher]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {ok, {protocol_name(Protocol),
Cipher}}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
?GETCONNINFO_ERR when byte_size(Bin) >= 5 ->
{Fd, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "connection_info_err: fd = ~w, "
"reason = ~w~n", [Fd, Reason]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {error, Reason}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
%%
%% listen
%%
?LISTEN_SYNC_ERR when byte_size(Bin) >= 5 ->
{IntRef, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "listen_sync_err: intref = ~w, "
"reason = ~w~n", [IntRef, Reason]),
case delete_by_intref(IntRef, StCons) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {error, Reason}),
{noreply, St#st{cons = Cons}};
_Other ->
{noreply, St}
end;
?LISTEN_REP when byte_size(Bin) >= 11 ->
{IntRef, ListenFd, LPort} = decode_msg(Bin, [int32, int32, int16]),
debug(St, "listen_rep: intref = ~w, "
"listenfd = ~w, sport = ~w~n", [IntRef, ListenFd, LPort]),
case replace_fd_from_by_intref(IntRef, StCons, ListenFd, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {ok, ListenFd, LPort}),
{noreply, St#st{cons = Cons}};
_Other ->
%% ListenFd has to be closed.
send_cmd(Port, ?CLOSE, int32(ListenFd)),
{noreply, St}
end;
%%
%% proxy join
%%
?PROXY_JOIN_REP when byte_size(Bin) >= 5 ->
Fd = decode_msg(Bin, [int32]),
debug(St, "proxy_join_rep: fd = ~w~n",
[Fd]),
case get_by_fd(Fd, StCons) of
{ok, {_, _, From}} ->
gen_server:reply(From, ok),
{noreply, St};
_Other ->
%% Already closed
{noreply, St}
end;
?PROXY_JOIN_ERR when byte_size(Bin) >= 5 ->
{Fd, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "proxy_join_rep: fd = ~w, "
"reason = ~w~n", [Fd, Reason]),
case delete_by_fd(Fd, StCons) of
{ok, {_, _, From}, Cons} ->
case Reason of
enoproxysocket ->
send_cmd(Port, ?CLOSE, int32(Fd));
_ ->
ok
%% Must not close Fd since it is published
end,
gen_server:reply(From, {error, Reason}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
%%
%% peername
%%
?GETPEERNAME_REP when byte_size(Bin) >= 5 ->
{Fd, LPort, IPString} = decode_msg(Bin, [int32, int16, string]),
debug(St, "getpeername_rep: fd = ~w, "
"sport = ~w, ip = ~p~n", [Fd, LPort, IPString]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {ok, {IPString, LPort}}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
?GETPEERNAME_ERR when byte_size(Bin) >= 5 ->
{Fd, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "getpeername_err: fd = ~w, "
"reason = ~w~n", [Fd, Reason]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {error, Reason}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
%%
%% ioctl
%%
?IOCTL_OK when byte_size(Bin) >= 5 ->
Fd = decode_msg(Bin, [int32]),
debug(St, "ioctl_ok: fd = ~w~n",
[Fd]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, ok),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
?IOCTL_ERR when byte_size(Bin) >= 5 ->
{Fd, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "ioctl_err: fd = ~w, "
"reason = ~w~n", [Fd, Reason]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {error, Reason}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
%%
%% sockname
%%
?GETSOCKNAME_REP when byte_size(Bin) >= 5 ->
{Fd, LPort, IPString} = decode_msg(Bin, [int32, int16, string]),
debug(St, "getsockname_rep: fd = ~w, "
"sport = ~w, ip = ~p~n", [Fd, LPort, IPString]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {ok, {IPString, LPort}}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
?GETSOCKNAME_ERR when byte_size(Bin) >= 5 ->
{Fd, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "getsockname_err: fd = ~w, "
"reason = ~w~n", [Fd, Reason]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {error, Reason}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
%%
%% peercert
%%
?GETPEERCERT_REP when byte_size(Bin) >= 5 ->
{Fd, Cert} = decode_msg(Bin, [int32, bin]),
debug(St, "getpeercert_rep: fd = ~w~n", [Fd]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {ok, Cert}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end;
?GETPEERCERT_ERR when byte_size(Bin) >= 5 ->
{Fd, Reason} = decode_msg(Bin, [int32, atom]),
debug(St, "getpeercert_err: fd = ~w, reason = ~w~n",
[Fd, Reason]),
case replace_from_by_fd(Fd, StCons, []) of
{ok, {_, _, From}, Cons} ->
gen_server:reply(From, {error, Reason}),
{noreply, St#st{cons = Cons}};
_Other ->
%% Already closed
{noreply, St}
end
end;
%%
%% EXIT
%%
handle_info({'EXIT', Pid, Reason}, St) when is_pid(Pid) ->
debug(St, "exit pid = ~w, "
"reason = ~w~n", [Pid, Reason]),
case delete_by_pid(Pid, St#st.cons) of
{ok, {{intref, _}, Pid, _}, Cons} ->
{noreply, St#st{cons = Cons}};
{ok, {Fd, Pid, _}, Cons} ->
send_cmd(St#st.port, ?CLOSE, int32(Fd)),
%% If Fd is a listen socket fd, there might be pending
%% accepts for that fd.
case delete_all_by_fd(Fd, St#st.paccepts) of
{ok, DelAccepts, RemAccepts} ->
%% Reply {error, closed} to all pending accepts.
lists:foreach(fun({_, _, From}) ->
gen_server:reply(From,
{error, closed})
end, DelAccepts),
{noreply,
St#st{cons = Cons, paccepts = RemAccepts}};
_ ->
{noreply, St#st{cons = Cons}}
end;
_ ->
case delete_by_pid(Pid, St#st.paccepts) of
{ok, {ListenFd, _, _}, PAccepts} ->
%% decrement ref count in port program
send_cmd(St#st.port, ?NOACCEPT, int32(ListenFd)),
{noreply, St#st{paccepts = PAccepts}};
_ ->
{noreply, St}
end
end;
%%
%% 'badsig' means bad message to port. Port program is unaffected.
%%
handle_info({'EXIT', Port, badsig}, #st{port = Port} = St) ->
debug(St, "badsig!!!~n", []),
{noreply, St};
handle_info({'EXIT', Port, Reason}, #st{port = Port} = St) ->
{stop, Reason, St};
handle_info(Info, St) ->
debug(St, "unexpected info: ~w~n", [Info]),
{noreply, St}.
%%
%% terminate(Reason, St) -> any
%%
terminate(_Reason, _St) ->
ok.
%%
%% code_change(OldVsn, St, Extra) -> {ok, NSt}
%%
code_change(_OldVsn, St, _Extra) ->
{ok, St}.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
%%
%% Send binary command to sock
%%
send_cmd(Port, Cmd, Args) ->
Port ! {self(), {command, [Cmd| Args]}}.
%%
%% add(Descr, Cons) -> NCons
%%
add(D, L) ->
[D| L].
%%
%% get_by_fd(Fd, Cons) -> {ok, Descr} | not_found
%%
get_by_fd(Fd, Cons) ->
get_by_pos(Fd, 1, Cons).
%%
%% delete_by_fd(Fd, Cons) -> {ok, OldDesc, NewCons} | not_found.
%%
delete_by_fd(Fd, Cons) ->
delete_by_pos(Fd, 1, Cons).
%%
%% delete_all_by_fd(Fd, Cons) -> {ok, DelCons, RemCons} | not_found.
%%
delete_all_by_fd(Fd, Cons) ->
delete_all_by_pos(Fd, 1, Cons).
%%
%% delete_by_intref(IntRef, Cons) -> {ok, OldDesc, NewCons} | not_found.
%%
delete_by_intref(IntRef, Cons) ->
delete_by_pos({intref, IntRef}, 1, Cons).
%%
%% delete_by_pid(Pid, Cons) -> {ok, OldDesc, NewCons} | not_found.
%%
delete_by_pid(Pid, Cons) ->
delete_by_pos(Pid, 2, Cons).
%%
%% delete_last_by_fd(Fd, Cons) -> {ok, OldDesc, NCons} | not_found
%%
delete_last_by_fd(Fd, Cons) ->
case dlbf(Fd, Cons) of
{X, L} ->
{ok, X, L};
_Other ->
not_found
end.
dlbf(Fd, [H]) ->
last_elem(Fd, H, []);
dlbf(Fd, [H|T]) ->
case dlbf(Fd, T) of
{X, L} ->
{X, [H|L]};
L ->
last_elem(Fd, H, L)
end;
dlbf(_Fd, []) ->
[].
last_elem(Fd, H, L) when element(1, H) == Fd ->
{H, L};
last_elem(_, H, L) ->
[H|L].
%%
%% replace_from_by_fd(Fd, Cons, From) -> {ok, OldDesc, NewList} | not_found
%%
replace_from_by_fd(Fd, Cons, From) ->
replace_posn_by_pos(Fd, 1, Cons, [{From, 3}]).
%%
%% replace_fd_by_intref(IntRef, Cons, Fd) -> {ok, OldDesc, NewList} | not_f.
%%
replace_fd_by_intref(IntRef, Cons, Fd) ->
replace_posn_by_pos({intref, IntRef}, 1, Cons, [{Fd, 1}]).
%%
%% replace_fd_from_by_intref(IntRef, Cons, NFd, From) ->
%% {ok, OldDesc, NewList} | not_found
%%
replace_fd_from_by_intref(IntRef, Cons, NFd, From) ->
replace_posn_by_pos({intref, IntRef}, 1, Cons, [{NFd, 1}, {From, 3}]).
%%
%% All *_by_pos functions
%%
get_by_pos(Key, Pos, [H|_]) when element(Pos, H) == Key ->
{ok, H};
get_by_pos(Key, Pos, [_|T]) ->
get_by_pos(Key, Pos, T);
get_by_pos(_, _, []) ->
not_found.
delete_by_pos(Key, Pos, Cons) ->
case delete_by_pos1(Key, Pos, {not_found, Cons}) of
{not_found, _} ->
not_found;
{ODesc, NCons} ->
{ok, ODesc, NCons}
end.
delete_by_pos1(Key, Pos, {_R, [H|T]}) when element(Pos, H) == Key ->
{H, T};
delete_by_pos1(Key, Pos, {R, [H|T]}) ->
{R0, T0} = delete_by_pos1(Key, Pos, {R, T}),
{R0, [H| T0]};
delete_by_pos1(_, _, {R, []}) ->
{R, []}.
delete_all_by_pos(Key, Pos, Cons) ->
case lists:foldl(fun(H, {Ds, Rs}) when element(Pos, H) == Key ->
{[H|Ds], Rs};
(H, {Ds, Rs}) ->
{Ds, [H|Rs]}
end, {[], []}, Cons) of
{[], _} ->
not_found;
{DelCons, RemCons} ->
{ok, DelCons, RemCons}
end.
replace_posn_by_pos(Key, Pos, Cons, Repls) ->
replace_posn_by_pos1(Key, Pos, Cons, Repls, []).
replace_posn_by_pos1(Key, Pos, [H0| T], Repls, Acc)
when element(Pos, H0) =:= Key ->
H = lists:foldl(fun({Val, VPos}, Tuple) ->
setelement(VPos, Tuple, Val)
end, H0, Repls),
{ok, H0, lists:reverse(Acc, [H| T])};
replace_posn_by_pos1(Key, Pos, [H|T], Repls, Acc) ->
replace_posn_by_pos1(Key, Pos, T, Repls, [H| Acc]);
replace_posn_by_pos1(_, _, [], _, _) ->
not_found.
%%
%% Binary/integer conversions
%%
int16(I) ->
%%[(I bsr 8) band 255, I band 255].
<<I:16>>.
int32(I) ->
%% [(I bsr 24) band 255,
%% (I bsr 16) band 255,
%% (I bsr 8) band 255,
%% I band 255].
<<I:32>>.
%% decode_msg(Bin, Format) -> Tuple | integer() | atom() | string() |
%% list of binaries()
%%
%% Decode message from binary
%% Format = [spec()]
%% spec() = int16 | int32 | string | atom | bin | bins
%%
%% Notice: The first byte (op code) of the binary message is removed.
%% Notice: bins returns a *list* of binaries.
%%
decode_msg(<<_, Bin/binary>>, Format) ->
Dec = dec(Format, Bin),
case Dec of
[Dec1] -> Dec1;
_ -> list_to_tuple(Dec)
end.
dec([], _) ->
[];
dec([int16| F], <<N:16, Bin/binary>>) ->
[N| dec(F, Bin)];
dec([int32| F], <<N:32, Bin/binary>>) ->
[N| dec(F, Bin)];
dec([string| F], Bin0) ->
{Cs, Bin1} = dec_string(Bin0),
[Cs| dec(F, Bin1)];
dec([atom|F], Bin0) ->
{Cs, Bin1} = dec_string(Bin0),
[list_to_atom(Cs)| dec(F, Bin1)];
dec([bin|F], Bin) ->
{Bin1, Bin2} = dec_bin(Bin),
[Bin1| dec(F, Bin2)].
%% NOTE: This clause is not actually used yet.
%% dec([bins|F], <<N:32, Bin0/binary>>) ->
%% {Bins, Bin1} = dec_bins(N, Bin0),
%% [Bins| dec(F, Bin1)].
dec_string(Bin) ->
dec_string(Bin, []).
dec_string(<<0, Bin/binary>>, RCs) ->
{lists:reverse(RCs), Bin};
dec_string(<<C, Bin/binary>>, RCs) ->
dec_string(Bin, [C| RCs]).
dec_bin(<<L:32, Bin0/binary>>) ->
<<Bin1:L/binary, Bin2/binary>> = Bin0,
{Bin1, Bin2}.
%% dec_bins(N, Bin) ->
%% dec_bins(N, Bin, []).
%% dec_bins(0, Bin, Acc) ->
%% {lists:reverse(Acc), Bin};
%% dec_bins(N, Bin0, Acc) when N > 0 ->
%% {Bin1, Bin2} = dec_bin(Bin0),
%% dec_bins(N - 1, Bin2, [Bin1| Acc]).
%%
%% new_intref
%%
new_intref(St) ->
(St#st.intref + 1) band 16#ffffffff.
%%
%% {Program, Flags} = mk_cmd_line(DefaultProgram)
%%
mk_cmd_line(Default) ->
{port_program(Default),
lists:flatten([debug_flag(), " ", debug_port_flag(), " ",
debugdir_flag(), " ",
msgdebug_flag(), " ", proxylsport_flag(), " ",
proxybacklog_flag(), " ", ephemeral_rsa_flag(), " ",
ephemeral_dh_flag(), " ",
protocol_version_flag(), " "])}.
port_program(Default) ->
case application:get_env(ssl, port_program) of
{ok, Program} when is_list(Program) ->
Program;
_Other ->
Default
end.
%%
%% As this server may be started by the distribution, it is not safe to assume
%% a working code server, neither a working file server.
%% I try to utilize the most primitive interfaces available to determine
%% the directory of the port_program.
%%
find_priv_bin() ->
PrivDir = case (catch code:priv_dir(ssl)) of
{'EXIT', _} ->
%% Code server probably not startet yet
{ok, P} = erl_prim_loader:get_path(),
ModuleFile = atom_to_list(?MODULE) ++ extension(),
Pd = (catch lists:foldl
(fun(X,Acc) ->
M = filename:join([X, ModuleFile]),
%% The file server probably not started
%% either, has to use raw interface.
case file:raw_read_file_info(M) of
{ok,_} ->
%% Found our own module in the
%% path, lets bail out with
%% the priv_dir of this directory
Y = filename:split(X),
throw(filename:join
(lists:sublist
(Y,length(Y) - 1)
++ ["priv"]));
_ ->
Acc
end
end,
false,P)),
case Pd of
false ->
exit(ssl_priv_dir_indeterminate);
_ ->
Pd
end;
Dir ->
Dir
end,
filename:join([PrivDir, "bin"]).
extension() ->
%% erlang:info(machine) returns machine name as text in all uppercase
"." ++ string:to_lower(erlang:system_info(machine)).
debug_flag() ->
case os:getenv("ERL_SSL_DEBUG") of
false ->
get_env(debug, "-d");
_ ->
"-d"
end.
debug_port_flag() ->
case os:getenv("ERL_SSL_DEBUGPORT") of
false ->
get_env(debug, "-d");
_ ->
"-d"
end.
msgdebug_flag() ->
case os:getenv("ERL_SSL_MSGDEBUG") of
false ->
get_env(msgdebug, "-dm");
_ ->
"-dm"
end.
proxylsport_flag() ->
case application:get_env(ssl, proxylsport) of
{ok, PortNum} ->
"-pp " ++ integer_to_list(PortNum);
_Other ->
""
end.
proxybacklog_flag() ->
case application:get_env(ssl, proxylsbacklog) of
{ok, Size} ->
"-pb " ++ integer_to_list(Size);
_Other ->
""
end.
debugdir_flag() ->
case os:getenv("ERL_SSL_DEBUG") of
false ->
case application:get_env(ssl, debugdir) of
{ok, Dir} when is_list(Dir) ->
"-dd " ++ Dir;
_Other ->
""
end;
_ ->
"-dd ./"
end.
ephemeral_rsa_flag() ->
case application:get_env(ssl, ephemeral_rsa) of
{ok, true} ->
"-ersa ";
_Other ->
""
end.
ephemeral_dh_flag() ->
case application:get_env(ssl, ephemeral_dh) of
{ok, true} ->
"-edh ";
_Other ->
""
end.
protocol_version_flag() ->
case application:get_env(ssl, protocol_version) of
{ok, []} ->
"";
{ok, Vsns} when is_list(Vsns) ->
case transform_vsns(Vsns) of
N when (N > 0) ->
"-pv " ++ integer_to_list(N);
_ ->
""
end;
_Other ->
""
end.
transform_vsns(Vsns) ->
transform_vsns(Vsns, 0).
transform_vsns([sslv2| Vsns], I) ->
transform_vsns(Vsns, I bor ?SSLv2);
transform_vsns([sslv3| Vsns], I) ->
transform_vsns(Vsns, I bor ?SSLv3);
transform_vsns([tlsv1| Vsns], I) ->
transform_vsns(Vsns, I bor ?TLSv1);
transform_vsns([_ | Vsns], I) ->
transform_vsns(Vsns, I);
transform_vsns([], I) ->
I.
protocol_name("SSLv2") -> sslv2;
protocol_name("SSLv3") -> sslv3;
protocol_name("TLSv1") -> tlsv1.
get_env(Key, Val) ->
case application:get_env(ssl, Key) of
{ok, true} ->
Val;
_Other ->
""
end.
ip_to_string({A,B,C,D}) ->
[integer_to_list(A),$.,integer_to_list(B),$.,
integer_to_list(C),$.,integer_to_list(D)].
debug(St, Format, Args) ->
debug1(St#st.debug, Format, Args).
debug1(true, Format0, Args) ->
{_MS, S, MiS} = erlang:now(),
Secs = S rem 100,
MiSecs = MiS div 1000,
Format = "++++ ~3..0w:~3..0w ssl_server (~w): " ++ Format0,
io:format(Format, [Secs, MiSecs, self()| Args]);
debug1(_, _, _) ->
ok.