From 3e631a1851d1b0546e9ba1b52a22cf15b2e32501 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Tue, 6 Jul 2010 13:49:25 +0200 Subject: Connect from both sides works now --- lib/ssl/inet_proxy_dist.erl | 228 ++++++++++++++++++++++++++++++++++++++++++ lib/ssl/proxy_server.erl | 218 ++++++++++++++++++++++++++++++++++++++++ lib/ssl/src/inet_ssl_dist.erl | 9 +- 3 files changed, 449 insertions(+), 6 deletions(-) create mode 100644 lib/ssl/inet_proxy_dist.erl create mode 100644 lib/ssl/proxy_server.erl (limited to 'lib/ssl') diff --git a/lib/ssl/inet_proxy_dist.erl b/lib/ssl/inet_proxy_dist.erl new file mode 100644 index 0000000000..6308deabe6 --- /dev/null +++ b/lib/ssl/inet_proxy_dist.erl @@ -0,0 +1,228 @@ +%%%------------------------------------------------------------------- +%%% @author Dan Gudmundsson +%%% @copyright (C) 2010, Dan Gudmundsson +%%% @doc +%%% +%%% @end +%%% Created : 22 Jun 2010 by Dan Gudmundsson +%%%------------------------------------------------------------------- +-module(inet_proxy_dist). + +-export([childspecs/0, listen/1, accept/1, accept_connection/5, + setup/5, close/1, select/1, is_node_name/1, tick/1]). + +-include_lib("kernel/src/net_address.hrl"). +-include_lib("kernel/src/dist.hrl"). +-include_lib("kernel/src/dist_util.hrl"). + +-import(error_logger,[error_msg/2]). + +childspecs() -> + io:format("childspecs called~n",[]), + {ok, [{proxy_server,{proxy_server, start_link, []}, + permanent, 2000, worker, [proxy_server]}]}. + +select(Node) -> + io:format("Select called~n",[]), + inet_ssl_dist:select(Node). + +is_node_name(Name) -> + io:format("is_node_name~n",[]), + inet_ssl_dist:is_node_name(Name). + +listen(Name) -> + io:format("listen called~n",[]), + gen_server:call(proxy_server, {listen, Name}, infinity). + +accept(Listen) -> + io:format("accept called~n",[]), + gen_server:call(proxy_server, {accept, Listen}, infinity). + +accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) -> + io:format("accept_connection called ~n",[]), + Kernel = self(), + spawn_link(fun() -> do_accept(Kernel, AcceptPid, Socket, + MyNode, Allowed, SetupTime) end). + +setup(Node, Type, MyNode, LongOrShortNames,SetupTime) -> + io:format("setup called~n",[]), + Kernel = self(), + spawn(fun() -> do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) end). + +do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> + [Name, Address] = splitnode(Node, LongOrShortNames), + case inet:getaddr(Address, inet) of + {ok, Ip} -> + Timer = dist_util:start_timer(SetupTime), + case erl_epmd:port_please(Name, Ip) of + {port, TcpPort, Version} -> + ?trace("port_please(~p) -> version ~p~n", + [Node,Version]), + dist_util:reset_timer(Timer), + case gen_server:call(proxy_server, {connect, Ip, TcpPort}, infinity) of + {ok, Socket} -> + HSData = #hs_data{ + kernel_pid = Kernel, + other_node = Node, + this_node = MyNode, + socket = Socket, + timer = Timer, + this_flags = 0, + other_version = Version, + f_send = fun(S,D) -> + io:format("Kernel call send~n",[]), + gen_tcp:send(S,D) + end, + f_recv = fun(S,N,T) -> + io:format("Kernel call receive~n",[]), + gen_tcp:recv(S,N,T) + end, + f_setopts_pre_nodeup = + fun(S) -> + io:format("Kernel pre nodeup~n",[]), + inet:setopts(S, [{active, false}, {packet, 4}]) + end, + f_setopts_post_nodeup = + fun(S) -> + io:format("Kernel post nodeup~n",[]), + inet:setopts(S, [{deliver, port},{active, true}]) + end, + f_getll = fun(S) -> inet:getll(S) end, + f_address = + fun(_,_) -> + #net_address{address = {Ip,TcpPort}, + host = Address, + protocol = proxy, + family = inet} + end, + mf_tick = fun(S) -> gen_tcp:send(S, <<>>) end, + mf_getstat = fun(S) -> + {ok, Stats} = inet:getstat(S, [recv_cnt, send_cnt, send_pend]), + R = proplists:get_value(recv_cnt, Stats, 0), + W = proplists:get_value(send_cnt, Stats, 0), + P = proplists:get_value(send_pend, Stats, 0), + {ok, R,W,P} + end, + request_type = Type + }, + dist_util:handshake_we_started(HSData); + _ -> + %% Other Node may have closed since + %% port_please ! + ?trace("other node (~p) " + "closed since port_please.~n", + [Node]), + ?shutdown(Node) + end; + _ -> + ?trace("port_please (~p) " + "failed.~n", [Node]), + ?shutdown(Node) + end; + _Other -> + ?trace("inet_getaddr(~p) " + "failed (~p).~n", [Node,Other]), + ?shutdown(Node) + end. + +close(Socket) -> + io:format("close called~n",[]), + gen_tcp:close(Socket), + ok. + +do_accept(Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> + process_flag(priority, max), + io:format("~p: in do_accept~n", [self()]), + receive + {AcceptPid, controller} -> + io:format("~p: do_accept controller~n", [self()]), + Timer = dist_util:start_timer(SetupTime), + case check_ip(Socket) of + true -> + HSData = #hs_data{ + kernel_pid = Kernel, + this_node = MyNode, + socket = Socket, + timer = Timer, + this_flags = 0, + allowed = Allowed, + f_send = fun(S,D) -> + io:format("Kernel call send~n",[]), + gen_tcp:send(S,D) end, + f_recv = fun(S,N,T) -> + io:format("Kernel call receive~n",[]), + gen_tcp:recv(S,N,T) end, + f_setopts_pre_nodeup = + fun(S) -> + io:format("Kernel pre nodeup~n",[]), + inet:setopts(S, [{active, false}, {packet, 4}]) + end, + f_setopts_post_nodeup = + fun(S) -> + io:format("Kernel post nodeup~n",[]), + inet:setopts(S, [{deliver, port},{active, true}]) + end, + f_getll = fun(S) -> inet:getll(S) end, + f_address = fun get_remote_id/2, + mf_tick = fun(S) -> gen_tcp:send(S, <<>>) end, + mf_getstat = fun(S) -> + {ok, Stats} = inet:getstat(S, [recv_cnt, send_cnt, send_pend]), + R = proplists:get_value(recv_cnt, Stats, 0), + W = proplists:get_value(send_cnt, Stats, 0), + P = proplists:get_value(send_pend, Stats, 0), + {ok, R,W,P} + end + }, + dist_util:handshake_other_started(HSData); + {false,IP} -> + error_logger:error_msg("** Connection attempt from " + "disallowed IP ~w ** ~n", [IP]), + ?shutdown(no_node) + end + end. + +get_remote_id(Socket, Node) -> + gen_server:call(proxy_server, {get_remote_id, {Socket,Node}}, infinity). + +tick(Socket) -> + gen_tcp:send(Socket, <<>>). + +check_ip(_) -> + true. + + +%% If Node is illegal terminate the connection setup!! +splitnode(Node, LongOrShortNames) -> + case split_node(atom_to_list(Node), $@, []) of + [Name|Tail] when Tail =/= [] -> + Host = lists:append(Tail), + case split_node(Host, $., []) of + [_] when LongOrShortNames == longnames -> + error_msg("** System running to use " + "fully qualified " + "hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown(Node); + [_, _ | _] when LongOrShortNames == shortnames -> + error_msg("** System NOT running to use fully qualified " + "hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown(Node); + _ -> + [Name, Host] + end; + [_] -> + error_msg("** Nodename ~p illegal, no '@' character **~n", + [Node]), + ?shutdown(Node); + _ -> + error_msg("** Nodename ~p illegal **~n", [Node]), + ?shutdown(Node) + end. + +split_node([Chr|T], Chr, Ack) -> [lists:reverse(Ack)|split_node(T, Chr, [])]; +split_node([H|T], Chr, Ack) -> split_node(T, Chr, [H|Ack]); +split_node([], _, Ack) -> [lists:reverse(Ack)]. + diff --git a/lib/ssl/proxy_server.erl b/lib/ssl/proxy_server.erl new file mode 100644 index 0000000000..9b0d1f2400 --- /dev/null +++ b/lib/ssl/proxy_server.erl @@ -0,0 +1,218 @@ +%%%------------------------------------------------------------------- +%%% @author Dan Gudmundsson +%%% @copyright (C) 2010, Dan Gudmundsson +%%% @doc start server with -proto_dist inet_proxy and net_kernel:start([s@faenor, shortnames]). +%%% +%%% @end +%%% Created : 22 Jun 2010 by Dan Gudmundsson +%%%------------------------------------------------------------------- +-module(proxy_server). + +-export([init/1, start_link/0, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-include_lib("kernel/src/net_address.hrl"). +-include_lib("kernel/src/dist.hrl"). +-include_lib("kernel/src/dist_util.hrl"). + +-record(state, + {listen, + accept_loop + }). + +start_link() -> + gen_server:start_link({local, proxy_server}, proxy_server, [], []). + +init([]) -> + io:format("~p: init~n",[self()]), + process_flag(priority, max), + {ok, #state{}}. + +handle_call(What = {listen, Name}, _From, State) -> + io:format("~p: call listen ~p~n",[self(), What]), + case gen_tcp:listen(0, [{active, false}, {packet,2}]) of + {ok, Socket} -> + {ok, World} = gen_tcp:listen(0, [{active, false}, binary, {packet,2}]), + TcpAddress = get_tcp_address(Socket), + WorldTcpAddress = get_tcp_address(World), + {_,Port} = WorldTcpAddress#net_address.address, + {ok, Creation} = erl_epmd:register_node(Name, Port), + {reply, {ok, {Socket, TcpAddress, Creation}}, + State#state{listen={Socket, World}}}; + Error -> + {reply, Error, State} + end; + +handle_call(What = {accept, Listen}, {From, _}, State = #state{listen={_, World}}) -> + io:format("~p: call accept ~p~n",[self(), What]), + Self = self(), + ErtsPid = spawn_link(fun() -> accept_loop(Self, erts, Listen, From) end), + WorldPid = spawn_link(fun() -> accept_loop(Self, world, World, Listen) end), + {reply, ErtsPid, State#state{accept_loop={ErtsPid, WorldPid}}}; + +handle_call({connect, Ip, Port}, {From, _}, State) -> + Me = self(), + Pid = spawn_link(fun() -> setup_proxy(Ip, Port, Me) end), + receive + {Pid, go_ahead, LPort} -> + Res = {ok, Socket} = try_connect(LPort), + ok = gen_tcp:controlling_process(Socket, From), + {reply, Res, State}; + {Pid, Error} -> + {reply, Error, State} + end; + +handle_call({get_remote_id, {Socket,_Node}}, _From, State) -> + Address = get_tcp_address(Socket), + io:format("~p: get_remote_id ~p~n",[self(), Address]), + {reply, Address, State}; + +handle_call(What, _From, State) -> + io:format("~p: call ~p~n",[self(), What]), + {reply, ok, State}. + +handle_cast(What, State) -> + io:format("~p: cast ~p~n",[self(), What]), + {noreply, State}. + +handle_info(What, State) -> + io:format("~p: info ~p~n",[self(), What]), + {noreply, State}. + +terminate(_Reason, _St) -> + ok. + +code_change(_OldVsn, St, _Extra) -> + {ok, St}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +get_tcp_address(Socket) -> + {ok, Address} = inet:sockname(Socket), + {ok, Host} = inet:gethostname(), + #net_address{ + address = Address, + host = Host, + protocol = proxy, + family = inet + }. + +accept_loop(Proxy, Type, Listen, Extra) -> + process_flag(priority, max), + case gen_tcp:accept(Listen) of + {ok, Socket} -> + case Type of + erts -> + io:format("~p: erts accept~n",[self()]), + Extra ! {accept,self(),Socket,inet,proxy}, + receive + {_Kernel, controller, Pid} -> + ok = gen_tcp:controlling_process(Socket, Pid), + Pid ! {self(), controller}; + {_Kernel, unsupported_protocol} -> + exit(unsupported_protocol) + end; + _ -> + io:format("~p: world accept~n",[self()]), + PairHandler = spawn(fun() -> setup_connection(Socket, Extra) end), + ok = gen_tcp:controlling_process(Socket, PairHandler) + end, + accept_loop(Proxy, Type, Listen, Extra); + Error -> + exit(Error) + end. + + +try_connect(Port) -> + case gen_tcp:connect({127,0,0,1}, Port, [{active, false}, {packet,2}]) of + R = {ok, _S} -> + R; + {error, _R} -> + io:format("Failed ~p~n",[_R]), + try_connect(Port) + end. + +setup_proxy(Ip, Port, Parent) -> + case gen_tcp:connect(Ip, Port, [{active, true}, binary, {packet,2}]) of + {ok, World} -> + {ok, ErtsL} = gen_tcp:listen(0, [{active, true}, binary, {packet,2}]), + #net_address{address={_,LPort}} = get_tcp_address(ErtsL), + Parent ! {self(), go_ahead, LPort}, + case gen_tcp:accept(ErtsL) of + {ok, Erts} -> + %% gen_tcp:close(ErtsL), + io:format("World ~p Erts ~p~n",[World, Erts]), + loop_conn_setup(World, Erts); + Err -> + Parent ! {self(), Err} + end; + Err -> + Parent ! {self(), Err} + end. + +setup_connection(World, ErtsListen) -> + io:format("Setup connection ~n",[]), + TcpAddress = get_tcp_address(ErtsListen), + {_Addr,Port} = TcpAddress#net_address.address, + {ok, Erts} = gen_tcp:connect({127,0,0,1}, Port, [{active, true}, binary, {packet,2}]), + inet:setopts(World, [{active,true}, {packet, 2}]), + io:format("~p ~n",[?LINE]), + loop_conn_setup(World, Erts). + +loop_conn_setup(World, Erts) -> + receive + {tcp, World, Data = <>} -> + gen_tcp:send(Erts, Data), + io:format("Handshake finished World -> Erts ~p ~c~n",[size(Data), a]), + inet:setopts(World, [{packet, 4}]), + inet:setopts(Erts, [{packet, 4}]), + loop_conn(World, Erts); + {tcp, Erts, Data = <>} -> + gen_tcp:send(World, Data), + io:format("Handshake finished Erts -> World ~p ~c~n",[size(Data), a]), + inet:setopts(World, [{packet, 4}]), + inet:setopts(Erts, [{packet, 4}]), + loop_conn(World, Erts); + + {tcp, World, Data = <>} -> + gen_tcp:send(Erts, Data), + io:format("Handshake World -> Erts ~p ~c~n",[size(Data), H]), + loop_conn_setup(World, Erts); + {tcp, Erts, Data = <>} -> + gen_tcp:send(World, Data), + io:format("Handshake Erts -> World ~p ~c~n",[size(Data), H]), + loop_conn_setup(World, Erts); + {tcp, World, Data} -> + gen_tcp:send(Erts, Data), + io:format("World -> Erts ~p <<>>~n",[size(Data)]), + loop_conn(World, Erts); + {tcp, Erts, Data} -> + gen_tcp:send(World, Data), + io:format("Erts -> World ~p <<>>~n",[size(Data)]), + loop_conn(World, Erts); + Other -> + io:format("~p ~p~n",[?LINE, Other]) + end. + + +loop_conn(World, Erts) -> + receive + {tcp, World, Data = <>} -> + gen_tcp:send(Erts, Data), + io:format("World -> Erts ~p ~c~n",[size(Data), H]), + loop_conn(World, Erts); + {tcp, Erts, Data = <>} -> + gen_tcp:send(World, Data), + io:format("Erts -> World ~p ~c~n",[size(Data), H]), + loop_conn(World, Erts); + {tcp, World, Data} -> + gen_tcp:send(Erts, Data), + io:format("World -> Erts ~p <<>>~n",[size(Data)]), + loop_conn(World, Erts); + {tcp, Erts, Data} -> + gen_tcp:send(World, Data), + io:format("Erts -> World ~p <<>>~n",[size(Data)]), + loop_conn(World, Erts); + + Other -> + io:format("~p ~p~n",[?LINE, Other]) + end. diff --git a/lib/ssl/src/inet_ssl_dist.erl b/lib/ssl/src/inet_ssl_dist.erl index 6c0fbc0618..f4bcb593d0 100644 --- a/lib/ssl/src/inet_ssl_dist.erl +++ b/lib/ssl/src/inet_ssl_dist.erl @@ -31,9 +31,7 @@ -import(error_logger,[error_msg/2]). --include("net_address.hrl"). - - +-include_lib("kernel/src/net_address.hrl"). -define(to_port(Socket, Data, Opts), case ssl_prim:send(Socket, Data, Opts) of @@ -44,9 +42,8 @@ R end). - --include("dist.hrl"). --include("dist_util.hrl"). +-include_lib("kernel/src/dist.hrl"). +-include_lib("kernel/src/dist_util.hrl"). %% ------------------------------------------------------------- %% This function should return a valid childspec, so that -- cgit v1.2.3 From 50392cec6e5bda7ac62abff3313eae551b006612 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Wed, 25 Aug 2010 15:23:30 +0200 Subject: Use ssl instead of being a proxy command --- lib/ssl/client.pem | 34 ++++++++++++ lib/ssl/inet_proxy_dist.erl | 11 ++-- lib/ssl/proxy_server.erl | 132 +++++++++++++++++++++++++++++++------------- lib/ssl/server.pem | 34 ++++++++++++ 4 files changed, 167 insertions(+), 44 deletions(-) create mode 100644 lib/ssl/client.pem create mode 100644 lib/ssl/server.pem (limited to 'lib/ssl') diff --git a/lib/ssl/client.pem b/lib/ssl/client.pem new file mode 100644 index 0000000000..90d88a259a --- /dev/null +++ b/lib/ssl/client.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIICfjCCAeegAwIBAgIFZ0ez/tEwDQYJKoZIhvcNAQEFBQAwdzEeMBwGCSqGSIb3 +DQEJARYPZGd1ZEBlcmxhbmcub3JnMQ0wCwYDVQQDEwRkZ3VkMRIwEAYDVQQHEwlT +dG9ja2hvbG0xCzAJBgNVBAYTAlNFMQ8wDQYDVQQKEwZlcmxhbmcxFDASBgNVBAsT +C3Rlc3RpbmcgZGVwMCIYDzIwMTAwODI1MDAwMDAwWhgPMjAxMDA5MDEwMDAwMDBa +MHcxHjAcBgkqhkiG9w0BCQEWD2RndWRAZXJsYW5nLm9yZzENMAsGA1UEAxMEZGd1 +ZDESMBAGA1UEBxMJU3RvY2tob2xtMQswCQYDVQQGEwJTRTEPMA0GA1UEChMGZXJs +YW5nMRQwEgYDVQQLEwt0ZXN0aW5nIGRlcDCBnjANBgkqhkiG9w0BAQEFAAOBjAAw +gYgCgYBk/3JXHJ02+rqJ1qJqtMtBhPh2HKRhy7SHFhIg0LbalsH+B0pXcP6c3b9p +nY68FEqhB69jJfFgb98tW68+qDDh4aWeJc3cw3NslVvJXB5ADWsewrUoXx0hTHiL +T/f+RC5BBvnfAZAJYXTxpoukiVZJvVuq7o/rVWDpQPfy8MNr/QIDAQABoxMwETAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAGXTeYefvpqgs6JcLTw8 +Hem8YrZIK1Icgu2QYRVZHuqFf45MBqrEUHHXNxDIWXD7U6shWezw5laB+5AcW8sq +9RI+3CYU0wOb0XgFQmcIfCMFbhKvTdB5S7zjy3B39B264/cRBZXFdgAeILEDsBk0 +zgFSLCMULbtTxF+3zNJ/Fclq +-----END CERTIFICATE----- + +XXX Following key assumed not encrypted +-----BEGIN RSA PRIVATE KEY----- +MIICWgIBAAKBgGT/clccnTb6uonWomq0y0GE+HYcpGHLtIcWEiDQttqWwf4HSldw +/pzdv2mdjrwUSqEHr2Ml8WBv3y1brz6oMOHhpZ4lzdzDc2yVW8lcHkANax7CtShf +HSFMeItP9/5ELkEG+d8BkAlhdPGmi6SJVkm9W6ruj+tVYOlA9/Lww2v9AgMBAAEC +gYAH8urm3EOrXhRsYM4ro8sTfwmnEh4F7Ghq8Vu/5W1eytq9yYkaVLRVWEaGY3Ym +a1psThSJsyTKOEPSaBLk1YvzQeITmgHLGpJ11qJOMZO6mvj7lSQBdCc2vuusajtw +zFOaGe6MOrFEetOKBjnGri8byrEfqJogEH2+aiPEog40KQJBAKYtiPFqh91oC3qH +AQ1uJodhyQTrTwSBltqN1Hp9nuE6ydfNWBd1aC9sIiDY1IjUhW89eJYEYvotougQ +ntU+8UcCQQCblsff2IGl8SdHfhWjqT3Rsg4RMKgDH52Ym9U2kI5y6Z4E9G9tQXuR +6/tohmWX/j6CFiORuz7FhVIQ7b4HuPqbAkBVuDthvMAk15zEMYu7b8x0HV7iKLdz +7ZzxVCP8o3wnVnnz1brRLwD1JWRdaTwI8Qd7oEvppo2f25ai+p/UBEnVAkAuU9Ur +59Gi0Y16kiZrVudbWwMpRy2f0HgiirQPzTc9LCarHwVWqNrcdkGju/DgMwn1vhXV +PMXSFoJ7G+8raX7lAkA4Ck9izAs08+37jmhRxcmYpOjdCxA9yWrwALJysYKlTw4N +Qwb7Q4uDQz6EunuTGfiXZz7Oep/0L+BXRJmvweBX +-----END RSA PRIVATE KEY----- + diff --git a/lib/ssl/inet_proxy_dist.erl b/lib/ssl/inet_proxy_dist.erl index 6308deabe6..9e415def3e 100644 --- a/lib/ssl/inet_proxy_dist.erl +++ b/lib/ssl/inet_proxy_dist.erl @@ -9,7 +9,7 @@ -module(inet_proxy_dist). -export([childspecs/0, listen/1, accept/1, accept_connection/5, - setup/5, close/1, select/1, is_node_name/1, tick/1]). + setup/5, close/1, select/1, is_node_name/1]). -include_lib("kernel/src/net_address.hrl"). -include_lib("kernel/src/dist.hrl"). @@ -126,7 +126,11 @@ do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> end. close(Socket) -> - io:format("close called~n",[]), + try + erlang:error(foo) + catch _:_ -> + io:format("close called ~p ~p~n",[Socket, erlang:get_stacktrace()]) + end, gen_tcp:close(Socket), ok. @@ -184,9 +188,6 @@ do_accept(Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> get_remote_id(Socket, Node) -> gen_server:call(proxy_server, {get_remote_id, {Socket,Node}}, infinity). -tick(Socket) -> - gen_tcp:send(Socket, <<>>). - check_ip(_) -> true. diff --git a/lib/ssl/proxy_server.erl b/lib/ssl/proxy_server.erl index 9b0d1f2400..38ec0ef0d8 100644 --- a/lib/ssl/proxy_server.erl +++ b/lib/ssl/proxy_server.erl @@ -20,6 +20,9 @@ accept_loop }). +-define(PPRE, 4). +-define(PPOST, 4). + start_link() -> gen_server:start_link({local, proxy_server}, proxy_server, [], []). @@ -30,9 +33,9 @@ init([]) -> handle_call(What = {listen, Name}, _From, State) -> io:format("~p: call listen ~p~n",[self(), What]), - case gen_tcp:listen(0, [{active, false}, {packet,2}]) of + case gen_tcp:listen(0, [{active, false}, {packet,?PPRE}]) of {ok, Socket} -> - {ok, World} = gen_tcp:listen(0, [{active, false}, binary, {packet,2}]), + {ok, World} = gen_tcp:listen(0, [{active, false}, binary, {packet,?PPRE}]), TcpAddress = get_tcp_address(Socket), WorldTcpAddress = get_tcp_address(World), {_,Port} = WorldTcpAddress#net_address.address, @@ -98,10 +101,10 @@ get_tcp_address(Socket) -> accept_loop(Proxy, Type, Listen, Extra) -> process_flag(priority, max), - case gen_tcp:accept(Listen) of - {ok, Socket} -> - case Type of - erts -> + case Type of + erts -> + case gen_tcp:accept(Listen) of + {ok, Socket} -> io:format("~p: erts accept~n",[self()]), Extra ! {accept,self(),Socket,inet,proxy}, receive @@ -111,19 +114,26 @@ accept_loop(Proxy, Type, Listen, Extra) -> {_Kernel, unsupported_protocol} -> exit(unsupported_protocol) end; - _ -> + Error -> + exit(Error) + end; + world -> + case gen_tcp:accept(Listen) of + {ok, Socket} -> + Opts = get_ssl_options(server), + {ok, SslSocket} = ssl:ssl_accept(Socket, Opts), io:format("~p: world accept~n",[self()]), - PairHandler = spawn(fun() -> setup_connection(Socket, Extra) end), - ok = gen_tcp:controlling_process(Socket, PairHandler) - end, - accept_loop(Proxy, Type, Listen, Extra); - Error -> - exit(Error) - end. + PairHandler = spawn_link(fun() -> setup_connection(SslSocket, Extra) end), + ok = ssl:controlling_process(SslSocket, PairHandler); + Error -> + exit(Error) + end + end, + accept_loop(Proxy, Type, Listen, Extra). try_connect(Port) -> - case gen_tcp:connect({127,0,0,1}, Port, [{active, false}, {packet,2}]) of + case gen_tcp:connect({127,0,0,1}, Port, [{active, false}, {packet,?PPRE}]) of R = {ok, _S} -> R; {error, _R} -> @@ -132,9 +142,11 @@ try_connect(Port) -> end. setup_proxy(Ip, Port, Parent) -> - case gen_tcp:connect(Ip, Port, [{active, true}, binary, {packet,2}]) of + process_flag(trap_exit, true), + Opts = get_ssl_options(client), + case ssl:connect(Ip, Port, [{active, true}, binary, {packet,?PPRE}] ++ Opts) of {ok, World} -> - {ok, ErtsL} = gen_tcp:listen(0, [{active, true}, binary, {packet,2}]), + {ok, ErtsL} = gen_tcp:listen(0, [{active, true}, binary, {packet,?PPRE}]), #net_address{address={_,LPort}} = get_tcp_address(ErtsL), Parent ! {self(), go_ahead, LPort}, case gen_tcp:accept(ErtsL) of @@ -150,69 +162,111 @@ setup_proxy(Ip, Port, Parent) -> end. setup_connection(World, ErtsListen) -> + process_flag(trap_exit, true), io:format("Setup connection ~n",[]), TcpAddress = get_tcp_address(ErtsListen), {_Addr,Port} = TcpAddress#net_address.address, - {ok, Erts} = gen_tcp:connect({127,0,0,1}, Port, [{active, true}, binary, {packet,2}]), - inet:setopts(World, [{active,true}, {packet, 2}]), + {ok, Erts} = gen_tcp:connect({127,0,0,1}, Port, [{active, true}, binary, {packet,?PPRE}]), + ssl:setopts(World, [{active,true}, {packet,?PPRE}]), io:format("~p ~n",[?LINE]), loop_conn_setup(World, Erts). loop_conn_setup(World, Erts) -> receive - {tcp, World, Data = <>} -> + {ssl, World, Data = <<$a, _/binary>>} -> gen_tcp:send(Erts, Data), - io:format("Handshake finished World -> Erts ~p ~c~n",[size(Data), a]), - inet:setopts(World, [{packet, 4}]), - inet:setopts(Erts, [{packet, 4}]), + io:format("Handshake finished World -> Erts ~p ~c~n",[size(Data), $a]), + ssl:setopts(World, [{packet,?PPOST}]), + inet:setopts(Erts, [{packet,?PPOST}]), loop_conn(World, Erts); - {tcp, Erts, Data = <>} -> - gen_tcp:send(World, Data), - io:format("Handshake finished Erts -> World ~p ~c~n",[size(Data), a]), - inet:setopts(World, [{packet, 4}]), - inet:setopts(Erts, [{packet, 4}]), + {tcp, Erts, Data = <<$a, _/binary>>} -> + ssl:send(World, Data), + io:format("Handshake finished Erts -> World ~p ~c~n",[size(Data), $a]), + ssl:setopts(World, [{packet,?PPOST}]), + inet:setopts(Erts, [{packet,?PPOST}]), loop_conn(World, Erts); - {tcp, World, Data = <>} -> + {ssl, World, Data = <>} -> gen_tcp:send(Erts, Data), io:format("Handshake World -> Erts ~p ~c~n",[size(Data), H]), loop_conn_setup(World, Erts); {tcp, Erts, Data = <>} -> - gen_tcp:send(World, Data), + ssl:send(World, Data), io:format("Handshake Erts -> World ~p ~c~n",[size(Data), H]), loop_conn_setup(World, Erts); - {tcp, World, Data} -> + {ssl, World, Data} -> gen_tcp:send(Erts, Data), io:format("World -> Erts ~p <<>>~n",[size(Data)]), - loop_conn(World, Erts); + loop_conn_setup(World, Erts); {tcp, Erts, Data} -> - gen_tcp:send(World, Data), + ssl:send(World, Data), io:format("Erts -> World ~p <<>>~n",[size(Data)]), - loop_conn(World, Erts); + loop_conn_setup(World, Erts); Other -> io:format("~p ~p~n",[?LINE, Other]) end. - loop_conn(World, Erts) -> receive - {tcp, World, Data = <>} -> + {ssl, World, Data = <>} -> gen_tcp:send(Erts, Data), io:format("World -> Erts ~p ~c~n",[size(Data), H]), loop_conn(World, Erts); {tcp, Erts, Data = <>} -> - gen_tcp:send(World, Data), + ssl:send(World, Data), io:format("Erts -> World ~p ~c~n",[size(Data), H]), loop_conn(World, Erts); - {tcp, World, Data} -> + {ssl, World, Data} -> gen_tcp:send(Erts, Data), io:format("World -> Erts ~p <<>>~n",[size(Data)]), loop_conn(World, Erts); {tcp, Erts, Data} -> - gen_tcp:send(World, Data), + ssl:send(World, Data), io:format("Erts -> World ~p <<>>~n",[size(Data)]), loop_conn(World, Erts); Other -> io:format("~p ~p~n",[?LINE, Other]) end. + +get_ssl_options(Type) -> + case init:get_argument(ssl_dist_opt) of + {ok, Args} -> + ssl_options(Type, Args); + _ -> + [] + end. + +ssl_options(_,[]) -> + []; +ssl_options(server, [["server_certfile", Value]|T]) -> + [{certfile, Value} | ssl_options(server,T)]; +ssl_options(client, [["client_certfile", Value]|T]) -> + [{certfile, Value} | ssl_options(client,T)]; +ssl_options(server, [["server_cacertfile", Value]|T]) -> + [{cacertfile, Value} | ssl_options(server,T)]; +ssl_options(server, [["server_keyfile", Value]|T]) -> + [{keyfile, Value} | ssl_options(server,T)]; +ssl_options(Type, [["client_certfile", _Value]|T]) -> + ssl_options(Type,T); +ssl_options(Type, [["server_certfile", _Value]|T]) -> + ssl_options(Type,T); +ssl_options(Type, [[Item, Value]|T]) -> + [{atomize(Item),fixup(Value)} | ssl_options(Type,T)]; +ssl_options(Type, [[Item,Value |T1]|T2]) -> + ssl_options(atomize(Type),[[Item,Value],T1|T2]); +ssl_options(_,_) -> + exit(malformed_ssl_dist_opt). + +fixup(Value) -> + case catch list_to_integer(Value) of + {'EXIT',_} -> + Value; + Int -> + Int + end. + +atomize(List) when is_list(List) -> + list_to_atom(List); +atomize(Atom) when is_atom(Atom) -> + Atom. diff --git a/lib/ssl/server.pem b/lib/ssl/server.pem new file mode 100644 index 0000000000..4e4aae5342 --- /dev/null +++ b/lib/ssl/server.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIICezCCAeSgAwIBAgIFFX2Pz5EwDQYJKoZIhvcNAQEFBQAwczEcMBoGCSqGSIb3 +DQEJARYNQ0FAZXJsYW5nLm9yZzELMAkGA1UEAxMCQ0ExEjAQBgNVBAcTCVN0b2Nr +aG9sbTELMAkGA1UEBhMCU0UxDzANBgNVBAoTBmVybGFuZzEUMBIGA1UECxMLdGVz +dGluZyBkZXAwIhgPMjAxMDA4MjUwMDAwMDBaGA8yMDEwMDkwMTAwMDAwMFowdzEe +MBwGCSqGSIb3DQEJARYPZGd1ZEBlcmxhbmcub3JnMQ0wCwYDVQQDEwRkZ3VkMRIw +EAYDVQQHEwlTdG9ja2hvbG0xCzAJBgNVBAYTAlNFMQ8wDQYDVQQKEwZlcmxhbmcx +FDASBgNVBAsTC3Rlc3RpbmcgZGVwMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB +gQDAu0FFOt/gZUz5DLBtqA/YUNrq+xXevXTsR1I/uxzNS+nYWkMN81W5oI2yXJ08 +LLdat6APru64DWRGQPMn6BTr4ti9l9Nq4jQEY96G2ee+YrB5SAduxkWwg2qyNMb3 +s4OIq56tp+pzty/v8VcapUTn3uKJv3SL0eYWxASD79WmdQIDAQABoxMwETAPBgNV +HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAKaT0GL8gIlnPBJS+81CnQos +cMoZll7QdXLGxzSN1laxDrvHOHE9SAtrx1EJHcv8nh/jvhL715bVbnuaoAtgxQoW +KF3A7DziDEYhkZd20G80rC+i6rx3n/+9T51RPhzymNbgSQhuVBFs0JXL73HPEqeZ +wskDuSyiV8DCDjiwlgpq +-----END CERTIFICATE----- + +XXX Following key assumed not encrypted +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDAu0FFOt/gZUz5DLBtqA/YUNrq+xXevXTsR1I/uxzNS+nYWkMN +81W5oI2yXJ08LLdat6APru64DWRGQPMn6BTr4ti9l9Nq4jQEY96G2ee+YrB5SAdu +xkWwg2qyNMb3s4OIq56tp+pzty/v8VcapUTn3uKJv3SL0eYWxASD79WmdQIDAQAB +AoGAERwOjVDOsyMLFEj2GKYE0hVLefTUWjPDf35NZO79fZQxrE1HCqQBhjskmSLz +qnXlyR3oDbxf4OL/deUqMO6/fJHVOD7O9UQRK26f01IZoTq0WmBMFP2C7upafzgx +9gxddQ7j9B6rqz2agV/YUpvij7hfhXFmV/ogggeuVsyQ0AECQQDNSBH8WMVgky0I +QLa7MfBLsiHQ5FXmVYU6i9C/QUpL7SWu6eV3edAm7xbtcWnqXEMxeC7D9NIAxDhO +VaV21bR1AkEA8Flmsy/XRVPF2rmfz0o1Cc+9m6NZOQAUK9sHAXuL3HoTPcigS+f5 +fHbAGFPDBoolS9qRJs5AcL95majzpDnqAQJAJ/SjK47LvCRpW3XdG0p5DwK4+kO3 +RIHY0LBuDQvUPjsGXqk/9KVNEobu24B7sRYMLhDKaXG5flSy8OxSrHKkEQJBAKvg +ItMs+RK4r5qUd7Xy6S7VAlCUZa+fYM1j2gSzZvcJzUy3dfoSL5VUDlbXP3YjwDwY +VwibIfX+12SNL35XdAECQHLGnDKYLO3M7HCPf9Yp8tiOmD9mASKcXd3NdBg5mD/l +oOlKIQhdAQS0BLFhyASfb6hzY0Mj8B2Nq5Z3sq8yD1s= +-----END RSA PRIVATE KEY----- + -- cgit v1.2.3 From 5b0a4180582921fe3b61b430f0c87d9a68ba6da8 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Thu, 30 Jun 2011 15:38:55 +0200 Subject: First fully working version --- lib/ssl/client.pem | 34 -- lib/ssl/doc/src/ssl_distribution.xml | 209 ++++++------ lib/ssl/doc/src/ssl_protocol.xml | 16 +- lib/ssl/inet_proxy_dist.erl | 229 ------------- lib/ssl/proxy_server.erl | 272 ---------------- lib/ssl/server.pem | 34 -- lib/ssl/src/Makefile | 5 +- lib/ssl/src/inet_ssl_dist.erl | 6 +- lib/ssl/src/inet_tls_dist.erl | 275 ++++++++++++++++ lib/ssl/src/ssl.app.src | 3 + lib/ssl/src/ssl.erl | 8 +- lib/ssl/src/ssl_connection.erl | 24 +- lib/ssl/src/ssl_connection_sup.erl | 12 +- lib/ssl/src/ssl_dist_sup.erl | 84 +++++ lib/ssl/src/ssl_internal.hrl | 4 +- lib/ssl/src/ssl_manager.erl | 28 +- lib/ssl/src/ssl_sup.erl | 35 +- lib/ssl/src/ssl_tls_dist_proxy.erl | 326 +++++++++++++++++++ lib/ssl/test/Makefile | 1 + lib/ssl/test/ssl_dist_SUITE.erl | 603 +++++++++++++++++++++++++++++++++++ 20 files changed, 1493 insertions(+), 715 deletions(-) delete mode 100644 lib/ssl/client.pem delete mode 100644 lib/ssl/inet_proxy_dist.erl delete mode 100644 lib/ssl/proxy_server.erl delete mode 100644 lib/ssl/server.pem create mode 100644 lib/ssl/src/inet_tls_dist.erl create mode 100644 lib/ssl/src/ssl_dist_sup.erl create mode 100644 lib/ssl/src/ssl_tls_dist_proxy.erl create mode 100644 lib/ssl/test/ssl_dist_SUITE.erl (limited to 'lib/ssl') diff --git a/lib/ssl/client.pem b/lib/ssl/client.pem deleted file mode 100644 index 90d88a259a..0000000000 --- a/lib/ssl/client.pem +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICfjCCAeegAwIBAgIFZ0ez/tEwDQYJKoZIhvcNAQEFBQAwdzEeMBwGCSqGSIb3 -DQEJARYPZGd1ZEBlcmxhbmcub3JnMQ0wCwYDVQQDEwRkZ3VkMRIwEAYDVQQHEwlT -dG9ja2hvbG0xCzAJBgNVBAYTAlNFMQ8wDQYDVQQKEwZlcmxhbmcxFDASBgNVBAsT -C3Rlc3RpbmcgZGVwMCIYDzIwMTAwODI1MDAwMDAwWhgPMjAxMDA5MDEwMDAwMDBa -MHcxHjAcBgkqhkiG9w0BCQEWD2RndWRAZXJsYW5nLm9yZzENMAsGA1UEAxMEZGd1 -ZDESMBAGA1UEBxMJU3RvY2tob2xtMQswCQYDVQQGEwJTRTEPMA0GA1UEChMGZXJs -YW5nMRQwEgYDVQQLEwt0ZXN0aW5nIGRlcDCBnjANBgkqhkiG9w0BAQEFAAOBjAAw -gYgCgYBk/3JXHJ02+rqJ1qJqtMtBhPh2HKRhy7SHFhIg0LbalsH+B0pXcP6c3b9p -nY68FEqhB69jJfFgb98tW68+qDDh4aWeJc3cw3NslVvJXB5ADWsewrUoXx0hTHiL -T/f+RC5BBvnfAZAJYXTxpoukiVZJvVuq7o/rVWDpQPfy8MNr/QIDAQABoxMwETAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAGXTeYefvpqgs6JcLTw8 -Hem8YrZIK1Icgu2QYRVZHuqFf45MBqrEUHHXNxDIWXD7U6shWezw5laB+5AcW8sq -9RI+3CYU0wOb0XgFQmcIfCMFbhKvTdB5S7zjy3B39B264/cRBZXFdgAeILEDsBk0 -zgFSLCMULbtTxF+3zNJ/Fclq ------END CERTIFICATE----- - -XXX Following key assumed not encrypted ------BEGIN RSA PRIVATE KEY----- -MIICWgIBAAKBgGT/clccnTb6uonWomq0y0GE+HYcpGHLtIcWEiDQttqWwf4HSldw -/pzdv2mdjrwUSqEHr2Ml8WBv3y1brz6oMOHhpZ4lzdzDc2yVW8lcHkANax7CtShf -HSFMeItP9/5ELkEG+d8BkAlhdPGmi6SJVkm9W6ruj+tVYOlA9/Lww2v9AgMBAAEC -gYAH8urm3EOrXhRsYM4ro8sTfwmnEh4F7Ghq8Vu/5W1eytq9yYkaVLRVWEaGY3Ym -a1psThSJsyTKOEPSaBLk1YvzQeITmgHLGpJ11qJOMZO6mvj7lSQBdCc2vuusajtw -zFOaGe6MOrFEetOKBjnGri8byrEfqJogEH2+aiPEog40KQJBAKYtiPFqh91oC3qH -AQ1uJodhyQTrTwSBltqN1Hp9nuE6ydfNWBd1aC9sIiDY1IjUhW89eJYEYvotougQ -ntU+8UcCQQCblsff2IGl8SdHfhWjqT3Rsg4RMKgDH52Ym9U2kI5y6Z4E9G9tQXuR -6/tohmWX/j6CFiORuz7FhVIQ7b4HuPqbAkBVuDthvMAk15zEMYu7b8x0HV7iKLdz -7ZzxVCP8o3wnVnnz1brRLwD1JWRdaTwI8Qd7oEvppo2f25ai+p/UBEnVAkAuU9Ur -59Gi0Y16kiZrVudbWwMpRy2f0HgiirQPzTc9LCarHwVWqNrcdkGju/DgMwn1vhXV -PMXSFoJ7G+8raX7lAkA4Ck9izAs08+37jmhRxcmYpOjdCxA9yWrwALJysYKlTw4N -Qwb7Q4uDQz6EunuTGfiXZz7Oep/0L+BXRJmvweBX ------END RSA PRIVATE KEY----- - diff --git a/lib/ssl/doc/src/ssl_distribution.xml b/lib/ssl/doc/src/ssl_distribution.xml index 7bcc12eb5f..a2c7370ddc 100644 --- a/lib/ssl/doc/src/ssl_distribution.xml +++ b/lib/ssl/doc/src/ssl_distribution.xml @@ -4,7 +4,7 @@
- 20002010 + 20002011 Ericsson AB. All Rights Reserved. @@ -33,36 +33,32 @@

