1%%--------------------------------------------------------------------
2%%
3%% %CopyrightBegin%
4%%
5%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
6%%
7%% Licensed under the Apache License, Version 2.0 (the "License");
8%% you may not use this file except in compliance with the License.
9%% You may obtain a copy of the License at
10%%
11%%     http://www.apache.org/licenses/LICENSE-2.0
12%%
13%% Unless required by applicable law or agreed to in writing, software
14%% distributed under the License is distributed on an "AS IS" BASIS,
15%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16%% See the License for the specific language governing permissions and
17%% limitations under the License.
18%%
19%% %CopyrightEnd%
20%%
21%%
22%%-----------------------------------------------------------------
23%% File: orber_iiop_net.erl
24%%
25%% Description:
26%%    This file contains the IIOP communication server
27%%
28%%-----------------------------------------------------------------
29-module(orber_iiop_net).
30
31-behaviour(gen_server).
32
33-include_lib("orber/src/orber_iiop.hrl").
34
35%%-----------------------------------------------------------------
36%% External exports
37%%-----------------------------------------------------------------
38-export([start/1, connect/5, connections/0,
39	 sockname2peername/2, peername2sockname/2,
40	 add_connection/5,
41	 add/3, remove/1, reconfigure/1, reconfigure/2]).
42
43%%-----------------------------------------------------------------
44%% Internal exports
45%%-----------------------------------------------------------------
46-export([init/1, terminate/2, handle_call/3,
47	 handle_cast/2, handle_info/2, code_change/3]).
48
49%%-----------------------------------------------------------------
50%% Server state record and definitions
51%%-----------------------------------------------------------------
52-define(CONNECTION_DB, orber_iiop_net_db).
53
54-record(state, {ports=[], max_connections, db, counter = 1, queue}).
55
56-record(connection, {pid, socket, type, peerdata, localdata, ref = 0}).
57
58-record(listen, {pid, socket, port, type, ref = 0, options, proxy_options = []}).
59
60%%-----------------------------------------------------------------
61%% External interface functions
62%%-----------------------------------------------------------------
63%%-----------------------------------------------------------------
64%% Func: start/1
65%%-----------------------------------------------------------------
66start(Opts) ->
67    gen_server:start_link({local, orber_iiop_net}, orber_iiop_net, Opts, []).
68
69add(IP, normal, Options) ->
70    Port = orber_tb:keysearch(iiop_port, Options, orber_env:iiop_port()),
71    gen_server:call(orber_iiop_net, {add, IP, normal, Port, Options}, infinity);
72add(IP, ssl, Options) ->
73    Port = orber_tb:keysearch(iiop_ssl_port, Options, orber_env:iiop_ssl_port()),
74    gen_server:call(orber_iiop_net, {add, IP, ssl, Port, Options}, infinity).
75
76remove(Ref) ->
77    gen_server:call(orber_iiop_net, {remove, Ref}, infinity).
78
79reconfigure(Options) ->
80    lists:foreach(fun(P) ->
81			  P !  {reconfigure, Options}
82		  end,
83		  do_select([{#connection{pid = '$1', _='_'},
84			      [], ['$1']}])).
85
86reconfigure(Options, Ref) ->
87    case do_select([{#connection{ref = Ref, pid = '$1', _='_'},
88		     [], ['$1']}]) of
89	[Pid] when is_pid(Pid) ->
90	    Pid !  {reconfigure, Options},
91	    ok;
92	_ ->
93	    {error, "No proxy matched the supplied reference"}
94    end.
95
96connect(Type, S, AcceptPid, Ref, ProxyOptions) ->
97    gen_server:call(orber_iiop_net, {connect, Type, S, AcceptPid,
98				     Ref, ProxyOptions}, infinity).
99
100connections() ->
101    do_select([{#connection{peerdata = '$1', _='_'}, [], ['$1']}]).
102
103sockname2peername(SockHost, SockPort) ->
104    do_select([{#connection{peerdata = '$1',
105			    localdata = {match_type(SockHost),
106					 match_type(SockPort)},
107			    _='_'}, [], ['$1']}]).
108
109
110peername2sockname(PeerHost, PeerPort) ->
111    do_select([{#connection{peerdata = {match_type(PeerHost),
112					match_type(PeerPort)},
113			    localdata = '$1',
114			    _='_'}, [], ['$1']}]).
115
116do_select(Pattern) ->
117    case catch ets:select(?CONNECTION_DB, Pattern) of
118	{'EXIT', _What} ->
119	    [];
120	Result ->
121	    Result
122    end.
123
124match_type(0) ->
125    %% Wildcard port number
126    '_';
127match_type("") ->
128    %% Wildcard host
129    '_';
130match_type(Key) ->
131    %% Wildcard not used.
132    Key.
133
134add_connection(Socket, Type, PeerData, LocalData, Ref) ->
135    ets:insert(?CONNECTION_DB, #connection{pid = self(), socket = Socket,
136					   type = Type, peerdata = PeerData,
137					   localdata = LocalData, ref = Ref}).
138
139%%-----------------------------------------------------------------
140%% Server functions
141%%-----------------------------------------------------------------
142%%-----------------------------------------------------------------
143%% Func: init/1
144%%-----------------------------------------------------------------
145init(Options) ->
146    process_flag(trap_exit, true),
147    {ok, parse_options(Options,
148		       #state{max_connections = orber:iiop_max_in_connections(),
149			      db = ets:new(?CONNECTION_DB, [set, public,
150							    named_table,
151							    {keypos, 2}]),
152			      queue = queue:new()})}.
153
154%%-----------------------------------------------------------------
155%% Func: terminate/1
156%%-----------------------------------------------------------------
157terminate(_Reason, _State) ->
158    ok.
159
160%%-----------------------------------------------------------------
161%% Func: get_options/2
162%%-----------------------------------------------------------------
163get_options(normal, _Options) ->
164    [];
165get_options(ssl, Options) ->
166    SSLOpts =
167	case orber_tb:keysearch(ssl_server_options, Options,
168				orber_env:ssl_server_options()) of
169	    [] ->
170		Verify = orber_tb:keysearch(ssl_server_verify, Options,
171					    orber_env:ssl_server_verify()),
172		Depth = orber_tb:keysearch(ssl_server_depth, Options,
173					   orber_env:ssl_server_depth()),
174		Cert = orber_tb:keysearch(ssl_server_certfile, Options,
175					  orber_env:ssl_server_certfile()),
176		CaCert = orber_tb:keysearch(ssl_server_cacertfile, Options,
177					    orber_env:ssl_server_cacertfile()),
178		Pwd = orber_tb:keysearch(ssl_server_password, Options,
179					 orber_env:ssl_server_password()),
180		Key = orber_tb:keysearch(ssl_server_keyfile, Options,
181					 orber_env:ssl_server_keyfile()),
182		Ciphers = orber_tb:keysearch(ssl_server_ciphers, Options,
183					     orber_env:ssl_server_ciphers()),
184		Timeout = orber_tb:keysearch(ssl_server_cachetimeout, Options,
185					     orber_env:ssl_server_cachetimeout()),
186		KeepAlive = orber_tb:keysearch(ssl_server_cachetimeout, Options,
187					       orber_env:iiop_ssl_in_keepalive()),
188		[{verify, Verify},
189		 {depth, Depth},
190		 {certfile, Cert},
191		 {cacertfile, CaCert},
192		 {password, Pwd},
193		 {keyfile, Key},
194		 {ciphers, Ciphers},
195		 {cachetimeout, Timeout},
196		 {keepalive, KeepAlive}];
197	    Opts ->
198		case orber_tb:check_illegal_tcp_options(Opts) of
199		    ok ->
200			check_old_ssl_server_options(Options),
201			Opts;
202		    {error, IllegalOpts} ->
203			error_logger:error_report([{application, orber},
204						   "TCP options not allowed to set on a connection",
205						   IllegalOpts]),
206			error("Illegal TCP option")
207		end
208	end,
209    ssl_server_extra_options(SSLOpts, []).
210
211%%-----------------------------------------------------------------
212%% Func: parse_options/2
213%%-----------------------------------------------------------------
214parse_options([{port, Type, Port} | Rest], State) ->
215    Options = get_options(Type, []),
216    Family = orber_env:ip_version(),
217    IPFamilyOptions =
218	case Family of
219	    inet -> [inet];
220	    inet6 -> [inet6, {ipv6_v6only, true}]
221	end,
222    Options2 =
223	case orber_env:ip_address_variable_defined() of
224	    false ->
225		IPFamilyOptions ++ Options;
226	    Host ->
227		{ok, IP} = inet:getaddr(Host, Family),
228		IPFamilyOptions ++ [{ip, IP} |Options]
229	end,
230
231    {ok, Listen, NewPort} = orber_socket:listen(Type, Port, Options2, true),
232    {ok, Pid} = orber_iiop_socketsup:start_accept(Type, Listen, 0),
233    link(Pid),
234    ets:insert(?CONNECTION_DB, #listen{pid = Pid, socket = Listen,
235				       port = NewPort, type = Type,
236				       options = Options2}),
237    parse_options(Rest, State);
238parse_options([], State) ->
239    State.
240
241ssl_server_extra_options([], Acc) ->
242    Acc;
243ssl_server_extra_options([{_Type, []}|T], Acc) ->
244    ssl_server_extra_options(T, Acc);
245ssl_server_extra_options([{_Type, infinity}|T], Acc) ->
246    ssl_server_extra_options(T, Acc);
247ssl_server_extra_options([{Type, Value}|T], Acc) ->
248    ssl_server_extra_options(T, [{Type, Value}|Acc]).
249
250filter_options([], Acc) ->
251    Acc;
252filter_options([{verify, _}|T], Acc) ->
253    filter_options(T, Acc);
254filter_options([{depth, _}|T], Acc) ->
255    filter_options(T, Acc);
256filter_options([{certfile, _}|T], Acc) ->
257    filter_options(T, Acc);
258filter_options([{cacertfile, _}|T], Acc) ->
259    filter_options(T, Acc);
260filter_options([{password, _}|T], Acc) ->
261    filter_options(T, Acc);
262filter_options([{keyfile, _}|T], Acc) ->
263    filter_options(T, Acc);
264filter_options([{ciphers, _}|T], Acc) ->
265    filter_options(T, Acc);
266filter_options([{cachetimeout, _}|T], Acc) ->
267    filter_options(T, Acc);
268filter_options([H|T], Acc) ->
269    filter_options(T, [H|Acc]).
270
271%%-----------------------------------------------------------------
272%% Func: handle_call/3
273%%-----------------------------------------------------------------
274handle_call({remove, Ref}, _From, State) ->
275    case do_select([{#listen{ref = Ref, pid = '$1', socket = '$2',
276			     type = '$3', _='_'}, [], [{{'$1', '$2', '$3'}}]}]) of
277	[{Pid, Listen, Type}|_] when is_pid(Pid) ->
278	    unlink(Pid),
279	    ets:delete(?CONNECTION_DB, Pid),
280	    %% Just close the listen socket. Will cause the accept processs
281	    %% to terminate.
282	    orber_socket:close(Type, Listen),
283	    stop_proxies(do_select([{#connection{ref = Ref, pid = '$1', _='_'},
284				     [], ['$1']}])),
285	    {reply, ok,
286	     State#state{queue =
287			 from_list(
288			   lists:keydelete(Pid, 1,
289					   queue:to_list(State#state.queue)))}};
290	_ ->
291	    {reply, ok, State}
292    end;
293handle_call({add, IP, Type, Port, AllOptions}, _From, State) ->
294    Family = orber_tb:keysearch(ip_family, AllOptions, orber_env:ip_version()),
295    IPFamilyOptions =
296	case Family of
297	    inet -> [inet];
298	    inet6 -> [inet6, {ipv6_v6only, true}]
299	end,
300    case inet:getaddr(IP, Family) of
301	{ok, IPTuple} ->
302	    try
303		Options = IPFamilyOptions ++ [{ip, IPTuple} |get_options(Type, AllOptions)],
304	        Ref = make_ref(),
305	        ProxyOptions = filter_options(AllOptions, []),
306	        case orber_socket:listen(Type, Port, Options, false) of
307		    {ok, Listen, NewPort} ->
308			{ok, Pid} = orber_iiop_socketsup:start_accept(Type, Listen, Ref,
309								      ProxyOptions),
310			link(Pid),
311			ets:insert(?CONNECTION_DB, #listen{pid = Pid,
312							   socket = Listen,
313							   port = NewPort,
314							   type = Type, ref = Ref,
315							   options = Options,
316							   proxy_options = ProxyOptions}),
317			{reply, {ok, Ref}, State};
318		    Error ->
319			{reply, Error, State}
320		end
321            catch
322		error:Reason ->
323		    {reply, {error, Reason}, State}
324	    end;
325	Other ->
326	    {reply, Other, State}
327    end;
328handle_call({connect, Type, Socket, _AcceptPid, AccepRef, ProxyOptions}, _From, State)
329  when State#state.max_connections == infinity;
330       State#state.max_connections > State#state.counter ->
331    case catch access_allowed(Type, Socket, Type) of
332	true ->
333	    case orber_iiop_insup:start_connection(Type, Socket,
334						   AccepRef, ProxyOptions) of
335		{ok, Pid} when is_pid(Pid) ->
336		    link(Pid),
337		    {reply, {ok, Pid, true}, update_counter(State, 1)};
338		Other ->
339		    {reply, Other, State}
340	    end;
341	_ ->
342	    {H, P} = orber_socket:peerdata(Type, Socket),
343	    orber_tb:info("Blocked connect attempt from ~s - ~p", [H, P]),
344	    {reply, denied, State}
345    end;
346handle_call({connect, Type, Socket, AcceptPid, AccepRef, ProxyOptions}, _From,
347	    #state{queue = Q} = State) ->
348    case catch access_allowed(Type, Socket, Type) of
349	true ->
350	    case orber_iiop_insup:start_connection(Type, Socket,
351						   AccepRef, ProxyOptions) of
352		{ok, Pid} when is_pid(Pid) ->
353		    link(Pid),
354		    Ref = erlang:make_ref(),
355		    {reply, {ok, Pid, Ref},
356		     update_counter(State#state{queue =
357						queue:in({AcceptPid, Ref}, Q)}, 1)};
358		Other ->
359		    {reply, Other, State}
360	    end;
361	_ ->
362	    {H, P} = orber_socket:peerdata(Type, Socket),
363	    orber_tb:info("Blocked connect attempt from ~s - ~p", [H, P]),
364	    {reply, denied, State}
365    end;
366handle_call(_, _, State) ->
367    {noreply, State}.
368
369stop_proxies([H|T]) ->
370    catch orber_iiop_inproxy:stop(H),
371    stop_proxies(T);
372stop_proxies([]) ->
373    ok.
374
375access_allowed(Type, Socket, Type) ->
376    Flags = orber:get_flags(),
377    case ?ORB_FLAG_TEST(Flags, ?ORB_ENV_USE_ACL_INCOMING) of
378	false ->
379	    true;
380	true ->
381	    SearchFor =
382		case Type of
383		    normal ->
384			tcp_in;
385		    ssl ->
386			ssl_in
387		end,
388	    {ok, {Host, Port}} = orber_socket:peername(Type, Socket),
389	    case orber_acl:match(Host, SearchFor, true) of
390		{true, [], 0} ->
391		    true;
392		{true, [], Port} ->
393		    true;
394		{true, [], {Min, Max}} when Port >= Min, Port =< Max ->
395		    true;
396		{true, Interfaces, 0} ->
397		    get_sockethost(Type, Socket),
398		    lists:member(get_sockethost(Type, Socket), Interfaces);
399		{true, Interfaces, Port} ->
400		    lists:member(get_sockethost(Type, Socket), Interfaces);
401		{true, Interfaces, {Min, Max}} when Port >= Min, Port =< Max ->
402		    lists:member(get_sockethost(Type, Socket), Interfaces);
403		_ ->
404		    false
405	    end
406    end.
407
408get_sockethost(Type, Socket) ->
409    case orber_socket:peername(Type, Socket) of
410	{ok, {Addr, _Port}} ->
411	    orber_env:addr2str(Addr);
412	_ ->
413	    false
414    end.
415
416%%------------------------------------------------------------
417%% Standard gen_server cast handle
418%%------------------------------------------------------------
419handle_cast(_, State) ->
420    {noreply,  State}.
421
422%%------------------------------------------------------------
423%% Standard gen_server handles
424%%------------------------------------------------------------
425handle_info({'EXIT', Pid, _Reason}, State) when is_pid(Pid) ->
426    case ets:lookup(?CONNECTION_DB, Pid) of
427	[#listen{pid = Pid, socket = Listen, port = Port, type = Type,
428		 ref = Ref, options = Options, proxy_options = POpts}] ->
429	    ets:delete(?CONNECTION_DB, Pid),
430	    unlink(Pid),
431	    {ok, NewPid} = orber_iiop_socketsup:start_accept(Type, Listen,
432							     Ref, POpts),
433	    link(NewPid),
434	    ets:insert(?CONNECTION_DB, #listen{pid = NewPid, socket = Listen,
435					       port = Port, type = Type,
436					       ref = Ref, options = Options,
437					       proxy_options = POpts}),
438	    %% Remove the connection if it's in the queue.
439	    {noreply,
440	     State#state{queue =
441			 from_list(
442			   lists:keydelete(Pid, 1,
443					   queue:to_list(State#state.queue)))}};
444	[#connection{pid = Pid}] ->
445	    ets:delete(?CONNECTION_DB, Pid),
446	    unlink(Pid),
447	    case queue:out(State#state.queue) of
448		{empty, _} ->
449		    {noreply, update_counter(State, -1)};
450		{{value, {AcceptPid, Ref}}, Q} ->
451		    AcceptPid ! {Ref, ok},
452		    {noreply, update_counter(State#state{queue = Q}, -1)}
453	    end;
454	[] ->
455	    {noreply, State}
456    end;
457handle_info(_, State) ->
458    {noreply,  State}.
459
460from_list(List) ->
461    from_list(List, queue:new()).
462
463from_list([], Q) ->
464    Q;
465from_list([H|T], Q) ->
466    NewQ = queue:in(H, Q),
467    from_list(T, NewQ).
468
469
470%%-----------------------------------------------------------------
471%% Func: code_change/3
472%%-----------------------------------------------------------------
473code_change(_OldVsn, State, _Extra) ->
474    {ok, State}.
475
476%%-----------------------------------------------------------------
477%% Internal Functions
478%%-----------------------------------------------------------------
479update_counter(#state{max_connections = infinity} = State, _) ->
480    State;
481update_counter(State, Value) ->
482    State#state{counter = State#state.counter + Value}.
483
484
485check_old_ssl_server_options(Options) ->
486    try
487	0 = orber_tb:keysearch(ssl_server_verify, Options,
488			       orber_env:ssl_server_verify()),
489    	1 = orber_tb:keysearch(ssl_server_depth, Options,
490			       orber_env:ssl_server_depth()),
491     	[] = orber_tb:keysearch(ssl_server_certfile, Options,
492				orber_env:ssl_server_certfile()),
493	[] = orber_tb:keysearch(ssl_server_cacertfile, Options,
494				orber_env:ssl_server_cacertfile()),
495	[] = orber_tb:keysearch(ssl_server_password, Options,
496				orber_env:ssl_server_password()),
497	[] = orber_tb:keysearch(ssl_server_keyfile, Options,
498				orber_env:ssl_server_keyfile()),
499	[] = orber_tb:keysearch(ssl_server_ciphers, Options,
500				orber_env:ssl_server_ciphers()),
501	infinity = orber_tb:keysearch(ssl_server_cachetimeout, Options,
502				      orber_env:ssl_server_cachetimeout()),
503	false = orber_tb:keysearch(iiop_ssl_in_keepalive, Options,
504				   orber_env:iiop_ssl_in_keepalive())
505    catch
506	_:_ ->
507					      io:format("hej\n",[]),
508	    error_logger:warning_report([{application, orber},
509			 "Ignoring deprecated ssl server options used together with the ssl_server_options"])
510    end.
511
512