1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1997-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(inet_config).
21
22-include("inet_config.hrl").
23-include("inet.hrl").
24
25-import(lists, [foreach/2, member/2, reverse/1]).
26
27%% Avoid warning for local function error/2 clashing with autoimported BIF.
28-compile({no_auto_import,[error/2]}).
29-export([init/0]).
30
31-export([do_load_resolv/2]).
32
33%%
34%% Must be called after inet_db:start
35%%
36%% Order in which to load inet_db data:
37%% 1. Hostname  (possibly derive domain and search)
38%% 2. OS default  /etc/resolv.conf,  Windows registry etc
39%%    a) Hosts database
40%%    b) Resolver options
41%% 3. Config (kernel app)
42%% 4. Root   (otp root)
43%% 5. Home   (user inetrc)
44%%
45%%
46-spec init() -> 'ok'.
47init() ->
48    set_hostname(),
49
50    %% Note: In shortnames (or non-distributed) mode we don't need to know
51    %% our own domain name. In longnames mode we do and we can't rely on
52    %% the user to provide it (by means of inetrc), so we need to look
53    %% for it ourselves.
54
55    OsType = os:type(),
56    do_load_resolv(OsType, erl_dist_mode()),
57
58    case OsType of
59	{unix,Type} ->
60	    if Type =:= linux ->
61		    %% It may be the case that the domain name was not set
62		    %% because the hostname was short. But NOW we can look it
63		    %% up and get the long name and the domain name from it.
64
65		    %% FIXME: The second call to set_hostname will insert
66		    %% a duplicate entry in the search list.
67
68		    case inet_db:res_option(domain) of
69			"" ->
70			    case inet:gethostbyname(inet_db:gethostname()) of
71				{ok,#hostent{h_name = []}} ->
72				    ok;
73				{ok,#hostent{h_name = HostName}} ->
74				    set_hostname({ok,HostName});
75				_ ->
76				    ok
77			    end;
78			_ ->
79			    ok
80		    end;
81	       true -> ok
82	    end,
83	    add_dns_lookup(inet_db:res_option(lookup));
84	_ ->
85	    ok
86    end,
87
88    %% Read inetrc file, if it exists.
89    {RcFile,CfgFiles,CfgList} = read_rc(),
90
91    %% Possibly read config files or system registry
92    lists:foreach(fun({file,hosts,File}) ->
93			  load_hosts(File, unix);
94		     ({file,Func,File}) ->
95			  load_resolv(File, Func);
96		     ({registry,win32}) ->
97			  case OsType of
98			      {win32,WinType} ->
99				  win32_load_from_registry(WinType);
100			      _ ->
101				  error("cannot read win32 system registry~n", [])
102			  end
103		  end, CfgFiles),
104
105    %% Add inetrc config entries
106    case inet_db:add_rc_list(CfgList) of
107	ok -> ok;
108	_  -> error("syntax error in ~ts~n", [RcFile])
109    end,
110
111    %% Set up a resolver configuration file for inet_res,
112    %% unless that already has been done
113    case OsType of
114	{unix,_} ->
115	    %% The Etc variable enables us to run tests with other
116	    %% configuration files than the normal ones
117	    Etc = os:getenv("ERL_INET_ETC_DIR", ?DEFAULT_ETC),
118	    case inet_db:res_option(resolv_conf) of
119		undefined ->
120		    inet_db:res_option(
121		      resolv_conf_name,
122		      filename:join(Etc, ?DEFAULT_RESOLV));
123		_ -> ok
124	    end,
125	    case inet_db:res_option(hosts_file) of
126		undefined ->
127		    inet_db:res_option(
128		      hosts_file_name,
129		      filename:join(Etc, ?DEFAULT_HOSTS));
130		_ -> ok
131	    end;
132	_ -> ok
133    end.
134
135
136
137erl_dist_mode() ->
138    case init:get_argument(sname) of
139	{ok,[[_SName]]} -> shortnames;
140	_ ->
141	    case init:get_argument(name) of
142		{ok,[[_Name]]} -> longnames;
143		_ -> nonames
144	    end
145    end.
146
147do_load_resolv({unix,Type}, longnames) ->
148    %% The Etc variable enables us to run tests with other
149    %% configuration files than the normal ones
150    Etc = os:getenv("ERL_INET_ETC_DIR", ?DEFAULT_ETC),
151    load_resolv(filename:join(Etc, ?DEFAULT_RESOLV), resolv),
152    case Type of
153	freebsd ->	    %% we may have to check version (2.2.2)
154	    load_resolv(filename:join(Etc,"host.conf"), host_conf_freebsd);
155	'bsd/os' ->
156	    load_resolv(filename:join(Etc,"irs.conf"), host_conf_bsdos);
157	sunos ->
158	    case os:version() of
159		{Major,_,_} when Major >= 5 ->
160		    load_resolv(filename:join(Etc,"nsswitch.conf"),
161				nsswitch_conf);
162		_ ->
163		    ok
164	    end;
165	netbsd ->
166	    case os:version() of
167		{Major,Minor,_} when Major >= 1, Minor >= 4 ->
168		    load_resolv(filename:join(Etc,"nsswitch.conf"),
169				nsswitch_conf);
170		_ ->
171		    ok
172	    end;
173	linux ->
174	    case load_resolv(filename:join(Etc,"host.conf"),
175			     host_conf_linux) of
176		ok ->
177		    ok;
178		_ ->
179		    load_resolv(filename:join(Etc,"nsswitch.conf"),
180				nsswitch_conf)
181	    end;
182	_ ->
183	    ok
184    end,
185    inet_db:set_lookup([native]);
186
187do_load_resolv({win32,Type}, longnames) ->
188    win32_load_from_registry(Type),
189    inet_db:set_lookup([native]);
190
191do_load_resolv(_, _) ->
192    inet_db:set_lookup([native]).
193
194add_dns_lookup(L) ->
195    case lists:member(dns,L) of
196	true -> ok;
197	_ ->
198	    case application:get_env(kernel,inet_dns_when_nis) of
199		{ok,true} ->
200		    add_dns_lookup(L,[]);
201		_ ->
202		    ok
203	    end
204    end.
205
206add_dns_lookup([yp|T],Acc) ->
207    add_dns_lookup(T,[yp,dns|Acc]);
208add_dns_lookup([H|T],Acc) ->
209    add_dns_lookup(T,[H|Acc]);
210add_dns_lookup([],Acc) ->
211    inet_db:set_lookup(reverse(Acc)).
212
213%%
214%% Set the hostname (SHORT)
215%% If hostname is long use the suffix as default domain
216%% and initalize the search option with the parts of domain
217%%
218set_hostname() ->
219    case inet_udp:open(0,[]) of
220	{ok,U} ->
221	    Res = inet:gethostname(U),
222	    inet_udp:close(U),
223	    set_hostname(Res);
224	_ ->
225	    set_hostname({ok, []})
226    end.
227
228set_hostname({ok,Name}) when length(Name) > 0 ->
229    {Host, Domain} = lists:splitwith(fun($.) -> false;
230					(_)  -> true
231				     end, Name),
232    inet_db:set_hostname(Host),
233    set_search_dom(Domain);
234set_hostname({ok,[]}) ->
235    inet_db:set_hostname("nohost"),
236    set_search_dom("nodomain").
237
238set_search_dom([$.|Domain]) ->
239    %% leading . not removed by dropwhile above.
240    inet_db:set_domain(Domain),
241    inet_db:ins_search(Domain),
242    ok;
243set_search_dom([]) ->
244    ok;
245set_search_dom(Domain) ->
246    inet_db:set_domain(Domain),
247    inet_db:ins_search(Domain),
248    ok.
249
250%%
251%% Load resolver data
252%%
253load_resolv(File, Func) ->
254    case get_file(File) of
255	{ok,Bin} ->
256            case inet_parse:Func(File, {chars, Bin}) of
257		{ok, Ls} ->
258		    inet_db:add_rc_list(Ls);
259		{error, Reason} ->
260		    error("parse error in file ~ts: ~p", [File, Reason])
261	    end;
262	Error ->
263	    warning("file not found ~ts: ~p~n", [File, Error])
264    end.
265
266%%
267%% Load a UNIX hosts file
268%%
269load_hosts(File,Os) ->
270    case get_file(File) of
271	{ok,Bin} ->
272	    case inet_parse:hosts(File,{chars,Bin}) of
273		{ok, Ls} ->
274		    foreach(
275		      fun({IP, Name, Aliases}) ->
276			      inet_db:add_host(IP, [Name|Aliases]) end,
277		      Ls);
278		{error, Reason} ->
279		    error("parse error in file ~ts: ~p", [File, Reason])
280	    end;
281	Error ->
282	    case Os of
283		unix ->
284		    error("file not found ~ts: ~p~n", [File, Error]);
285		_ ->
286		    %% for windows or nt the hosts file is not always there
287		    %% and we don't require it
288		    ok
289	    end
290    end.
291
292%%
293%% Load resolver data from Windows registry
294%%
295win32_load_from_registry(Type) ->
296    %% The TcpReg variable enables us to run tests with other registry configurations than
297    %% the normal ones
298    TcpReg = os:getenv("ERL_INET_ETC_DIR", ""),
299    {ok, Reg} = win32reg:open([read]),
300    {TcpIp,HFileKey} =
301    case Type of
302	nt ->
303	    case TcpReg of
304		[] ->
305		    {"\\hklm\\system\\CurrentControlSet\\Services\\TcpIp\\Parameters",
306		     "DataBasePath" };
307		Other ->
308		    {Other,"DataBasePath"}
309	    end;
310	windows ->
311	    case TcpReg of
312		[] ->
313		    {"\\hklm\\system\\CurrentControlSet\\Services\\VxD\\MSTCP",
314		     "LMHostFile" };
315		Other ->
316		    {Other,"LMHostFile"}
317	    end
318    end,
319    Result =
320	case win32reg:change_key(Reg,TcpIp) of
321	    ok ->
322		win32_load1(Reg,Type,HFileKey);
323	    {error, _Reason} ->
324		error("Failed to locate TCP/IP parameters (is TCP/IP installed)?",
325		      [])
326	end,
327    win32reg:close(Reg),
328    Result.
329
330win32_load1(Reg,Type,HFileKey) ->
331    Names = [HFileKey, "Domain", "DhcpDomain",
332	     "EnableDNS", "NameServer", "SearchList"],
333    case win32_get_strings(Reg, Names) of
334	[DBPath0, Domain, DhcpDomain,
335	 _EnableDNS, NameServers0, Search] ->
336	    inet_db:set_domain(
337	      case Domain of "" -> DhcpDomain; _ -> Domain end),
338	    NameServers = win32_split_line(NameServers0,Type),
339	    AddNs = fun(Addr) ->
340			    case inet_parse:address(Addr) of
341				{ok, Address} ->
342				    inet_db:add_ns(Address);
343				{error, _} ->
344				    error("Bad TCP/IP address in registry", [])
345			    end
346		    end,
347	    foreach(AddNs, NameServers),
348	    Searches0 = win32_split_line(Search,Type),
349	    Searches = case member(Domain, Searches0) of
350			   true  -> Searches0;
351			   false -> [Domain|Searches0]
352		       end,
353	    foreach(fun(D) -> inet_db:add_search(D) end, Searches),
354	    if Type =:= nt ->
355		    DBPath = win32reg:expand(DBPath0),
356		    load_hosts(filename:join(DBPath, "hosts"),nt);
357		Type =:= windows ->
358		    load_hosts(filename:join(DBPath0,""),windows)
359	    end,
360%% Maybe activate this later as an optimization
361%% For now we use native always as the SAFE way
362%%	    case NameServers of
363%%		[] -> inet_db:set_lookup([native, file]);
364%%		_  -> inet_db:set_lookup([dns, file, native])
365%%	    end;
366	    true;
367	{error, _Reason} ->
368	    error("Failed to read TCP/IP parameters from registry", [])
369    end.
370
371win32_split_line(Line,nt) -> inet_parse:split_line(Line);
372win32_split_line(Line,windows) -> string:lexemes(Line, ",").
373
374win32_get_strings(Reg, Names) ->
375    win32_get_strings(Reg, Names, []).
376
377win32_get_strings(Reg, [Name|Rest], Result) ->
378    case win32reg:value(Reg, Name) of
379	{ok, Value} when is_list(Value) ->
380	    win32_get_strings(Reg, Rest, [Value|Result]);
381	{ok, _NotString} ->
382	    {error, not_string};
383	{error, _Reason} ->
384	    win32_get_strings(Reg, Rest, [""|Result])
385    end;
386win32_get_strings(_, [], Result) ->
387    lists:reverse(Result).
388
389read_rc() ->
390    {RcFile,CfgList} = read_inetrc(),
391    case extract_cfg_files(CfgList, [], []) of
392	{CfgFiles,CfgList1} ->
393	    {RcFile,CfgFiles,CfgList1};
394	error ->
395	    {error,[],[]}
396    end.
397
398
399
400extract_cfg_files([E = {file,Type,_File} | Es], CfgFiles, CfgList) ->
401    extract_cfg_files1(Type, E, Es, CfgFiles, CfgList);
402extract_cfg_files([E = {registry,Type} | Es], CfgFiles, CfgList) ->
403    extract_cfg_files1(Type, E, Es, CfgFiles, CfgList);
404extract_cfg_files([E | Es], CfgFiles, CfgList) ->
405    extract_cfg_files(Es, CfgFiles, [E | CfgList]);
406extract_cfg_files([], CfgFiles, CfgList) ->
407    {reverse(CfgFiles),reverse(CfgList)}.
408
409extract_cfg_files1(Type, E, Es, CfgFiles, CfgList) ->
410    case valid_type(Type) of
411	true ->
412	    extract_cfg_files(Es, [E | CfgFiles], CfgList);
413	false ->
414	    error("invalid config value ~w in inetrc~n", [Type]),
415	    error
416    end.
417
418valid_type(resolv) ->            true;
419valid_type(host_conf_freebsd) -> true;
420valid_type(host_conf_bsdos) ->   true;
421valid_type(host_conf_linux) ->   true;
422valid_type(nsswitch_conf) ->     true;
423valid_type(hosts) ->             true;
424valid_type(win32) ->             true;
425valid_type(_) ->                 false.
426
427read_inetrc() ->
428   case application:get_env(inetrc) of
429       {ok,File} ->
430	   try_get_rc(File);
431       _ ->
432	   case os:getenv("ERL_INETRC") of
433	       false ->
434		   {nofile,[]};
435	       File ->
436		   try_get_rc(File)
437	   end
438   end.
439
440try_get_rc(File) ->
441    case get_rc(File) of
442	error -> {nofile,[]};
443	Ls ->    {File,Ls}
444    end.
445
446get_rc(File) ->
447    case get_file(File) of
448	{ok,Bin} ->
449	    case parse_inetrc(Bin) of
450		{ok,Ls} ->
451		    Ls;
452		_Error ->
453		    error("parse error in ~ts~n", [File]),
454		    error
455	    end;
456	_Error ->
457	    error("file ~ts not found~n", [File]),
458	    error
459    end.
460
461%% XXX Check if we really need to prim load the stuff
462get_file(File) ->
463    case erl_prim_loader:get_file(File) of
464	{ok,Bin,_} -> {ok,Bin};
465	Error -> Error
466    end.
467
468error(Fmt, Args) ->
469    error_logger:error_msg("inet_config: " ++ Fmt, Args).
470
471warning(Fmt, Args) ->
472    case application:get_env(kernel,inet_warnings) of
473	%{ok,silent} -> ok;
474	{ok,on} ->
475	    error_logger:info_msg("inet_config:" ++ Fmt, Args);
476	_ ->
477	    ok
478    end.
479
480%%
481%% Parse inetrc, i.e. make a binary of a term list.
482%% The extra newline is to let the user ignore the whitespace !!!
483%% Ignore leading whitespace before a token (due to bug in erl_scan) !
484%%
485parse_inetrc(Bin) ->
486    case file_binary_to_list(Bin) of
487        {ok, String} ->
488            parse_inetrc(String ++ "\n", 1, []);
489        error ->
490            {error, 'bad_encoding'}
491    end.
492
493parse_inetrc_skip_line([], _Line, Ack) ->
494    {ok, reverse(Ack)};
495parse_inetrc_skip_line([$\n|Str], Line, Ack) ->
496    parse_inetrc(Str, Line+1, Ack);
497parse_inetrc_skip_line([_|Str], Line, Ack) ->
498    parse_inetrc_skip_line(Str, Line, Ack).
499
500parse_inetrc([$%|Str], Line, Ack) ->
501    parse_inetrc_skip_line(Str, Line, Ack);
502parse_inetrc([$\s|Str], Line, Ack) ->
503    parse_inetrc(Str, Line, Ack);
504parse_inetrc([$\n |Str], Line, Ack) ->
505    parse_inetrc(Str, Line+1, Ack);
506parse_inetrc([$\t|Str], Line, Ack) ->
507    parse_inetrc(Str, Line, Ack);
508parse_inetrc([], _, Ack) ->
509    {ok, reverse(Ack)};
510
511
512%% The clauses above are here due to a bug in erl_scan (OTP-1449).
513
514parse_inetrc(Str, Line, Ack) ->
515    case erl_scan:tokens([], Str, Line) of
516	{done, {ok, Tokens, EndLine}, MoreChars} ->
517	    case erl_parse:parse_term(Tokens) of
518		{ok, Term} ->
519		    parse_inetrc(MoreChars, EndLine, [Term|Ack]);
520		Error ->
521		    {error, {'parse_inetrc', Error}}
522	    end;
523	{done, {eof, _}, _} ->
524	    {ok, reverse(Ack)};
525	{done, Error, _} ->
526	    {error, {'scan_inetrc', Error}};
527	{more, _} -> %% Bug in erl_scan !!
528	    {error, {'scan_inetrc', {eof, Line}}}
529    end.
530
531file_binary_to_list(Bin) ->
532    Enc = case epp:read_encoding_from_binary(Bin) of
533              none -> epp:default_encoding();
534              Encoding -> Encoding
535          end,
536    case catch unicode:characters_to_list(Bin, Enc) of
537        String when is_list(String) ->
538            {ok, String};
539        _ ->
540            error
541    end.
542
543