This chapter describes how the Erlang distribution can use SSL to get additional verification and security. - -

Note this - documentation is written for the old ssl implementation and - will be updated for the new one once this functionality is - supported by the new implementation.

Introduction

The Erlang distribution can in theory use almost any connection based protocol as bearer. A module that implements the protocol - specific parts of connection setup is however needed. The + specific parts of the connection setup is however needed. The default distribution module is inet_tcp_dist which is included in the Kernel application. When starting an Erlang node distributed, net_kernel uses this module to setup listen ports and connections.

-

In the SSL application there is an additional distribution - module, inet_ssl_dist which can be used as an + +

In the SSL application there is an additional distribution + module, inet_tls_dist which can be used as an alternative. All distribution connections will be using SSL and all participating Erlang nodes in a distributed system must use this distribution module.

-

The security depends on how the connections are set up, one can - use key files or certificates to just get a encrypted - connection. One can also make the SSL package verify the - certificates of other nodes to get additional security. - Cookies are however always used as they can be used to - differentiate between two different Erlang networks.

+ +

The security level depends on the parameters provided to the + SSL connection setup. Erlang node cookies are however always + used, as they can be used to differentiate between two different + Erlang networks.

Setting up Erlang distribution over SSL involves some simple but necessary steps:

- + + Building boot scripts including the SSL application Specifying the distribution module for net_kernel Specifying security options and other SSL options @@ -77,122 +73,135 @@ SASL application. Refer to the SASL documentations for more information on systools. This is only an example of what can be done.

-

The simplest boot script possible includes only the Kernel + +

The simplest boot script possible includes only the Kernel and STDLIB applications. Such a script is located in the Erlang distributions bin directory. The source for the script can be found under the Erlang installation top directory under - start_clean.rel]]>. Copy that + /start_clean.rel]]>. Copy that script to another location (and preferably another name) - and add the SSL application with its current version number + and add the applications crypto, public_key and SSL with their current version numbers after the STDLIB application.

An example .rel file with SSL added may look like this:

+ -{release, {"OTP APN 181 01","P7A"}, {erts, "5.0"}, - [{kernel,"2.5"}, - {stdlib,"1.8.1"}, - {ssl,"2.2.1"}]}. -

Note that the version numbers surely will differ in your system. - Whenever one of the applications included in the script is - upgraded, the script has to be changed.

-

Assuming the above .rel file is stored in a file - start_ssl.rel in the current directory, a boot script - can be built like this:

- -1> systools:make_script("start_ssl",[]). -

There will now be a file start_ssl.boot in the current - directory. To test the boot script, start Erlang with the - -boot command line parameter specifying this boot script - (with its full path but without the .boot suffix), in - Unix it could look like this:

-

- + +

Note that the version numbers surely will differ in your system. + Whenever one of the applications included in the script is + upgraded, the script has to be changed.

+

Assuming the above .rel file is stored in a file + start_ssl.rel in the current directory, a boot script + can be built like this:

+ + + 1> systools:make_script("start_ssl",[]). + +

There will now be a file start_ssl.boot in the current + directory. To test the boot script, start Erlang with the + -boot command line parameter specifying this boot script + (with its full path but without the .boot suffix), in + Unix it could look like this:

+

+ + whereis(ssl_server). -<0.32.0> ]]> +1> whereis(ssl_manager). +<0.41.0> ]]>

The whereis function call verifies that the SSL application is really started.

-

As an alternative to building a bootscript, one can explicitly - add the path to the ssl ebin directory on the command + +

As an alternative to building a bootscript, one can explicitly + add the path to the SSL ebin directory on the command line. This is done with the command line option -pa. This - works as the ssl application really need not be started for the - distribution to come up, a primitive version of the ssl server - is started by the distribution module itself, so as long as the - primitive code server can reach the code, the distribution will + works as the SSL application does not need to be started for the + distribution to come up, as a clone of the SSL application is + hooked into the kernel application, so as long as the + SSL applications code can be reached, the distribution will start. The -pa method is only recommended for testing purposes.

+ +

Note that the clone of the SSL application is necessary to + enable the use of the SSL code in such an early bootstage as + needed to setup the distribution, however this will make it + impossible to soft upgrade the SSL application.

Specifying distribution module for net_kernel -

The distribution module for SSL is named inet_ssl_dist - and is specified on the command line whit the -proto_dist +

The distribution module for SSL is named inet_tls_dist + and is specified on the command line with the -proto_dist option. The argument to -proto_dist should be the module name without the _dist suffix, so this distribution - module is specified with -proto_dist inet_ssl on the + module is specified with -proto_dist inet_tls on the command line.

+

Extending the command line from above gives us the following:

-$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_ssl -

For the distribution to actually be started, we need to give - the emulator a name as well:

+$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls + +

For the distribution to actually be started, we need to give +the emulator a name as well:

-$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_ssl -sname ssl_test +$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls -sname ssl_test Erlang (BEAM) emulator version 5.0 [source] Eshell V5.0 (abort with ^G) (ssl_test@myhost)1>

Note however that a node started in this way will refuse to talk - to other nodes, as no certificates or key files are supplied + to other nodes, as no ssl parameters are supplied (see below).

-

When the SSL distribution starts, the OTP system is in its - early boot stage, why neither application nor code - are usable. As SSL needs to start a port program in this early - stage, it tries to determine the path to that program from the - primitive code loaders code path. If this fails, one need to - specify the directory where the port program resides. This can - be done either with an environment variable - ERL_SSL_PORTPROGRAM_DIR or with the command line option - -ssl_portprogram_dir. The value should be the directory - where the ssl_esock port program is located. Note that - this option is never needed in a normal Erlang installation.

- Specifying security options and other SSL options -

For SSL to work, you either need certificate files or a - key file. Certificate files can be specified both when working as - client and as server (connecting or accepting).

-

+ Specifying SSL options

For SSL to work, at least + a public key and certificate needs to be specified for the server + side. In the following example the PEM-files consists of two + entries the servers certificate and its private key.

+

On the erl command line one can specify options that the - ssl distribution will add when creation a socket. It is - mandatory to specify at least a key file or client and server - certificates. One can specify any SSL option on the - command line, but must not specify any socket options (like - packet size and such). The SSL options are listed in the - Reference Manual. The only difference between the - options in the reference manual and the ones that can be - specified to the distribution on the command line is that - certfile can (and usually needs to) be specified as - client_certfile and server_certfile. The - client_certfile is used when the distribution initiates a - connection to another node and the server_certfile is used - when accepting a connection from a remote node.

-

The command line argument for specifying the SSL options is named - -ssl_dist_opt and should be followed by an even number of - SSL options/option values. The -ssl_dist_opt argument can - be repeated any number of times.

-

An example command line would now look something like this + SSL distribution will add when creating a socket.

+ +

One can specify the simpler SSL options certfile, keyfile, + password, cacertfile, verify, reuse_sessions, + secure_renegotiation, depth, hibernate_after and ciphers (use old + string format) by adding the prefix server_ or client_ to the + option name. The server can also take the options dhfile and + fail_if_no_peer_cert (also prefixed). + client_-prfixed options are used when the distribution initiates a + connection to another node and the server_-prefixed options are used + when accepting a connection from a remote node.

+ +

More complex options such as verify_fun are not available at + the moment but a mechanism to handle such options may be added in + a future release.

+ +

Raw socket options such as packet and size must not be specified on + the command line

. + +

The command line argument for specifying the SSL options is named + -ssl_dist_opt and should be followed by pairs of + SSL options and their values. The -ssl_dist_opt argument can + be repeated any number of times.

+ +

An example command line would now look something like this (line breaks in the command are for readability, they should not be there when typed):

-$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_ssl - -ssl_dist_opt client_certfile "/home/me/ssl/erlclient.pem" +$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls -ssl_dist_opt server_certfile "/home/me/ssl/erlserver.pem" - -ssl_dist_opt verify 1 depth 1 + -ssl_dist_opt server_secure_renegotiation true client_secure_renegotiate true -sname ssl_test Erlang (BEAM) emulator version 5.0 [source] @@ -211,12 +220,11 @@ Eshell V5.0 (abort with ^G) subsequent invocations of Erlang.

In a Unix (Bourne) shell it could look like this (line breaks for - readability):

+ readability, they should not be there when typed):

-$ ERL_FLAGS="-boot \\"/home/me/ssl/start_ssl\\" -proto_dist inet_ssl - -ssl_dist_opt client_certfile \\"/home/me/ssl/erlclient.pem\\" - -ssl_dist_opt server_certfile \\"/home/me/ssl/erlserver.pem\\" - -ssl_dist_opt verify 1 -ssl_dist_opt depth 1" +$ ERL_FLAGS="-boot /home/me/ssl/start_ssl -proto_dist inet_tls + -ssl_dist_opt server_certfile /home/me/ssl/erlserver.pem + -ssl_dist_opt server_secure_renegotiation true client_secure_renegotiate true" $ export ERL_FLAGS $ erl -sname ssl_test Erlang (BEAM) emulator version 5.0 [source] @@ -227,15 +235,12 @@ Eshell V5.0 (abort with ^G) {progname,["erl "]}, {sname,["ssl_test"]}, {boot,["/home/me/ssl/start_ssl"]}, - {proto_dist,["inet_ssl"]}, - {ssl_dist_opt,["client_certfile","/home/me/ssl/erlclient.pem"]}, + {proto_dist,["inet_tls"]}, {ssl_dist_opt,["server_certfile","/home/me/ssl/erlserver.pem"]}, - {ssl_dist_opt,["verify","1"]}, - {ssl_dist_opt,["depth","1"]}, + {ssl_dist_opt,["server_secure_renegotiation","true", + "client_secure_renegotiate","true"] {home,["/home/me"]}]

The init:get_arguments() call verifies that the correct arguments are supplied to the emulator.

- - diff --git a/lib/ssl/doc/src/ssl_protocol.xml b/lib/ssl/doc/src/ssl_protocol.xml index ca5cc8bc7a..17268a634d 100644 --- a/lib/ssl/doc/src/ssl_protocol.xml +++ b/lib/ssl/doc/src/ssl_protocol.xml @@ -4,7 +4,7 @@
- 20032010 + 20032011 Ericsson AB. All Rights Reserved. @@ -25,18 +25,18 @@ ssl_protocol.xml
-

The erlang ssl application currently supports SSL 3.0 and TLS 1.0 +

The erlang SSL application currently supports SSL 3.0 and TLS 1.0 RFC 2246, and will in the future also support later versions of TLS. SSL 2.0 is not supported.

-

By default erlang ssl is run over the TCP/IP protocol even +

By default erlang SSL is run over the TCP/IP protocol even though you could plug in any other reliable transport protocol with the same API as gen_tcp.

If a client and server wants to use an upgrade mechanism, such as - defined by RFC2817, to upgrade a regular TCP/IP connection to an ssl - connection the erlang ssl API supports this. This can be useful for + defined by RFC2817, to upgrade a regular TCP/IP connection to an SSL + connection the erlang SSL API supports this. This can be useful for things such as supporting HTTP and HTTPS on the same port and implementing virtual hosting.

@@ -131,7 +131,7 @@ connections. Sessions are used to avoid the expensive negotiation of new security parameters for each connection."

-

Session data is by default kept by the ssl application in a +

Session data is by default kept by the SSL application in a memory storage hence session data will be lost at application restart or takeover. Users may define their own callback module to handle session data storage if persistent data storage is @@ -140,8 +140,8 @@ possible to configure the amount of time the session data should be saved.

-

Ssl clients will by default try to reuse an available session, - ssl servers will by default agree to reuse sessions when clients +

SSL clients will by default try to reuse an available session, + SSL servers will by default agree to reuse sessions when clients ask to do so.

diff --git a/lib/ssl/inet_proxy_dist.erl b/lib/ssl/inet_proxy_dist.erl deleted file mode 100644 index 9e415def3e..0000000000 --- a/lib/ssl/inet_proxy_dist.erl +++ /dev/null @@ -1,229 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author Dan Gudmundsson -%%% @copyright (C) 2010, Dan Gudmundsson -%%% @doc -%%% -%%% @end -%%% Created : 22 Jun 2010 by Dan Gudmundsson -%%%------------------------------------------------------------------- --module(inet_proxy_dist). - --export([childspecs/0, listen/1, accept/1, accept_connection/5, - setup/5, close/1, select/1, is_node_name/1]). - --include_lib("kernel/src/net_address.hrl"). --include_lib("kernel/src/dist.hrl"). --include_lib("kernel/src/dist_util.hrl"). - --import(error_logger,[error_msg/2]). - -childspecs() -> - io:format("childspecs called~n",[]), - {ok, [{proxy_server,{proxy_server, start_link, []}, - permanent, 2000, worker, [proxy_server]}]}. - -select(Node) -> - io:format("Select called~n",[]), - inet_ssl_dist:select(Node). - -is_node_name(Name) -> - io:format("is_node_name~n",[]), - inet_ssl_dist:is_node_name(Name). - -listen(Name) -> - io:format("listen called~n",[]), - gen_server:call(proxy_server, {listen, Name}, infinity). - -accept(Listen) -> - io:format("accept called~n",[]), - gen_server:call(proxy_server, {accept, Listen}, infinity). - -accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) -> - io:format("accept_connection called ~n",[]), - Kernel = self(), - spawn_link(fun() -> do_accept(Kernel, AcceptPid, Socket, - MyNode, Allowed, SetupTime) end). - -setup(Node, Type, MyNode, LongOrShortNames,SetupTime) -> - io:format("setup called~n",[]), - Kernel = self(), - spawn(fun() -> do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) end). - -do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> - [Name, Address] = splitnode(Node, LongOrShortNames), - case inet:getaddr(Address, inet) of - {ok, Ip} -> - Timer = dist_util:start_timer(SetupTime), - case erl_epmd:port_please(Name, Ip) of - {port, TcpPort, Version} -> - ?trace("port_please(~p) -> version ~p~n", - [Node,Version]), - dist_util:reset_timer(Timer), - case gen_server:call(proxy_server, {connect, Ip, TcpPort}, infinity) of - {ok, Socket} -> - HSData = #hs_data{ - kernel_pid = Kernel, - other_node = Node, - this_node = MyNode, - socket = Socket, - timer = Timer, - this_flags = 0, - other_version = Version, - f_send = fun(S,D) -> - io:format("Kernel call send~n",[]), - gen_tcp:send(S,D) - end, - f_recv = fun(S,N,T) -> - io:format("Kernel call receive~n",[]), - gen_tcp:recv(S,N,T) - end, - f_setopts_pre_nodeup = - fun(S) -> - io:format("Kernel pre nodeup~n",[]), - inet:setopts(S, [{active, false}, {packet, 4}]) - end, - f_setopts_post_nodeup = - fun(S) -> - io:format("Kernel post nodeup~n",[]), - inet:setopts(S, [{deliver, port},{active, true}]) - end, - f_getll = fun(S) -> inet:getll(S) end, - f_address = - fun(_,_) -> - #net_address{address = {Ip,TcpPort}, - host = Address, - protocol = proxy, - family = inet} - end, - mf_tick = fun(S) -> gen_tcp:send(S, <<>>) end, - mf_getstat = fun(S) -> - {ok, Stats} = inet:getstat(S, [recv_cnt, send_cnt, send_pend]), - R = proplists:get_value(recv_cnt, Stats, 0), - W = proplists:get_value(send_cnt, Stats, 0), - P = proplists:get_value(send_pend, Stats, 0), - {ok, R,W,P} - end, - request_type = Type - }, - dist_util:handshake_we_started(HSData); - _ -> - %% Other Node may have closed since - %% port_please ! - ?trace("other node (~p) " - "closed since port_please.~n", - [Node]), - ?shutdown(Node) - end; - _ -> - ?trace("port_please (~p) " - "failed.~n", [Node]), - ?shutdown(Node) - end; - _Other -> - ?trace("inet_getaddr(~p) " - "failed (~p).~n", [Node,Other]), - ?shutdown(Node) - end. - -close(Socket) -> - try - erlang:error(foo) - catch _:_ -> - io:format("close called ~p ~p~n",[Socket, erlang:get_stacktrace()]) - end, - gen_tcp:close(Socket), - ok. - -do_accept(Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> - process_flag(priority, max), - io:format("~p: in do_accept~n", [self()]), - receive - {AcceptPid, controller} -> - io:format("~p: do_accept controller~n", [self()]), - Timer = dist_util:start_timer(SetupTime), - case check_ip(Socket) of - true -> - HSData = #hs_data{ - kernel_pid = Kernel, - this_node = MyNode, - socket = Socket, - timer = Timer, - this_flags = 0, - allowed = Allowed, - f_send = fun(S,D) -> - io:format("Kernel call send~n",[]), - gen_tcp:send(S,D) end, - f_recv = fun(S,N,T) -> - io:format("Kernel call receive~n",[]), - gen_tcp:recv(S,N,T) end, - f_setopts_pre_nodeup = - fun(S) -> - io:format("Kernel pre nodeup~n",[]), - inet:setopts(S, [{active, false}, {packet, 4}]) - end, - f_setopts_post_nodeup = - fun(S) -> - io:format("Kernel post nodeup~n",[]), - inet:setopts(S, [{deliver, port},{active, true}]) - end, - f_getll = fun(S) -> inet:getll(S) end, - f_address = fun get_remote_id/2, - mf_tick = fun(S) -> gen_tcp:send(S, <<>>) end, - mf_getstat = fun(S) -> - {ok, Stats} = inet:getstat(S, [recv_cnt, send_cnt, send_pend]), - R = proplists:get_value(recv_cnt, Stats, 0), - W = proplists:get_value(send_cnt, Stats, 0), - P = proplists:get_value(send_pend, Stats, 0), - {ok, R,W,P} - end - }, - dist_util:handshake_other_started(HSData); - {false,IP} -> - error_logger:error_msg("** Connection attempt from " - "disallowed IP ~w ** ~n", [IP]), - ?shutdown(no_node) - end - end. - -get_remote_id(Socket, Node) -> - gen_server:call(proxy_server, {get_remote_id, {Socket,Node}}, infinity). - -check_ip(_) -> - true. - - -%% If Node is illegal terminate the connection setup!! -splitnode(Node, LongOrShortNames) -> - case split_node(atom_to_list(Node), $@, []) of - [Name|Tail] when Tail =/= [] -> - Host = lists:append(Tail), - case split_node(Host, $., []) of - [_] when LongOrShortNames == longnames -> - error_msg("** System running to use " - "fully qualified " - "hostnames **~n" - "** Hostname ~s is illegal **~n", - [Host]), - ?shutdown(Node); - [_, _ | _] when LongOrShortNames == shortnames -> - error_msg("** System NOT running to use fully qualified " - "hostnames **~n" - "** Hostname ~s is illegal **~n", - [Host]), - ?shutdown(Node); - _ -> - [Name, Host] - end; - [_] -> - error_msg("** Nodename ~p illegal, no '@' character **~n", - [Node]), - ?shutdown(Node); - _ -> - error_msg("** Nodename ~p illegal **~n", [Node]), - ?shutdown(Node) - end. - -split_node([Chr|T], Chr, Ack) -> [lists:reverse(Ack)|split_node(T, Chr, [])]; -split_node([H|T], Chr, Ack) -> split_node(T, Chr, [H|Ack]); -split_node([], _, Ack) -> [lists:reverse(Ack)]. - diff --git a/lib/ssl/proxy_server.erl b/lib/ssl/proxy_server.erl deleted file mode 100644 index 38ec0ef0d8..0000000000 --- a/lib/ssl/proxy_server.erl +++ /dev/null @@ -1,272 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author Dan Gudmundsson -%%% @copyright (C) 2010, Dan Gudmundsson -%%% @doc start server with -proto_dist inet_proxy and net_kernel:start([s@faenor, shortnames]). -%%% -%%% @end -%%% Created : 22 Jun 2010 by Dan Gudmundsson -%%%------------------------------------------------------------------- --module(proxy_server). - --export([init/1, start_link/0, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --include_lib("kernel/src/net_address.hrl"). --include_lib("kernel/src/dist.hrl"). --include_lib("kernel/src/dist_util.hrl"). - --record(state, - {listen, - accept_loop - }). - --define(PPRE, 4). --define(PPOST, 4). - -start_link() -> - gen_server:start_link({local, proxy_server}, proxy_server, [], []). - -init([]) -> - io:format("~p: init~n",[self()]), - process_flag(priority, max), - {ok, #state{}}. - -handle_call(What = {listen, Name}, _From, State) -> - io:format("~p: call listen ~p~n",[self(), What]), - case gen_tcp:listen(0, [{active, false}, {packet,?PPRE}]) of - {ok, Socket} -> - {ok, World} = gen_tcp:listen(0, [{active, false}, binary, {packet,?PPRE}]), - TcpAddress = get_tcp_address(Socket), - WorldTcpAddress = get_tcp_address(World), - {_,Port} = WorldTcpAddress#net_address.address, - {ok, Creation} = erl_epmd:register_node(Name, Port), - {reply, {ok, {Socket, TcpAddress, Creation}}, - State#state{listen={Socket, World}}}; - Error -> - {reply, Error, State} - end; - -handle_call(What = {accept, Listen}, {From, _}, State = #state{listen={_, World}}) -> - io:format("~p: call accept ~p~n",[self(), What]), - Self = self(), - ErtsPid = spawn_link(fun() -> accept_loop(Self, erts, Listen, From) end), - WorldPid = spawn_link(fun() -> accept_loop(Self, world, World, Listen) end), - {reply, ErtsPid, State#state{accept_loop={ErtsPid, WorldPid}}}; - -handle_call({connect, Ip, Port}, {From, _}, State) -> - Me = self(), - Pid = spawn_link(fun() -> setup_proxy(Ip, Port, Me) end), - receive - {Pid, go_ahead, LPort} -> - Res = {ok, Socket} = try_connect(LPort), - ok = gen_tcp:controlling_process(Socket, From), - {reply, Res, State}; - {Pid, Error} -> - {reply, Error, State} - end; - -handle_call({get_remote_id, {Socket,_Node}}, _From, State) -> - Address = get_tcp_address(Socket), - io:format("~p: get_remote_id ~p~n",[self(), Address]), - {reply, Address, State}; - -handle_call(What, _From, State) -> - io:format("~p: call ~p~n",[self(), What]), - {reply, ok, State}. - -handle_cast(What, State) -> - io:format("~p: cast ~p~n",[self(), What]), - {noreply, State}. - -handle_info(What, State) -> - io:format("~p: info ~p~n",[self(), What]), - {noreply, State}. - -terminate(_Reason, _St) -> - ok. - -code_change(_OldVsn, St, _Extra) -> - {ok, St}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -get_tcp_address(Socket) -> - {ok, Address} = inet:sockname(Socket), - {ok, Host} = inet:gethostname(), - #net_address{ - address = Address, - host = Host, - protocol = proxy, - family = inet - }. - -accept_loop(Proxy, Type, Listen, Extra) -> - process_flag(priority, max), - case Type of - erts -> - case gen_tcp:accept(Listen) of - {ok, Socket} -> - io:format("~p: erts accept~n",[self()]), - Extra ! {accept,self(),Socket,inet,proxy}, - receive - {_Kernel, controller, Pid} -> - ok = gen_tcp:controlling_process(Socket, Pid), - Pid ! {self(), controller}; - {_Kernel, unsupported_protocol} -> - exit(unsupported_protocol) - end; - Error -> - exit(Error) - end; - world -> - case gen_tcp:accept(Listen) of - {ok, Socket} -> - Opts = get_ssl_options(server), - {ok, SslSocket} = ssl:ssl_accept(Socket, Opts), - io:format("~p: world accept~n",[self()]), - PairHandler = spawn_link(fun() -> setup_connection(SslSocket, Extra) end), - ok = ssl:controlling_process(SslSocket, PairHandler); - Error -> - exit(Error) - end - end, - accept_loop(Proxy, Type, Listen, Extra). - - -try_connect(Port) -> - case gen_tcp:connect({127,0,0,1}, Port, [{active, false}, {packet,?PPRE}]) of - R = {ok, _S} -> - R; - {error, _R} -> - io:format("Failed ~p~n",[_R]), - try_connect(Port) - end. - -setup_proxy(Ip, Port, Parent) -> - process_flag(trap_exit, true), - Opts = get_ssl_options(client), - case ssl:connect(Ip, Port, [{active, true}, binary, {packet,?PPRE}] ++ Opts) of - {ok, World} -> - {ok, ErtsL} = gen_tcp:listen(0, [{active, true}, binary, {packet,?PPRE}]), - #net_address{address={_,LPort}} = get_tcp_address(ErtsL), - Parent ! {self(), go_ahead, LPort}, - case gen_tcp:accept(ErtsL) of - {ok, Erts} -> - %% gen_tcp:close(ErtsL), - io:format("World ~p Erts ~p~n",[World, Erts]), - loop_conn_setup(World, Erts); - Err -> - Parent ! {self(), Err} - end; - Err -> - Parent ! {self(), Err} - end. - -setup_connection(World, ErtsListen) -> - process_flag(trap_exit, true), - io:format("Setup connection ~n",[]), - TcpAddress = get_tcp_address(ErtsListen), - {_Addr,Port} = TcpAddress#net_address.address, - {ok, Erts} = gen_tcp:connect({127,0,0,1}, Port, [{active, true}, binary, {packet,?PPRE}]), - ssl:setopts(World, [{active,true}, {packet,?PPRE}]), - io:format("~p ~n",[?LINE]), - loop_conn_setup(World, Erts). - -loop_conn_setup(World, Erts) -> - receive - {ssl, World, Data = <<$a, _/binary>>} -> - gen_tcp:send(Erts, Data), - io:format("Handshake finished World -> Erts ~p ~c~n",[size(Data), $a]), - ssl:setopts(World, [{packet,?PPOST}]), - inet:setopts(Erts, [{packet,?PPOST}]), - loop_conn(World, Erts); - {tcp, Erts, Data = <<$a, _/binary>>} -> - ssl:send(World, Data), - io:format("Handshake finished Erts -> World ~p ~c~n",[size(Data), $a]), - ssl:setopts(World, [{packet,?PPOST}]), - inet:setopts(Erts, [{packet,?PPOST}]), - loop_conn(World, Erts); - - {ssl, World, Data = <>} -> - gen_tcp:send(Erts, Data), - io:format("Handshake World -> Erts ~p ~c~n",[size(Data), H]), - loop_conn_setup(World, Erts); - {tcp, Erts, Data = <>} -> - ssl:send(World, Data), - io:format("Handshake Erts -> World ~p ~c~n",[size(Data), H]), - loop_conn_setup(World, Erts); - {ssl, World, Data} -> - gen_tcp:send(Erts, Data), - io:format("World -> Erts ~p <<>>~n",[size(Data)]), - loop_conn_setup(World, Erts); - {tcp, Erts, Data} -> - ssl:send(World, Data), - io:format("Erts -> World ~p <<>>~n",[size(Data)]), - loop_conn_setup(World, Erts); - Other -> - io:format("~p ~p~n",[?LINE, Other]) - end. - -loop_conn(World, Erts) -> - receive - {ssl, World, Data = <>} -> - gen_tcp:send(Erts, Data), - io:format("World -> Erts ~p ~c~n",[size(Data), H]), - loop_conn(World, Erts); - {tcp, Erts, Data = <>} -> - ssl:send(World, Data), - io:format("Erts -> World ~p ~c~n",[size(Data), H]), - loop_conn(World, Erts); - {ssl, World, Data} -> - gen_tcp:send(Erts, Data), - io:format("World -> Erts ~p <<>>~n",[size(Data)]), - loop_conn(World, Erts); - {tcp, Erts, Data} -> - ssl:send(World, Data), - io:format("Erts -> World ~p <<>>~n",[size(Data)]), - loop_conn(World, Erts); - - Other -> - io:format("~p ~p~n",[?LINE, Other]) - end. - -get_ssl_options(Type) -> - case init:get_argument(ssl_dist_opt) of - {ok, Args} -> - ssl_options(Type, Args); - _ -> - [] - end. - -ssl_options(_,[]) -> - []; -ssl_options(server, [["server_certfile", Value]|T]) -> - [{certfile, Value} | ssl_options(server,T)]; -ssl_options(client, [["client_certfile", Value]|T]) -> - [{certfile, Value} | ssl_options(client,T)]; -ssl_options(server, [["server_cacertfile", Value]|T]) -> - [{cacertfile, Value} | ssl_options(server,T)]; -ssl_options(server, [["server_keyfile", Value]|T]) -> - [{keyfile, Value} | ssl_options(server,T)]; -ssl_options(Type, [["client_certfile", _Value]|T]) -> - ssl_options(Type,T); -ssl_options(Type, [["server_certfile", _Value]|T]) -> - ssl_options(Type,T); -ssl_options(Type, [[Item, Value]|T]) -> - [{atomize(Item),fixup(Value)} | ssl_options(Type,T)]; -ssl_options(Type, [[Item,Value |T1]|T2]) -> - ssl_options(atomize(Type),[[Item,Value],T1|T2]); -ssl_options(_,_) -> - exit(malformed_ssl_dist_opt). - -fixup(Value) -> - case catch list_to_integer(Value) of - {'EXIT',_} -> - Value; - Int -> - Int - end. - -atomize(List) when is_list(List) -> - list_to_atom(List); -atomize(Atom) when is_atom(Atom) -> - Atom. diff --git a/lib/ssl/server.pem b/lib/ssl/server.pem deleted file mode 100644 index 4e4aae5342..0000000000 --- a/lib/ssl/server.pem +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICezCCAeSgAwIBAgIFFX2Pz5EwDQYJKoZIhvcNAQEFBQAwczEcMBoGCSqGSIb3 -DQEJARYNQ0FAZXJsYW5nLm9yZzELMAkGA1UEAxMCQ0ExEjAQBgNVBAcTCVN0b2Nr -aG9sbTELMAkGA1UEBhMCU0UxDzANBgNVBAoTBmVybGFuZzEUMBIGA1UECxMLdGVz -dGluZyBkZXAwIhgPMjAxMDA4MjUwMDAwMDBaGA8yMDEwMDkwMTAwMDAwMFowdzEe -MBwGCSqGSIb3DQEJARYPZGd1ZEBlcmxhbmcub3JnMQ0wCwYDVQQDEwRkZ3VkMRIw -EAYDVQQHEwlTdG9ja2hvbG0xCzAJBgNVBAYTAlNFMQ8wDQYDVQQKEwZlcmxhbmcx -FDASBgNVBAsTC3Rlc3RpbmcgZGVwMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB -gQDAu0FFOt/gZUz5DLBtqA/YUNrq+xXevXTsR1I/uxzNS+nYWkMN81W5oI2yXJ08 -LLdat6APru64DWRGQPMn6BTr4ti9l9Nq4jQEY96G2ee+YrB5SAduxkWwg2qyNMb3 -s4OIq56tp+pzty/v8VcapUTn3uKJv3SL0eYWxASD79WmdQIDAQABoxMwETAPBgNV -HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAKaT0GL8gIlnPBJS+81CnQos -cMoZll7QdXLGxzSN1laxDrvHOHE9SAtrx1EJHcv8nh/jvhL715bVbnuaoAtgxQoW -KF3A7DziDEYhkZd20G80rC+i6rx3n/+9T51RPhzymNbgSQhuVBFs0JXL73HPEqeZ -wskDuSyiV8DCDjiwlgpq ------END CERTIFICATE----- - -XXX Following key assumed not encrypted ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDAu0FFOt/gZUz5DLBtqA/YUNrq+xXevXTsR1I/uxzNS+nYWkMN -81W5oI2yXJ08LLdat6APru64DWRGQPMn6BTr4ti9l9Nq4jQEY96G2ee+YrB5SAdu -xkWwg2qyNMb3s4OIq56tp+pzty/v8VcapUTn3uKJv3SL0eYWxASD79WmdQIDAQAB -AoGAERwOjVDOsyMLFEj2GKYE0hVLefTUWjPDf35NZO79fZQxrE1HCqQBhjskmSLz -qnXlyR3oDbxf4OL/deUqMO6/fJHVOD7O9UQRK26f01IZoTq0WmBMFP2C7upafzgx -9gxddQ7j9B6rqz2agV/YUpvij7hfhXFmV/ogggeuVsyQ0AECQQDNSBH8WMVgky0I -QLa7MfBLsiHQ5FXmVYU6i9C/QUpL7SWu6eV3edAm7xbtcWnqXEMxeC7D9NIAxDhO -VaV21bR1AkEA8Flmsy/XRVPF2rmfz0o1Cc+9m6NZOQAUK9sHAXuL3HoTPcigS+f5 -fHbAGFPDBoolS9qRJs5AcL95majzpDnqAQJAJ/SjK47LvCRpW3XdG0p5DwK4+kO3 -RIHY0LBuDQvUPjsGXqk/9KVNEobu24B7sRYMLhDKaXG5flSy8OxSrHKkEQJBAKvg -ItMs+RK4r5qUd7Xy6S7VAlCUZa+fYM1j2gSzZvcJzUy3dfoSL5VUDlbXP3YjwDwY -VwibIfX+12SNL35XdAECQHLGnDKYLO3M7HCPf9Yp8tiOmD9mASKcXd3NdBg5mD/l -oOlKIQhdAQS0BLFhyASfb6hzY0Mj8B2Nq5Z3sq8yD1s= ------END RSA PRIVATE KEY----- - diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 7514ad2aa2..9c40d4ea53 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1999-2010. All Rights Reserved. +# Copyright Ericsson AB 1999-2011. 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 @@ -43,10 +43,12 @@ MODULES= \ ssl_app \ ssl_broker \ ssl_broker_sup \ + ssl_dist_sup\ ssl_server \ ssl_sup \ ssl_prim \ inet_ssl_dist \ + inet_tls_dist \ ssl_certificate\ ssl_certificate_db\ ssl_cipher \ @@ -62,6 +64,7 @@ MODULES= \ ssl_ssl2 \ ssl_ssl3 \ ssl_tls1 \ + ssl_tls_dist_proxy INTERNAL_HRL_FILES = \ ssl_int.hrl ssl_broker_int.hrl ssl_debug.hrl \ diff --git a/lib/ssl/src/inet_ssl_dist.erl b/lib/ssl/src/inet_ssl_dist.erl index f4bcb593d0..42a03a4879 100644 --- a/lib/ssl/src/inet_ssl_dist.erl +++ b/lib/ssl/src/inet_ssl_dist.erl @@ -31,7 +31,7 @@ -import(error_logger,[error_msg/2]). --include_lib("kernel/src/net_address.hrl"). +-include_lib("kernel/include/net_address.hrl"). -define(to_port(Socket, Data, Opts), case ssl_prim:send(Socket, Data, Opts) of @@ -42,8 +42,8 @@ R end). --include_lib("kernel/src/dist.hrl"). --include_lib("kernel/src/dist_util.hrl"). +-include_lib("kernel/include/dist.hrl"). +-include_lib("kernel/include/dist_util.hrl"). %% ------------------------------------------------------------- %% This function should return a valid childspec, so that diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl new file mode 100644 index 0000000000..f42c076460 --- /dev/null +++ b/lib/ssl/src/inet_tls_dist.erl @@ -0,0 +1,275 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2011. 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% +%% + +%% +-module(inet_tls_dist). + +-export([childspecs/0, listen/1, accept/1, accept_connection/5, + setup/5, close/1, select/1, is_node_name/1]). + +-include_lib("kernel/include/net_address.hrl"). +-include_lib("kernel/include/dist.hrl"). +-include_lib("kernel/include/dist_util.hrl"). + +childspecs() -> + {ok, [{ssl_dist_sup,{ssl_dist_sup, start_link, []}, + permanent, 2000, worker, [ssl_dist_sup]}]}. + +select(Node) -> + case split_node(atom_to_list(Node), $@, []) of + [_,_Host] -> + true; + _ -> + false + end. + +is_node_name(Node) when is_atom(Node) -> + select(Node); +is_node_name(_) -> + false. + +listen(Name) -> + ssl_tls_dist_proxy:listen(Name). + +accept(Listen) -> + ssl_tls_dist_proxy:accept(Listen). + +accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) -> + Kernel = self(), + spawn_link(fun() -> do_accept(Kernel, AcceptPid, Socket, + MyNode, Allowed, SetupTime) end). + +setup(Node, Type, MyNode, LongOrShortNames,SetupTime) -> + Kernel = self(), + spawn(fun() -> do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) end). + +do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> + [Name, Address] = splitnode(Node, LongOrShortNames), + case inet:getaddr(Address, inet) of + {ok, Ip} -> + Timer = dist_util:start_timer(SetupTime), + case erl_epmd:port_please(Name, Ip) of + {port, TcpPort, Version} -> + ?trace("port_please(~p) -> version ~p~n", + [Node,Version]), + dist_util:reset_timer(Timer), + case ssl_tls_dist_proxy:connect(Ip, TcpPort) of + {ok, Socket} -> + HSData = connect_hs_data(Kernel, Node, MyNode, Socket, + Timer, Version, Ip, TcpPort, Address, + Type), + dist_util:handshake_we_started(HSData); + _ -> + %% Other Node may have closed since + %% port_please ! + ?trace("other node (~p) " + "closed since port_please.~n", + [Node]), + ?shutdown(Node) + end; + _ -> + ?trace("port_please (~p) " + "failed.~n", [Node]), + ?shutdown(Node) + end; + _Other -> + ?trace("inet_getaddr(~p) " + "failed (~p).~n", [Node,Other]), + ?shutdown(Node) + end. + +close(Socket) -> + try + erlang:error(foo) + catch _:_ -> + io:format("close called ~p ~p~n",[Socket, erlang:get_stacktrace()]) + end, + gen_tcp:close(Socket), + ok. + +do_accept(Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> + process_flag(priority, max), + receive + {AcceptPid, controller} -> + Timer = dist_util:start_timer(SetupTime), + case check_ip(Socket) of + true -> + HSData = accept_hs_data(Kernel, MyNode, Socket, Timer, Allowed), + dist_util:handshake_other_started(HSData); + {false,IP} -> + error_logger:error_msg("** Connection attempt from " + "disallowed IP ~w ** ~n", [IP]), + ?shutdown(no_node) + end + end. +%% ------------------------------------------------------------ +%% Do only accept new connection attempts from nodes at our +%% own LAN, if the check_ip environment parameter is true. +%% ------------------------------------------------------------ +check_ip(Socket) -> + case application:get_env(check_ip) of + {ok, true} -> + case get_ifs(Socket) of + {ok, IFs, IP} -> + check_ip(IFs, IP); + _ -> + ?shutdown(no_node) + end; + _ -> + true + end. + +get_ifs(Socket) -> + case ssl_prim:peername(Socket) of + {ok, {IP, _}} -> + case ssl_prim:getif(Socket) of + {ok, IFs} -> {ok, IFs, IP}; + Error -> Error + end; + Error -> + Error + end. + +check_ip([{OwnIP, _, Netmask}|IFs], PeerIP) -> + case {mask(Netmask, PeerIP), mask(Netmask, OwnIP)} of + {M, M} -> true; + _ -> check_ip(IFs, PeerIP) + end; +check_ip([], PeerIP) -> + {false, PeerIP}. + +mask({M1,M2,M3,M4}, {IP1,IP2,IP3,IP4}) -> + {M1 band IP1, + M2 band IP2, + M3 band IP3, + M4 band IP4}; + +mask({M1,M2,M3,M4, M5, M6, M7, M8}, {IP1,IP2,IP3,IP4, IP5, IP6, IP7, IP8}) -> + {M1 band IP1, + M2 band IP2, + M3 band IP3, + M4 band IP4, + M5 band IP5, + M6 band IP6, + M7 band IP7, + M8 band IP8}. + + +%% If Node is illegal terminate the connection setup!! +splitnode(Node, LongOrShortNames) -> + case split_node(atom_to_list(Node), $@, []) of + [Name|Tail] when Tail =/= [] -> + Host = lists:append(Tail), + check_node(Name, Node, Host, LongOrShortNames); + [_] -> + error_logger:error_msg("** Nodename ~p illegal, no '@' character **~n", + [Node]), + ?shutdown(Node); + _ -> + error_logger:error_msg("** Nodename ~p illegal **~n", [Node]), + ?shutdown(Node) + end. + +check_node(Name, Node, Host, LongOrShortNames) -> + case split_node(Host, $., []) of + [_] when LongOrShortNames == longnames -> + error_logger:error_msg("** System running to use " + "fully qualified " + "hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown(Node); + [_, _ | _] when LongOrShortNames == shortnames -> + error_logger:error_msg("** System NOT running to use fully qualified " + "hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown(Node); + _ -> + [Name, Host] + end. + +split_node([Chr|T], Chr, Ack) -> + [lists:reverse(Ack)|split_node(T, Chr, [])]; +split_node([H|T], Chr, Ack) -> + split_node(T, Chr, [H|Ack]); +split_node([], _, Ack) -> + [lists:reverse(Ack)]. + +connect_hs_data(Kernel, Node, MyNode, Socket, Timer, Version, Ip, TcpPort, Address, Type) -> + common_hs_data(Kernel, MyNode, Socket, Timer, + #hs_data{other_node = Node, + other_version = Version, + f_address = + fun(_,_) -> + #net_address{address = {Ip,TcpPort}, + host = Address, + protocol = proxy, + family = inet} + end, + request_type = Type + }). + +accept_hs_data(Kernel, MyNode, Socket, Timer, Allowed) -> + common_hs_data(Kernel, MyNode, Socket, Timer, #hs_data{ + allowed = Allowed, + f_address = fun(S, N) -> + ssl_tls_dist_proxy:get_remote_id(S, N) + end + }). + +common_hs_data(Kernel, MyNode, Socket, Timer, HsData) -> + HsData#hs_data{ + kernel_pid = Kernel, + this_node = MyNode, + socket = Socket, + timer = Timer, + this_flags = 0, + f_send = + fun(S,D) -> + gen_tcp:send(S,D) + end, + f_recv = + fun(S,N,T) -> + gen_tcp:recv(S,N,T) + end, + f_setopts_pre_nodeup = + fun(S) -> + inet:setopts(S, [{active, false}, {packet, 4}]) + end, + f_setopts_post_nodeup = + fun(S) -> + inet:setopts(S, [{deliver, port},{active, true}]) + end, + f_getll = + fun(S) -> + inet:getll(S) + end, + mf_tick = + fun(S) -> + gen_tcp:send(S, <<>>) + end, + mf_getstat = + fun(S) -> + {ok, Stats} = inet:getstat(S, [recv_cnt, send_cnt, send_pend]), + R = proplists:get_value(recv_cnt, Stats, 0), + W = proplists:get_value(send_cnt, Stats, 0), + P = proplists:get_value(send_pend, Stats, 0), + {ok, R,W,P} + end}. diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index b9716786e6..afe19da900 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -8,6 +8,9 @@ ssl_broker, ssl_broker_sup, ssl_prim, + inet_tls_dist, + ssl_tls_dist_proxy, + ssl_dist_sup, inet_ssl_dist, ssl_tls1, ssl_ssl3, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 74900936a3..795b891aa0 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -742,7 +742,8 @@ handle_options(Opts0, _Role) -> secure_renegotiate = handle_option(secure_renegotiate, Opts, false), renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT), debug = handle_option(debug, Opts, []), - hibernate_after = handle_option(hibernate_after, Opts, undefined) + hibernate_after = handle_option(hibernate_after, Opts, undefined), + erl_dist = handle_option(erl_dist, Opts, false) }, CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}), @@ -751,7 +752,7 @@ handle_options(Opts0, _Role) -> depth, cert, certfile, key, keyfile, password, cacerts, cacertfile, dh, dhfile, ciphers, debug, reuse_session, reuse_sessions, ssl_imp, - cb_info, renegotiate_at, secure_renegotiate, hibernate_after], + cb_info, renegotiate_at, secure_renegotiate, hibernate_after, erl_dist], SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) @@ -862,6 +863,9 @@ validate_option(hibernate_after, undefined) -> undefined; validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 -> Value; +validate_option(erl_dist,Value) when Value == true; + Value == false -> + Value; validate_option(Opt, Value) -> throw({error, {eoptions, {Opt, Value}}}). diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 0ae39689cc..95af7f2448 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1033,7 +1033,8 @@ code_change(_OldVsn, StateName, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -start_fsm(Role, Host, Port, Socket, Opts, User, {CbModule, _,_, _} = CbInfo, +start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_} = Opts, + User, {CbModule, _,_, _} = CbInfo, Timeout) -> try {ok, Pid} = ssl_connection_sup:start_child([Role, Host, Port, Socket, @@ -1044,9 +1045,26 @@ start_fsm(Role, Host, Port, Socket, Opts, User, {CbModule, _,_, _} = CbInfo, catch error:{badmatch, {error, _} = Error} -> Error + end; + +start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_} = Opts, + User, {CbModule, _,_, _} = CbInfo, + Timeout) -> + try + {ok, Pid} = ssl_connection_sup:start_child_dist([Role, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = socket_control(Socket, Pid, CbModule), + ok = handshake(SslSocket, Timeout), + {ok, SslSocket} + catch + error:{badmatch, {error, _} = Error} -> + Error end. ssl_init(SslOpts, Role) -> + + init_manager_name(SslOpts#ssl_options.erl_dist), + {ok, CertDbRef, CertDbHandle, CacheHandle, OwnCert} = init_certificates(SslOpts, Role), PrivateKey = init_private_key(CertDbHandle, SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, @@ -1054,6 +1072,10 @@ ssl_init(SslOpts, Role) -> DHParams = init_diffie_hellman(CertDbHandle, SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), {ok, CertDbRef, CertDbHandle, CacheHandle, OwnCert, PrivateKey, DHParams}. +init_manager_name(false) -> + put(ssl_manager, ssl_manager); +init_manager_name(true) -> + put(ssl_manager, ssl_manager_dist). init_certificates(#ssl_options{cacerts = CaCerts, cacertfile = CACertFile, diff --git a/lib/ssl/src/ssl_connection_sup.erl b/lib/ssl/src/ssl_connection_sup.erl index e9328d5f7c..78cfda5e63 100644 --- a/lib/ssl/src/ssl_connection_sup.erl +++ b/lib/ssl/src/ssl_connection_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. +%% Copyright Ericsson AB 2007-2011. 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 @@ -26,8 +26,8 @@ -behaviour(supervisor). %% API --export([start_link/0]). --export([start_child/1]). +-export([start_link/0, start_link_dist/0]). +-export([start_child/1, start_child_dist/1]). %% Supervisor callback -export([init/1]). @@ -38,9 +38,15 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). +start_link_dist() -> + supervisor:start_link({local, ssl_connection_sup_dist}, ?MODULE, []). + start_child(Args) -> supervisor:start_child(?MODULE, Args). +start_child_dist(Args) -> + supervisor:start_child(ssl_connection_sup_dist, Args). + %%%========================================================================= %%% Supervisor callback %%%========================================================================= diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl new file mode 100644 index 0000000000..c1912401d7 --- /dev/null +++ b/lib/ssl/src/ssl_dist_sup.erl @@ -0,0 +1,84 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2011. 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% +%% + +%% + +-module(ssl_dist_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_link() -> {ok, pid()} | ignore | {error, term()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +-spec init([]) -> {ok, {SupFlags :: tuple(), [ChildSpec :: tuple()]}}. + +init([]) -> + SessionCertManager = session_and_cert_manager_child_spec(), + ConnetionManager = connection_manager_child_spec(), + ProxyServer = proxy_server_child_spec(), + + {ok, {{one_for_all, 10, 3600}, [SessionCertManager, ConnetionManager, + ProxyServer]}}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +session_and_cert_manager_child_spec() -> + Opts = ssl_sup:manager_opts(), + Name = ssl_manager_dist, + StartFunc = {ssl_manager, start_link_dist, [Opts]}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_manager], + Type = worker, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +connection_manager_child_spec() -> + Name = ssl_connection_dist, + StartFunc = {ssl_connection_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_connection], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +proxy_server_child_spec() -> + Name = ssl_tls_dist_proxy, + StartFunc = {ssl_tls_dist_proxy, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_tls_dist_proxy], + Type = worker, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 6bf1edc452..483e06067c 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -98,10 +98,12 @@ renegotiate_at, secure_renegotiate, debug, - hibernate_after % undefined if not hibernating, + hibernate_after,% undefined if not hibernating, % or number of ms of inactivity % after which ssl_connection will % go into hibernation + %% This option should only be set to true by inet_tls_dist + erl_dist = false }). -record(socket_options, diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 56c43a16d1..0d308438b7 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -27,7 +27,7 @@ -include("ssl_internal.hrl"). %% Internal application API --export([start_link/1, +-export([start_link/1, start_link_dist/1, connection_init/2, cache_pem_file/2, lookup_trusted_cert/4, issuer_candidate/2, client_session_id/4, server_session_id/4, @@ -66,10 +66,20 @@ %%-------------------------------------------------------------------- -spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}. %% -%% Description: Starts the server +%% Description: Starts the ssl manager that takes care of sessions +%% and certificate caching. %%-------------------------------------------------------------------- start_link(Opts) -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []). + gen_server:start_link({local, ?MODULE}, ?MODULE, [?MODULE, Opts], []). + +%%-------------------------------------------------------------------- +-spec start_link_dist(list()) -> {ok, pid()} | ignore | {error, term()}. +%% +%% Description: Starts a special instance of the ssl manager to +%% be used by the erlang distribution. Note disables soft upgrade! +%%-------------------------------------------------------------------- +start_link_dist(Opts) -> + gen_server:start_link({local, ssl_manager_dist}, ?MODULE, [ssl_manager_dist, Opts], []). %%-------------------------------------------------------------------- -spec connection_init(string()| {der, list()}, client | server) -> @@ -166,7 +176,8 @@ invalidate_session(Port, Session) -> %% %% Description: Initiates the server %%-------------------------------------------------------------------- -init([Opts]) -> +init([Name, Opts]) -> + put(ssl_manager, Name), process_flag(trap_exit, true), CacheCb = proplists:get_value(session_cb, Opts, ssl_session_cache), SessionLifeTime = @@ -376,10 +387,10 @@ code_change(_OldVsn, State, _Extra) -> %%% Internal functions %%-------------------------------------------------------------------- call(Msg) -> - gen_server:call(?MODULE, {Msg, self()}, infinity). + gen_server:call(get(ssl_manager), {Msg, self()}, infinity). cast(Msg) -> - gen_server:cast(?MODULE, Msg). + gen_server:cast(get(ssl_manager), Msg). validate_session(Host, Port, Session, LifeTime) -> case ssl_session:valid_session(Session, LifeTime) of @@ -399,9 +410,10 @@ validate_session(Port, Session, LifeTime) -> start_session_validator(Cache, CacheCb, LifeTime) -> spawn_link(?MODULE, init_session_validator, - [[Cache, CacheCb, LifeTime]]). + [[get(ssl_manager), Cache, CacheCb, LifeTime]]). -init_session_validator([Cache, CacheCb, LifeTime]) -> +init_session_validator([SslManagerName, Cache, CacheCb, LifeTime]) -> + put(ssl_manager, SslManagerName), CacheCb:foldl(fun session_validation/2, LifeTime, Cache). diff --git a/lib/ssl/src/ssl_sup.erl b/lib/ssl/src/ssl_sup.erl index 316ed8a4e9..a008682b89 100644 --- a/lib/ssl/src/ssl_sup.erl +++ b/lib/ssl/src/ssl_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% Copyright Ericsson AB 1998-2011. 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 @@ -24,7 +24,7 @@ -behaviour(supervisor). %% API --export([start_link/0]). +-export([start_link/0, manager_opts/0]). %% Supervisor callback -export([init/1]). @@ -62,6 +62,22 @@ init([]) -> {ok, {{one_for_all, 10, 3600}, [Child2, SessionCertManager, ConnetionManager]}}. + +manager_opts() -> + CbOpts = case application:get_env(ssl, session_cb) of + {ok, Cb} when is_atom(Cb) -> + InitArgs = session_cb_init_args(), + [{session_cb, Cb}, {session_cb_init_args, InitArgs}]; + _ -> + [] + end, + case application:get_env(ssl, session_lifetime) of + {ok, Time} when is_integer(Time) -> + [{session_lifetime, Time}| CbOpts]; + _ -> + CbOpts + end. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -86,21 +102,6 @@ connection_manager_child_spec() -> {Name, StartFunc, Restart, Shutdown, Type, Modules}. -manager_opts() -> - CbOpts = case application:get_env(ssl, session_cb) of - {ok, Cb} when is_atom(Cb) -> - InitArgs = session_cb_init_args(), - [{session_cb, Cb}, {session_cb_init_args, InitArgs}]; - _ -> - [] - end, - case application:get_env(ssl, session_lifetime) of - {ok, Time} when is_integer(Time) -> - [{session_lifetime, Time}| CbOpts]; - _ -> - CbOpts - end. - session_cb_init_args() -> case application:get_env(ssl, session_cb_init_args) of {ok, Args} when is_list(Args) -> diff --git a/lib/ssl/src/ssl_tls_dist_proxy.erl b/lib/ssl/src/ssl_tls_dist_proxy.erl new file mode 100644 index 0000000000..1a998a0f34 --- /dev/null +++ b/lib/ssl/src/ssl_tls_dist_proxy.erl @@ -0,0 +1,326 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2011. 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% +%% +-module(ssl_tls_dist_proxy). + + +-export([listen/1, accept/1, connect/2, get_remote_id/2]). +-export([init/1, start_link/0, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3, ssl_options/2]). + +-include_lib("kernel/include/net_address.hrl"). + +-record(state, + {listen, + accept_loop + }). + +-define(PPRE, 4). +-define(PPOST, 4). + + +%%==================================================================== +%% Internal application API +%%==================================================================== + +listen(Name) -> + gen_server:call(?MODULE, {listen, Name}, infinity). + +accept(Listen) -> + gen_server:call(?MODULE, {accept, Listen}, infinity). + +connect(Ip, Port) -> + gen_server:call(?MODULE, {connect, Ip, Port}, infinity). + +get_remote_id(Socket, Node) -> + gen_server:call(?MODULE, {get_remote_id, {Socket,Node}}, infinity). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +init([]) -> + process_flag(priority, max), + {ok, #state{}}. + +handle_call({listen, Name}, _From, State) -> + case gen_tcp:listen(0, [{active, false}, {packet,?PPRE}]) of + {ok, Socket} -> + {ok, World} = gen_tcp:listen(0, [{active, false}, binary, {packet,?PPRE}]), + TcpAddress = get_tcp_address(Socket), + WorldTcpAddress = get_tcp_address(World), + {_,Port} = WorldTcpAddress#net_address.address, + {ok, Creation} = erl_epmd:register_node(Name, Port), + {reply, {ok, {Socket, TcpAddress, Creation}}, + State#state{listen={Socket, World}}}; + Error -> + {reply, Error, State} + end; + +handle_call({accept, Listen}, {From, _}, State = #state{listen={_, World}}) -> + Self = self(), + ErtsPid = spawn_link(fun() -> accept_loop(Self, erts, Listen, From) end), + WorldPid = spawn_link(fun() -> accept_loop(Self, world, World, Listen) end), + {reply, ErtsPid, State#state{accept_loop={ErtsPid, WorldPid}}}; + +handle_call({connect, Ip, Port}, {From, _}, State) -> + Me = self(), + Pid = spawn_link(fun() -> setup_proxy(Ip, Port, Me) end), + receive + {Pid, go_ahead, LPort} -> + Res = {ok, Socket} = try_connect(LPort), + ok = gen_tcp:controlling_process(Socket, From), + flush_old_controller(From, Socket), + {reply, Res, State}; + {Pid, Error} -> + {reply, Error, State} + end; + +handle_call({get_remote_id, {Socket,_Node}}, _From, State) -> + Address = get_tcp_address(Socket), + {reply, Address, State}; + +handle_call(_What, _From, State) -> + {reply, ok, State}. + +handle_cast(_What, State) -> + {noreply, State}. + +handle_info(_What, State) -> + {noreply, State}. + +terminate(_Reason, _St) -> + ok. + +code_change(_OldVsn, St, _Extra) -> + {ok, St}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +get_tcp_address(Socket) -> + {ok, Address} = inet:sockname(Socket), + {ok, Host} = inet:gethostname(), + #net_address{ + address = Address, + host = Host, + protocol = proxy, + family = inet + }. + +accept_loop(Proxy, Type, Listen, Extra) -> + process_flag(priority, max), + case Type of + erts -> + case gen_tcp:accept(Listen) of + {ok, Socket} -> + Extra ! {accept,self(),Socket,inet,proxy}, + receive + {_Kernel, controller, Pid} -> + ok = gen_tcp:controlling_process(Socket, Pid), + flush_old_controller(Pid, Socket), + Pid ! {self(), controller}; + {_Kernel, unsupported_protocol} -> + exit(unsupported_protocol) + end; + Error -> + exit(Error) + end; + world -> + case gen_tcp:accept(Listen) of + {ok, Socket} -> + Opts = get_ssl_options(server), + case ssl:ssl_accept(Socket, Opts) of + {ok, SslSocket} -> + PairHandler = + spawn_link(fun() -> + setup_connection(SslSocket, Extra) + end), + ok = ssl:controlling_process(SslSocket, PairHandler), + flush_old_controller(PairHandler, SslSocket); + _ -> + gen_tcp:close(Socket) + end; + Error -> + exit(Error) + end + end, + accept_loop(Proxy, Type, Listen, Extra). + + +try_connect(Port) -> + case gen_tcp:connect({127,0,0,1}, Port, [{active, false}, {packet,?PPRE}]) of + R = {ok, _S} -> + R; + {error, _R} -> + try_connect(Port) + end. + +setup_proxy(Ip, Port, Parent) -> + process_flag(trap_exit, true), + Opts = get_ssl_options(client), + case ssl:connect(Ip, Port, [{active, true}, binary, {packet,?PPRE}] ++ Opts) of + {ok, World} -> + {ok, ErtsL} = gen_tcp:listen(0, [{active, true}, binary, {packet,?PPRE}]), + #net_address{address={_,LPort}} = get_tcp_address(ErtsL), + Parent ! {self(), go_ahead, LPort}, + case gen_tcp:accept(ErtsL) of + {ok, Erts} -> + %% gen_tcp:close(ErtsL), + loop_conn_setup(World, Erts); + Err -> + Parent ! {self(), Err} + end; + Err -> + Parent ! {self(), Err} + end. + +setup_connection(World, ErtsListen) -> + process_flag(trap_exit, true), + TcpAddress = get_tcp_address(ErtsListen), + {_Addr,Port} = TcpAddress#net_address.address, + {ok, Erts} = gen_tcp:connect({127,0,0,1}, Port, [{active, true}, binary, {packet,?PPRE}]), + ssl:setopts(World, [{active,true}, {packet,?PPRE}]), + loop_conn_setup(World, Erts). + +loop_conn_setup(World, Erts) -> + receive + {ssl, World, Data = <<$a, _/binary>>} -> + gen_tcp:send(Erts, Data), + ssl:setopts(World, [{packet,?PPOST}]), + inet:setopts(Erts, [{packet,?PPOST}]), + loop_conn(World, Erts); + {tcp, Erts, Data = <<$a, _/binary>>} -> + ssl:send(World, Data), + ssl:setopts(World, [{packet,?PPOST}]), + inet:setopts(Erts, [{packet,?PPOST}]), + loop_conn(World, Erts); + {ssl, World, Data = <<_, _/binary>>} -> + gen_tcp:send(Erts, Data), + loop_conn_setup(World, Erts); + {tcp, Erts, Data = <<_, _/binary>>} -> + ssl:send(World, Data), + loop_conn_setup(World, Erts); + {ssl, World, Data} -> + gen_tcp:send(Erts, Data), + loop_conn_setup(World, Erts); + {tcp, Erts, Data} -> + ssl:send(World, Data), + loop_conn_setup(World, Erts) + end. + +loop_conn(World, Erts) -> + receive + {ssl, World, Data} -> + gen_tcp:send(Erts, Data), + loop_conn(World, Erts); + {tcp, Erts, Data} -> + ssl:send(World, Data), + loop_conn(World, Erts); + {tcp_closed, Erts} -> + ssl:close(World); + {ssl_closed, World} -> + gen_tcp:close(Erts) + end. + +get_ssl_options(Type) -> + case init:get_argument(ssl_dist_opt) of + {ok, Args} -> + [{erl_dist, true} | ssl_options(Type, Args)]; + _ -> + [{erl_dist, true}] + end. + +ssl_options(_,[]) -> + []; +ssl_options(server, [["client_" ++ _, _Value]|T]) -> + ssl_options(server,T); +ssl_options(client, [["server_" ++ _, _Value]|T]) -> + ssl_options(client,T); +ssl_options(server, [["server_certfile", Value]|T]) -> + [{certfile, Value} | ssl_options(server,T)]; +ssl_options(client, [["client_certfile", Value]|T]) -> + [{certfile, Value} | ssl_options(client,T)]; +ssl_options(server, [["server_cacertfile", Value]|T]) -> + [{cacertfile, Value} | ssl_options(server,T)]; +ssl_options(client, [["client_cacertfile", Value]|T]) -> + [{cacertfile, Value} | ssl_options(client,T)]; +ssl_options(server, [["server_keyfile", Value]|T]) -> + [{keyfile, Value} | ssl_options(server,T)]; +ssl_options(client, [["client_keyfile", Value]|T]) -> + [{keyfile, Value} | ssl_options(client,T)]; +ssl_options(server, [["server_password", Value]|T]) -> + [{password, Value} | ssl_options(server,T)]; +ssl_options(client, [["client_password", Value]|T]) -> + [{password, Value} | ssl_options(client,T)]; +ssl_options(server, [["server_verify", Value]|T]) -> + [{verify, atomize(Value)} | ssl_options(server,T)]; +ssl_options(client, [["client_verify", Value]|T]) -> + [{verify, atomize(Value)} | ssl_options(client,T)]; +ssl_options(server, [["server_reuse_sessions", Value]|T]) -> + [{reuse_sessions, atomize(Value)} | ssl_options(server,T)]; +ssl_options(client, [["client_reuse_sessions", Value]|T]) -> + [{reuse_sessions, atomize(Value)} | ssl_options(client,T)]; +ssl_options(server, [["server_secure_renegotiation", Value]|T]) -> + [{secure_renegotiation, atomize(Value)} | ssl_options(server,T)]; +ssl_options(client, [["client_secure_renegotiation", Value]|T]) -> + [{secure_renegotiation, atomize(Value)} | ssl_options(client,T)]; +ssl_options(server, [["server_depth", Value]|T]) -> + [{depth, list_to_integer(Value)} | ssl_options(server,T)]; +ssl_options(client, [["client_depth", Value]|T]) -> + [{depth, list_to_integer(Value)} | ssl_options(client,T)]; +ssl_options(server, [["server_hibernate_after", Value]|T]) -> + [{hibernate_after, list_to_integer(Value)} | ssl_options(server,T)]; +ssl_options(client, [["client_hibernate_after", Value]|T]) -> + [{hibernate_after, list_to_integer(Value)} | ssl_options(client,T)]; +ssl_options(server, [["server_ciphers", Value]|T]) -> + [{ciphers, Value} | ssl_options(server,T)]; +ssl_options(client, [["client_ciphers", Value]|T]) -> + [{ciphers, Value} | ssl_options(client,T)]; +ssl_options(server, [["server_dhfile", Value]|T]) -> + [{dhfile, Value} | ssl_options(server,T)]; +ssl_options(server, [["server_fail_if_no_peer_cert", Value]|T]) -> + [{fail_if_no_peer_cert, atomize(Value)} | ssl_options(server,T)]; +ssl_options(_,_) -> + exit(malformed_ssl_dist_opt). + +atomize(List) when is_list(List) -> + list_to_atom(List); +atomize(Atom) when is_atom(Atom) -> + Atom. + +flush_old_controller(Pid, Socket) -> + receive + {tcp, Socket, Data} -> + Pid ! {tcp, Socket, Data}, + flush_old_controller(Pid, Socket); + {tcp_closed, Socket} -> + Pid ! {tcp_closed, Socket}, + flush_old_controller(Pid, Socket); + {ssl, Socket, Data} -> + Pid ! {ssl, Socket, Data}, + flush_old_controller(Pid, Socket); + {ssl_closed, Socket} -> + Pid ! {ssl_closed, Socket}, + flush_old_controller(Pid, Socket) + after 0 -> + ok + end. diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index 5be07cad2c..45a401aa68 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -41,6 +41,7 @@ MODULES = \ ssl_payload_SUITE \ ssl_to_openssl_SUITE \ ssl_session_cache_SUITE \ + ssl_dist_SUITE \ ssl_test_MACHINE \ old_ssl_active_SUITE \ old_ssl_active_once_SUITE \ diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl new file mode 100644 index 0000000000..7325e97ff5 --- /dev/null +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -0,0 +1,603 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2011. 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% +%% + +-module(ssl_dist_SUITE). + +-include_lib("test_server/include/test_server.hrl"). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-define(DEFAULT_TIMETRAP_SECS, 240). + +-define(AWAIT_SLL_NODE_UP_TIMEOUT, 30000). + +-record(node_handle, + {connection_handler, + socket, + name, + nodename} + ). + +suite() -> + [{ct_hooks,[ts_install_cth]}]. + +all() -> + [basic]. + +groups() -> + []. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_suite(Config) -> + try crypto:start() of + ok -> + add_ssl_opts_config(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(Config) -> + application:stop(crypto), + Config. + +init_per_testcase(Case, Config) when list(Config) -> + Dog = ?t:timetrap(?t:seconds(?DEFAULT_TIMETRAP_SECS)), + [{watchdog, Dog},{testcase, Case}|Config]. + +end_per_testcase(_Case, Config) when list(Config) -> + Dog = ?config(watchdog, Config), + ?t:timetrap_cancel(Dog), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% Testcases %% +%% %% + +basic(doc) -> + ["Test that two nodes can connect via ssl distribution"]; +basic(suite) -> + []; +basic(Config) when is_list(Config) -> + NH1 = start_ssl_node(Config), + Node1 = NH1#node_handle.nodename, + NH2 = start_ssl_node(Config), + Node2 = NH2#node_handle.nodename, + + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), + + %% The test_server node has the same cookie as the ssl nodes + %% but it should not be able to communicate with the ssl nodes + %% via the erlang distribution. + pang = net_adm:ping(Node1), + pang = net_adm:ping(Node2), + + %% + %% Check that we are able to communicate over the erlang + %% distribution between the ssl nodes. + %% + Ref = make_ref(), + spawn(fun () -> + apply_on_ssl_node( + NH1, + fun () -> + tstsrvr_format("Hi from ~p!~n", [node()]), + send_to_tstcntrl({Ref, self()}), + receive + {From, ping} -> + tstsrvr_format("Received ping ~p!~n", [node()]), + From ! {self(), pong} + end + end) + end), + receive + {Ref, SslPid} -> + ok = apply_on_ssl_node( + NH2, + fun () -> + tstsrvr_format("Hi from ~p!~n", [node()]), + SslPid ! {self(), ping}, + receive + {SslPid, pong} -> + ok + end + end) + end, + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% Internal functions %% +%% %% + +%% +%% ssl_node side api +%% + +tstsrvr_format(Fmt, ArgList) -> + send_to_tstsrvr({format, Fmt, ArgList}). + +send_to_tstcntrl(Message) -> + send_to_tstsrvr({message, Message}). + + +%% +%% test_server side api +%% + +apply_on_ssl_node(Node, M, F, A) when atom(M), atom(F), list(A) -> + Ref = make_ref(), + send_to_ssl_node(Node, {apply, self(), Ref, M, F, A}), + receive + {Ref, Result} -> + Result + end. + +apply_on_ssl_node(Node, Fun) when is_function(Fun, 0) -> + Ref = make_ref(), + send_to_ssl_node(Node, {apply, self(), Ref, Fun}), + receive + {Ref, Result} -> + Result + end. + +stop_ssl_node(#node_handle{connection_handler = Handler, + socket = Socket, + name = Name}) -> + ?t:format("Trying to stop ssl node ~s.~n", [Name]), + Mon = erlang:monitor(process, Handler), + unlink(Handler), + case gen_tcp:send(Socket, term_to_binary(stop)) of + ok -> + receive + {'DOWN', Mon, process, Handler, Reason} -> + case Reason of + normal -> ok; + _ -> exit(Reason) + end + end; + Error -> + erlang:demonitor(Mon, [flush]), + exit(Error) + end. + +start_ssl_node(Config) -> + start_ssl_node(Config, ""). + +start_ssl_node(Config, XArgs) -> + Name = mk_node_name(Config), + SSL = ?config(ssl_opts, Config), + SSLDistOpts = setup_dist_opts(Name, ?config(priv_dir, Config)), + start_ssl_node_raw(Name, SSL ++ " " ++ SSLDistOpts ++ XArgs). + +start_ssl_node_raw(Name, Args) -> + {ok, LSock} = gen_tcp:listen(0, + [binary, {packet, 4}, {active, false}]), + {ok, ListenPort} = inet:port(LSock), + CmdLine = mk_node_cmdline(ListenPort, Name, Args), + ?t:format("Attempting to start ssl node ~s: ~s~n", [Name, CmdLine]), + case open_port({spawn, CmdLine}, []) of + Port when port(Port) -> + unlink(Port), + erlang:port_close(Port), + case await_ssl_node_up(Name, LSock) of + #node_handle{} = NodeHandle -> + ?t:format("Ssl node ~s started.~n", [Name]), + NodeName = list_to_atom(Name ++ "@" ++ host_name()), + NodeHandle#node_handle{nodename = NodeName}; + Error -> + exit({failed_to_start_node, Name, Error}) + end; + Error -> + exit({failed_to_start_node, Name, Error}) + end. + +%% +%% command line creation +%% + +host_name() -> + [$@ | Host] = lists:dropwhile(fun ($@) -> false; (_) -> true end, + atom_to_list(node())), + Host. + +mk_node_name(Config) -> + {A, B, C} = erlang:now(), + Case = ?config(testcase, Config), + atom_to_list(?MODULE) + ++ "_" + ++ atom_to_list(Case) + ++ "_" + ++ integer_to_list(A) + ++ "-" + ++ integer_to_list(B) + ++ "-" + ++ integer_to_list(C). + +mk_node_cmdline(ListenPort, Name, Args) -> + Static = "-detached -noinput", + Pa = filename:dirname(code:which(?MODULE)), + Prog = case catch init:get_argument(progname) of + {ok,[[P]]} -> P; + _ -> exit(no_progname_argument_found) + end, + NameSw = case net_kernel:longnames() of + false -> "-sname "; + _ -> "-name " + end, + {ok, Pwd} = file:get_cwd(), + Prog ++ " " + ++ Static ++ " " + ++ NameSw ++ " " ++ Name ++ " " + ++ "-pa " ++ Pa ++ " " + ++ "-run application start crypto -run application start public_key " + ++ "-run " ++ atom_to_list(?MODULE) ++ " cnct2tstsrvr " + ++ host_name() ++ " " + ++ integer_to_list(ListenPort) ++ " " + ++ Args ++ " " + ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ Name ++ " " + ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()). + +%% +%% Connection handler test_server side +%% + +await_ssl_node_up(Name, LSock) -> + case gen_tcp:accept(LSock, ?AWAIT_SLL_NODE_UP_TIMEOUT) of + timeout -> + gen_tcp:close(LSock), + ?t:format("Timeout waiting for ssl node ~s to come up~n", + [Name]), + timeout; + {ok, Socket} -> + gen_tcp:close(LSock), + case gen_tcp:recv(Socket, 0) of + {ok, Bin} -> + check_ssl_node_up(Socket, Name, Bin); + {error, closed} -> + gen_tcp:close(Socket), + exit({lost_connection_with_ssl_node_before_up, Name}) + end; + {error, Error} -> + gen_tcp:close(LSock), + exit({accept_failed, Error}) + end. + +check_ssl_node_up(Socket, Name, Bin) -> + case catch binary_to_term(Bin) of + {'EXIT', _} -> + gen_tcp:close(Socket), + exit({bad_data_received_from_ssl_node, Name, Bin}); + {ssl_node_up, NodeName} -> + case list_to_atom(Name++"@"++host_name()) of + NodeName -> + Parent = self(), + Go = make_ref(), + %% Spawn connection handler on test server side + Pid = spawn_link( + fun () -> + receive Go -> ok end, + tstsrvr_con_loop(Name, Socket, Parent) + end), + ok = gen_tcp:controlling_process(Socket, Pid), + Pid ! Go, + #node_handle{connection_handler = Pid, + socket = Socket, + name = Name}; + _ -> + exit({unexpected_ssl_node_connected, NodeName}) + end; + Msg -> + exit({unexpected_msg_instead_of_ssl_node_up, Name, Msg}) + end. + +send_to_ssl_node(#node_handle{connection_handler = Hndlr}, Term) -> + Hndlr ! {relay_to_ssl_node, term_to_binary(Term)}, + ok. + +tstsrvr_con_loop(Name, Socket, Parent) -> + inet:setopts(Socket,[{active,once}]), + receive + {relay_to_ssl_node, Data} when is_binary(Data) -> + case gen_tcp:send(Socket, Data) of + ok -> + ok; + _Error -> + gen_tcp:close(Socket), + exit({failed_to_relay_data_to_ssl_node, Name, Data}) + end; + {tcp, Socket, Bin} -> + case catch binary_to_term(Bin) of + {'EXIT', _} -> + gen_tcp:close(Socket), + exit({bad_data_received_from_ssl_node, Name, Bin}); + {format, FmtStr, ArgList} -> + ?t:format(FmtStr, ArgList); + {message, Msg} -> + ?t:format("Got message ~p", [Msg]), + Parent ! Msg; + {apply_res, To, Ref, Res} -> + To ! {Ref, Res}; + bye -> + ?t:format("Ssl node ~s stopped.~n", [Name]), + gen_tcp:close(Socket), + exit(normal); + Unknown -> + exit({unexpected_message_from_ssl_node, Name, Unknown}) + end; + {tcp_closed, Socket} -> + gen_tcp:close(Socket), + exit({lost_connection_with_ssl_node, Name}) + end, + tstsrvr_con_loop(Name, Socket, Parent). + +%% +%% Connection handler ssl_node side +%% + +% cnct2tstsrvr() is called via command line arg -run ... +cnct2tstsrvr([Host, Port]) when list(Host), list(Port) -> + %% Spawn connection handler on ssl node side + ConnHandler + = spawn(fun () -> + case catch gen_tcp:connect(Host, + list_to_integer(Port), + [binary, + {packet, 4}, + {active, false}]) of + {ok, Socket} -> + notify_ssl_node_up(Socket), + ets:new(test_server_info, + [set, + public, + named_table, + {keypos, 1}]), + ets:insert(test_server_info, + {test_server_handler, self()}), + ssl_node_con_loop(Socket); + _Error -> + halt("Failed to connect to test server") + end + end), + spawn(fun () -> + Mon = erlang:monitor(process, ConnHandler), + receive + {'DOWN', Mon, process, ConnHandler, Reason} -> + receive after 1000 -> ok end, + halt("test server connection handler terminated: " + ++ + lists:flatten(io_lib:format("~p", [Reason]))) + end + end). + +notify_ssl_node_up(Socket) -> + case catch gen_tcp:send(Socket, + term_to_binary({ssl_node_up, node()})) of + ok -> ok; + _ -> halt("Failed to notify test server that I'm up") + end. + +send_to_tstsrvr(Term) -> + case catch ets:lookup_element(test_server_info, test_server_handler, 2) of + Hndlr when pid(Hndlr) -> + Hndlr ! {relay_to_test_server, term_to_binary(Term)}, ok; + _ -> + receive after 200 -> ok end, + send_to_tstsrvr(Term) + end. + +ssl_node_con_loop(Socket) -> + inet:setopts(Socket,[{active,once}]), + receive + {relay_to_test_server, Data} when is_binary(Data) -> + case gen_tcp:send(Socket, Data) of + ok -> + ok; + _Error -> + gen_tcp:close(Socket), + halt("Failed to relay data to test server") + end; + {tcp, Socket, Bin} -> + case catch binary_to_term(Bin) of + {'EXIT', _} -> + gen_tcp:close(Socket), + halt("test server sent me bad data"); + {apply, From, Ref, M, F, A} -> + spawn_link( + fun () -> + send_to_tstsrvr({apply_res, + From, + Ref, + (catch apply(M, F, A))}) + end); + {apply, From, Ref, Fun} -> + spawn_link(fun () -> + send_to_tstsrvr({apply_res, + From, + Ref, + (catch Fun())}) + end); + stop -> + gen_tcp:send(Socket, term_to_binary(bye)), + gen_tcp:close(Socket), + init:stop(), + receive after infinity -> ok end; + _Unknown -> + halt("test server sent me an unexpected message") + end; + {tcp_closed, Socket} -> + halt("Lost connection to test server") + end, + ssl_node_con_loop(Socket). + +%% +%% Setup ssl dist info +%% + +rand_bin(N) -> + rand_bin(N, []). + +rand_bin(0, Acc) -> + Acc; +rand_bin(N, Acc) -> + rand_bin(N-1, [random:uniform(256)-1|Acc]). + +make_randfile(Dir) -> + {ok, IoDev} = file:open(filename:join([Dir, "RAND"]), [write]), + {A, B, C} = erlang:now(), + random:seed(A, B, C), + ok = file:write(IoDev, rand_bin(1024)), + file:close(IoDev). + +append_files(FileNames, ResultFileName) -> + {ok, ResultFile} = file:open(ResultFileName, [write]), + do_append_files(FileNames, ResultFile). + +do_append_files([], RF) -> + ok = file:close(RF); +do_append_files([F|Fs], RF) -> + {ok, Data} = file:read_file(F), + ok = file:write(RF, Data), + do_append_files(Fs, RF). + +setup_dist_opts(Name, PrivDir) -> + NodeDir = filename:join([PrivDir, Name]), + RGenDir = filename:join([NodeDir, "rand_gen"]), + ok = file:make_dir(NodeDir), + ok = file:make_dir(RGenDir), + make_randfile(RGenDir), + make_certs:all(RGenDir, NodeDir), + SDir = filename:join([NodeDir, "server"]), + SC = filename:join([SDir, "cert.pem"]), + SK = filename:join([SDir, "key.pem"]), + SKC = filename:join([SDir, "keycert.pem"]), + append_files([SK, SC], SKC), + CDir = filename:join([NodeDir, "client"]), + CC = filename:join([CDir, "cert.pem"]), + CK = filename:join([CDir, "key.pem"]), + CKC = filename:join([CDir, "keycert.pem"]), + append_files([CK, CC], CKC), + "-proto_dist inet_tls " + ++ "-ssl_dist_opt server_certfile " ++ SKC ++ " " + ++ "-ssl_dist_opt client_certfile " ++ CKC ++ " ". + +%% +%% Start scripts etc... +%% + +add_ssl_opts_config(Config) -> + %% + %% Start with boot scripts if on an installed system; otherwise, + %% just point out ssl ebin with -pa. + %% + try + Dir = ?config(priv_dir, Config), + LibDir = code:lib_dir(), + Apps = application:which_applications(), + {value, {stdlib, _, STDL_VSN}} = lists:keysearch(stdlib, 1, Apps), + {value, {kernel, _, KRNL_VSN}} = lists:keysearch(kernel, 1, Apps), + StdlDir = filename:join([LibDir, "stdlib-" ++ STDL_VSN]), + KrnlDir = filename:join([LibDir, "kernel-" ++ KRNL_VSN]), + {ok, _} = file:read_file_info(StdlDir), + {ok, _} = file:read_file_info(KrnlDir), + SSL_VSN = vsn(ssl), + VSN_CRYPTO = vsn(crypto), + VSN_PKEY = vsn(public_key), + + SslDir = filename:join([LibDir, "ssl-" ++ SSL_VSN]), + {ok, _} = file:read_file_info(SslDir), + %% We are using an installed otp system, create the boot script. + Script = filename:join(Dir, atom_to_list(?MODULE)), + {ok, RelFile} = file:open(Script ++ ".rel", [write]), + io:format(RelFile, + "{release, ~n" + " {\"SSL distribution test release\", \"~s\"},~n" + " {erts, \"~s\"},~n" + " [{kernel, \"~s\"},~n" + " {stdlib, \"~s\"},~n" + " {crypto, \"~s\"},~n" + " {public_key, \"~s\"},~n" + " {ssl, \"~s\"}]}.~n", + [case catch erlang:system_info(otp_release) of + {'EXIT', _} -> "R11B"; + Rel -> Rel + end, + erlang:system_info(version), + KRNL_VSN, + STDL_VSN, + VSN_CRYPTO, + VSN_PKEY, + SSL_VSN]), + ok = file:close(RelFile), + ok = systools:make_script(Script, []), + [{ssl_opts, "-boot " ++ Script} | Config] + catch + _:_ -> + [{ssl_opts, "-pa " ++ filename:dirname(code:which(ssl))} + | add_comment_config( + "Bootscript wasn't used since the test wasn't run on an " + "installed OTP system.", + Config)] + end. + +%% +%% Add common comments to config +%% + +add_comment_config(Comment, []) -> + [{comment, Comment}]; +add_comment_config(Comment, [{comment, OldComment} | Cs]) -> + [{comment, Comment ++ " " ++ OldComment} | Cs]; +add_comment_config(Comment, [C|Cs]) -> + [C|add_comment_config(Comment, Cs)]. + +%% +%% Call when test case success +%% + +success(Config) -> + case lists:keysearch(comment, 1, Config) of + {value, {comment, _} = Res} -> Res; + _ -> ok + end. + +vsn(App) -> + application:start(App), + try + {value, + {ssl, + _, + VSN}} = lists:keysearch(App, + 1, + application:which_applications()), + VSN + after + application:stop(ssl) + end. -- cgit v1.2.3