1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1997-2017. 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_parse).
21
22%% Parser for all kinds of ineternet configuration files
23
24%% Avoid warning for local function error/2 clashing with autoimported BIF.
25-compile({no_auto_import,[error/2]}).
26-export([hosts/1, hosts/2]).
27-export([protocols/1, protocols/2]).
28-export([netmasks/1, netmasks/2]).
29-export([networks/1, networks/2]).
30-export([services/1, services/2]).
31-export([rpc/1, rpc/2]).
32-export([resolv/1, resolv/2]).
33-export([host_conf_linux/1, host_conf_linux/2]).
34-export([host_conf_freebsd/1, host_conf_freebsd/2]).
35-export([host_conf_bsdos/1, host_conf_bsdos/2]).
36-export([nsswitch_conf/1, nsswitch_conf/2]).
37
38-export([ipv4_address/1, ipv6_address/1]).
39-export([ipv4strict_address/1, ipv6strict_address/1]).
40-export([address/1, strict_address/1]).
41-export([visible_string/1, domain/1]).
42-export([ntoa/1, dots/1]).
43-export([split_line/1]).
44
45-import(lists, [reverse/1]).
46
47-include_lib("kernel/include/file.hrl").
48
49%% --------------------------------------------------------------------------
50%% Parse services internet style
51%% Syntax:
52%%      Name   Port/Protocol    [Aliases]  \n
53%%      # comment
54%% --------------------------------------------------------------------------
55
56services(File) ->
57    services(noname, File).
58
59services(Fname, File) ->
60    Fn = fun([Name, PortProto | Aliases]) ->
61		 {Proto,Port} = port_proto(PortProto, 0),
62		 {Name,Proto,Port,Aliases}
63	 end,
64    parse_file(Fname, File, Fn).
65
66%% --------------------------------------------------------------------------
67%% Parse rpc program names
68%% Syntax:
69%%      Name   Program  [Aliases]  \n |
70%%      # comment
71%% --------------------------------------------------------------------------
72
73rpc(File) ->
74    rpc(noname, File).
75
76rpc(Fname, File) ->
77    Fn = fun([Name,Program | Aliases]) ->
78		 Prog = list_to_integer(Program),
79		 {Name,Prog,Aliases}
80	 end,
81    parse_file(Fname, File, Fn).
82
83%% --------------------------------------------------------------------------
84%% Parse hosts file unix style
85%% Syntax:
86%%      IP Name [Aliases]  \n |
87%%      # comment
88%% --------------------------------------------------------------------------
89hosts(File) ->
90    hosts(noname,File).
91
92hosts(Fname,File) ->
93    Fn = fun([Address, Name | Aliases]) ->
94		 %% XXX Fix for link-local IPv6 addresses that specify
95		 %% interface with a %if suffix. These kind of
96		 %% addresses maybe need to be gracefully handled
97		 %% throughout inet* and inet_drv.
98		 case string:lexemes(Address, "%") of
99		     [Addr,_] ->
100			 {ok,_} = address(Addr),
101			 skip;
102		     _ ->
103			 {ok,IP} = address(Address),
104			 {IP, Name, Aliases}
105		 end
106	 end,
107    parse_file(Fname, File, Fn).
108
109%% --------------------------------------------------------------------------
110%% Parse resolv file unix style
111%% Syntax:
112%%      domain Domain \n
113%%      nameserver IP \n
114%%      search Dom1 Dom2 ... \n
115%%      lookup Method1 Method2 Method3 \n
116%%      # comment
117%% --------------------------------------------------------------------------
118
119resolv(File) ->
120    resolv(noname,File).
121
122resolv(Fname, File) ->
123    Fn = fun(["domain", Domain]) ->
124		 {domain, Domain};
125	    (["nameserver", Address]) ->
126		 {ok,IP} = address(Address),
127		 {nameserver,IP};
128	    (["search" | List]) ->
129		 {search, List};
130	    (["lookup" | Types]) ->
131		 {lookup, Types};
132	    (_) ->
133		 skip  %% there are too many local options, we MUST skip
134	 end,
135    parse_file(Fname, File, Fn).
136
137%% --------------------------------------------------------------------------
138%%
139%% Parse Linux host.conf file
140%% find "order" only.
141%%
142%% --------------------------------------------------------------------------
143host_conf_linux(File) ->
144    host_conf_linux(noname,File).
145
146host_conf_linux(Fname, File) ->
147    Fn = fun(["order" | Order]) ->
148		 %% XXX remove ',' between entries
149		 {lookup, split_comma(Order)};
150	    (_) ->
151		 skip
152	 end,
153    parse_file(Fname, File, Fn).
154
155%% --------------------------------------------------------------------------
156%%
157%% Parse Freebsd/Netbsd host.conf file
158%% find "order" only.
159%%
160%% --------------------------------------------------------------------------
161host_conf_freebsd(File) ->
162    host_conf_freebsd(noname,File).
163
164host_conf_freebsd(Fname, File) ->
165    Fn = fun([Type]) -> Type end,
166    case parse_file(Fname, File, Fn) of
167	{ok, Ls} -> {ok, [{lookup, Ls}]};
168	Error -> Error
169    end.
170
171
172
173%% --------------------------------------------------------------------------
174%%
175%% Parse BSD/OS irs.conf file
176%% find "hosts" only and ignore options.
177%%
178%% Syntax:
179%%      Map AccessMethod [,AccessMethod] [continue|merge [,merge|,continue]] \n
180%%      # comment
181
182%% --------------------------------------------------------------------------
183host_conf_bsdos(File) ->
184    host_conf_bsdos(noname,File).
185
186host_conf_bsdos(Fname, File) ->
187    Fn = fun(["hosts" | List]) ->
188		 delete_options(split_comma(List));
189	    (_) ->
190		 skip
191	 end,
192    case parse_file(Fname, File, Fn) of
193	{ok, Ls} ->
194	    {ok, [{lookup, lists:append(Ls)}]};
195	Error -> Error
196    end.
197
198delete_options(["continue"|T]) ->
199    delete_options(T);
200delete_options(["merge"|T]) ->
201    delete_options(T);
202delete_options([H|T]) ->
203    [H|delete_options(T)];
204delete_options([]) ->
205    [].
206
207
208%% --------------------------------------------------------------------------
209%%
210%% Parse Solaris nsswitch.conf
211%% find "hosts:" only
212%%
213%% --------------------------------------------------------------------------
214
215nsswitch_conf(File) ->
216    nsswitch_conf(noname,File).
217
218nsswitch_conf(Fname, File) ->
219    Fn = fun(["hosts:" | Types]) ->
220		 {lookup, Types};
221	    (_) -> skip
222	 end,
223    parse_file(Fname, File, Fn).
224
225%% --------------------------------------------------------------------------
226%% Parse protocol file unix style
227%% Syntax:
228%%      name protocol number name \n
229%%      # comment
230%% --------------------------------------------------------------------------
231
232protocols(File) ->
233    protocols(noname,File).
234
235protocols(Fname, File) ->
236    Fn = fun([Name, Number, DName]) ->
237		 {list_to_atom(Name), list_to_integer(Number), DName}
238	 end,
239    parse_file(Fname, File, Fn).
240
241%% --------------------------------------------------------------------------
242%% Parse netmasks file unix style
243%% Syntax:
244%%      Network  Subnetmask
245%%      # comment
246%% --------------------------------------------------------------------------
247
248netmasks(File) ->
249    netmasks(noname, File).
250
251netmasks(Fname, File) ->
252    Fn = fun([Net, Subnetmask]) ->
253		 {ok, NetIP} = address(Net),
254		 {ok, Mask} =  address(Subnetmask),
255		 {NetIP, Mask}
256	 end,
257    parse_file(Fname, File, Fn).
258
259%% --------------------------------------------------------------------------
260%% Parse networks file unix style
261%% Syntax:
262%%      network-name  network-number aliases ...
263%%      # comment
264%% --------------------------------------------------------------------------
265
266networks(File) ->
267    networks(noname, File).
268
269networks(Fname, File) ->
270    Fn = fun([NetName, NetNumber]) ->
271		 Number = list_to_integer(NetNumber),
272		 {NetName, Number}
273	 end,
274    parse_file(Fname, File, Fn).
275
276%% --------------------------------------------------------------------------
277%%
278%% Simple Line by Line parser
279%%
280%% --------------------------------------------------------------------------
281
282parse_file(Fname, {fd,Fd}, Fn) ->
283    parse_fd(Fname,Fd, 1, Fn, []);
284parse_file(Fname, {chars,Cs}, Fn) when is_list(Cs) ->
285    parse_cs(Fname, Cs, 1, Fn, []);
286parse_file(Fname, {chars,Cs}, Fn) when is_binary(Cs) ->
287    parse_cs(Fname, binary_to_list(Cs), 1, Fn, []);
288parse_file(_, File, Fn) ->
289    case file:open(File, [read]) of
290	{ok, Fd} ->
291	    Result = parse_fd(File,Fd, 1, Fn, []),
292	    _ = file:close(Fd),
293	    Result;
294	Error -> Error
295    end.
296
297parse_fd(Fname,Fd, Line, Fun, Ls) ->
298    case read_line(Fd) of
299	eof -> {ok, reverse(Ls)};
300	Cs ->
301	    case split_line(Cs) of
302		[] -> parse_fd(Fname, Fd, Line+1, Fun, Ls);
303		Toks ->
304		    case catch Fun(Toks) of
305			{'EXIT',_} ->
306			    error("~p:~p: erroneous line, SKIPPED~n",[Fname,Line]),
307			    parse_fd(Fname, Fd,Line+1,Fun,Ls);
308			{warning,Wlist,Val} ->
309			    warning("~p:~p: warning! strange domain name(s) ~p ~n",[Fname,Line,Wlist]),
310			    parse_fd(Fname, Fd,Line+1,Fun,[Val|Ls]);
311
312			skip ->
313			    parse_fd(Fname, Fd, Line+1, Fun, Ls);
314			Val -> parse_fd(Fname, Fd, Line+1, Fun, [Val|Ls])
315		    end
316	    end
317    end.
318
319parse_cs(Fname, Chars, Line, Fun, Ls) ->
320    case get_line(Chars) of
321	eof -> {ok, reverse(Ls)};
322	{Cs,Chars1} ->
323	    case split_line(Cs) of
324		[] -> parse_cs(Fname, Chars1, Line+1, Fun, Ls);
325		Toks ->
326		    case catch Fun(Toks) of
327			{'EXIT',_} ->
328			    error("~p:~p: erroneous line, SKIPPED~n",[Fname,Line]),
329 			    parse_cs(Fname, Chars1, Line+1, Fun, Ls);
330			{warning,Wlist,Val} ->
331			    warning("~p:~p: warning! strange domain name(s) ~p ~n",[Fname,Line,Wlist]),
332			    parse_cs(Fname, Chars1, Line+1, Fun, [Val|Ls]);
333
334			skip -> parse_cs(Fname, Chars1, Line+1, Fun, Ls);
335			Val -> parse_cs(Fname, Chars1, Line+1, Fun, [Val|Ls])
336		    end
337	    end
338    end.
339
340get_line([]) -> eof;
341get_line(Chars) -> get_line(Chars,[]).
342
343get_line([], Acc) -> {reverse(Acc), []};
344get_line([$\r, $\n | Cs], Acc) -> {reverse([$\n|Acc]), Cs};
345get_line([$\n | Cs], Acc) -> {reverse([$\n|Acc]), Cs};
346get_line([C | Cs], Acc) -> get_line(Cs, [C|Acc]).
347
348%%
349%% Read a line
350%%
351read_line(Fd) when is_pid(Fd) -> io:get_line(Fd, '');
352read_line(Fd = #file_descriptor{}) ->
353    collect_line(Fd, []).
354
355collect_line(Fd, Cs) ->
356    case file:read(Fd, 80) of
357	{ok, Line} when is_binary(Line) ->
358	    collect_line(Fd, byte_size(Line), binary_to_list(Line), Cs);
359	{ok, Line} ->
360	    collect_line(Fd, length(Line), Line, Cs);
361	eof when Cs =:= [] ->
362	    eof;
363	eof -> reverse(Cs)
364    end.
365
366collect_line(Fd, N, [$\r, $\n|_], Cs) ->
367    {ok, _} = file:position(Fd, {cur,-(N-2)}),
368    reverse([$\n|Cs]);
369collect_line(Fd, N, [$\n|_], Cs) ->
370    {ok, _} = file:position(Fd, {cur,-(N-1)}),
371    reverse([$\n|Cs]);
372collect_line(Fd, _, [], Cs) ->
373    collect_line(Fd, Cs);
374collect_line(Fd, N, [X|Xs], Cs) ->
375    collect_line(Fd, N-1, Xs, [X|Cs]).
376
377
378%% split Port/Proto -> {Port, Proto}
379port_proto([X|Xs], N) when X >= $0, X =< $9 ->
380    port_proto(Xs, N*10 + (X - $0));
381port_proto([$/ | Proto], Port) when Port =/= 0 ->
382    {list_to_atom(Proto), Port}.
383
384%%
385%% Check if a String is a string with visible characters #21..#7E
386%% visible_string(String) -> Bool
387%%
388visible_string([H|T]) ->
389    is_vis1([H|T]);
390visible_string(_) ->
391    false.
392
393is_vis1([C | Cs]) when C >= 16#21, C =< 16#7e -> is_vis1(Cs);
394is_vis1([]) -> true;
395is_vis1(_) -> false.
396
397%%
398%% Check if a String is a domain name according to RFC XXX.
399%% domain(String) -> Bool
400%%
401domain([H|T]) ->
402    is_dom1([H|T]);
403domain(_) ->
404    false.
405
406is_dom1([C | Cs]) when C >= $a, C =< $z -> is_dom_ldh(Cs);
407is_dom1([C | Cs]) when C >= $A, C =< $Z -> is_dom_ldh(Cs);
408is_dom1([C | Cs]) when C >= $0, C =< $9 ->
409    case is_dom_ldh(Cs) of
410	true  -> is_dom2(string:lexemes([C | Cs],"."));
411	false -> false
412    end;
413is_dom1(_) -> false.
414
415is_dom_ldh([C | Cs]) when C >= $a, C =< $z -> is_dom_ldh(Cs);
416is_dom_ldh([C | Cs]) when C >= $A, C =< $Z -> is_dom_ldh(Cs);
417is_dom_ldh([C | Cs]) when C >= $0, C =< $9 -> is_dom_ldh(Cs);
418is_dom_ldh([$-,$. | _]) -> false;
419is_dom_ldh([$_,$. | _]) -> false;
420is_dom_ldh([$_ | Cs]) -> is_dom_ldh(Cs);
421is_dom_ldh([$- | Cs]) -> is_dom_ldh(Cs);
422is_dom_ldh([$. | Cs]) -> is_dom1(Cs);
423is_dom_ldh([]) -> true;
424is_dom_ldh(_) -> false.
425
426%%% Check that we don't get a IP-address as a domain name.
427
428-define(L2I(L), (catch list_to_integer(L))).
429
430is_dom2([A,B,C,D]) ->
431    case ?L2I(D) of
432	Di when is_integer(Di) ->
433	    case {?L2I(A),?L2I(B),?L2I(C)} of
434		{Ai,Bi,Ci} when is_integer(Ai),
435                                is_integer(Bi),
436                                is_integer(Ci) -> false;
437		_ -> true
438	    end;
439	_ -> true
440    end;
441is_dom2(_) ->
442    true.
443
444
445
446%%
447%% Parse ipv4 address or ipv6 address
448%% Return {ok, Address} | {error, Reason}
449%%
450address(Cs) when is_list(Cs) ->
451    case ipv4_address(Cs) of
452	{ok,IP} ->
453	    {ok,IP};
454	_ ->
455	    ipv6strict_address(Cs)
456    end;
457address(_) ->
458    {error, einval}.
459
460%%Parse ipv4 strict address or ipv6 strict address
461strict_address(Cs) when is_list(Cs) ->
462    case ipv4strict_address(Cs) of
463	{ok,IP} ->
464	    {ok,IP};
465	_ ->
466	    ipv6strict_address(Cs)
467    end;
468strict_address(_) ->
469    {error, einval}.
470
471%%
472%% Parse IPv4 address:
473%%    d1.d2.d3.d4
474%%    d1.d2.d4
475%%    d1.d4
476%%    d4
477%% Any d may be octal, hexadecimal or decimal by C language standards.
478%% d4 fills all LSB bytes. This is legacy behaviour from Solaris
479%% and FreeBSD. And partly Linux that behave the same except
480%% it does not accept hexadecimal.
481%%
482%% Return {ok, IP} | {error, einval}
483%%
484ipv4_address(Cs) ->
485    try ipv4_addr(Cs) of
486	Addr ->
487	    {ok,Addr}
488    catch
489	error:badarg ->
490	    {error,einval}
491    end.
492
493ipv4_addr(Cs) ->
494    case ipv4_addr(Cs, []) of
495	[D] when D < (1 bsl 32) ->
496	    <<D1,D2,D3,D4>> = <<D:32>>,
497	    {D1,D2,D3,D4};
498	[D,D1] when D < (1 bsl 24), D1 < 256 ->
499	    <<D2,D3,D4>> = <<D:24>>,
500	    {D1,D2,D3,D4};
501	[D,D2,D1] when D < (1 bsl 16), (D2 bor D1) < 256 ->
502	    <<D3,D4>> = <<D:16>>,
503	    {D1,D2,D3,D4};
504	[D4,D3,D2,D1] when (D4 bor D3 bor D2 bor D1) < 256 ->
505	    {D1,D2,D3,D4};
506	_ ->
507	    erlang:error(badarg)
508    end.
509
510ipv4_addr([_|_], [_,_,_,_]) ->
511    %% Early bailout for extra characters
512    erlang:error(badarg);
513ipv4_addr("0x"++Cs, Ds) ->
514    ipv4_addr(strip0(Cs), Ds, [], 16, 8);
515ipv4_addr("0X"++Cs, Ds) ->
516    ipv4_addr(strip0(Cs), Ds, [], 16, 8);
517ipv4_addr("0"++Cs, Ds) ->
518    ipv4_addr(strip0(Cs), Ds, [$0], 8, 11);
519ipv4_addr(Cs, Ds) when is_list(Cs) ->
520    ipv4_addr(Cs, Ds, [], 10, 10).
521
522ipv4_addr(Cs0, Ds, Rs, Base, N) ->
523    case ipv4_field(Cs0, N, Rs, Base) of
524	{D,""} ->
525	    [D|Ds];
526	{D,[$.|[_|_]=Cs]} ->
527	    ipv4_addr(Cs, [D|Ds]);
528	{_,_} ->
529	    erlang:error(badarg)
530    end.
531
532strip0("0"++Cs) ->
533    strip0(Cs);
534strip0(Cs) when is_list(Cs) ->
535    Cs.
536
537
538%%
539%% Parse IPv4 strict dotted decimal address, no leading zeros:
540%%    d1.d2.d3.d4
541%%
542%% Return {ok, IP} | {error, einval}
543%%
544ipv4strict_address(Cs) ->
545    try ipv4strict_addr(Cs) of
546	Addr ->
547	    {ok,Addr}
548    catch
549	error:badarg ->
550	    {error,einval}
551    end.
552
553ipv4strict_addr(Cs) ->
554    case ipv4strict_addr(Cs, []) of
555	[D4,D3,D2,D1] when (D4 bor D3 bor D2 bor D1) < 256 ->
556	    {D1,D2,D3,D4};
557	_ ->
558	    erlang:error(badarg)
559    end.
560
561ipv4strict_addr([_|_], [_,_,_,_]) ->
562    %% Early bailout for extra characters
563    erlang:error(badarg);
564ipv4strict_addr("0", Ds) ->
565    [0|Ds];
566ipv4strict_addr("0."++Cs, Ds) ->
567    ipv4strict_addr(Cs, [0|Ds]);
568ipv4strict_addr(Cs0, Ds) when is_list(Cs0) ->
569    case ipv4_field(Cs0, 3, [], 10) of
570	{D,""} ->
571	    [D|Ds];
572	{D,[$.|[_|_]=Cs]} ->
573	    ipv4strict_addr(Cs, [D|Ds]);
574	{_,_} ->
575	    erlang:error(badarg)
576    end.
577
578
579
580ipv4_field("", _, Rs, Base) ->
581    {ipv4_field(Rs, Base),""};
582ipv4_field("."++_=Cs, _, Rs, Base) ->
583    {ipv4_field(Rs, Base),Cs};
584ipv4_field("0"++_, _, [], _) ->
585    erlang:error(badarg);
586ipv4_field([C|Cs], N, Rs, Base) when N > 0 ->
587    ipv4_field(Cs, N-1, [C|Rs], Base);
588ipv4_field(Cs, _, _, _) when is_list(Cs) ->
589    erlang:error(badarg).
590
591ipv4_field(Rs, Base) ->
592    V = erlang:list_to_integer(lists:reverse(Rs), Base),
593    if  V < 0 ->
594	    erlang:error(badarg);
595	true ->
596	    V
597    end.
598
599
600
601%%
602%% Forgiving IPv6 address
603%%
604%% Accepts IPv4 address and returns it as a IPv4 compatible IPv6 address
605%%
606ipv6_address(Cs) ->
607    case ipv4_address(Cs) of
608	{ok,{D1,D2,D3,D4}} ->
609	    {ok,{0,0,0,0,0,16#ffff,(D1 bsl 8) bor D2,(D3 bsl 8) bor D4}};
610	_ ->
611	    ipv6strict_address(Cs)
612    end.
613
614%%
615%% Parse IPv6 address according to RFC 4291:
616%%     x1:x2:x3:x4:x5:x6:x7:x8
617%%     x1:x2::x7:x8
618%%     ::x7:x8
619%%     x1:x2::
620%%     ::
621%%     x1:x2:x3:x4:x5:x6:d7a.d7b.d8a.d8b
622%%     x1:x2::x5:x6:d7a.d7b.d8a.d8b
623%%     ::x5:x6:d7a.d7b.d8a.d8b
624%%     x1:x2::d7a.d7b.d8a.d8b
625%%     ::d7a.d7b.d8a.d8b
626%%     etc
627%%
628%% Return {ok, IP} | {error, einval}
629%%
630ipv6strict_address(Cs) ->
631    try ipv6_addr(Cs) of
632	Addr ->
633	    {ok,Addr}
634    catch
635	error:badarg ->
636	    {error,einval}
637    end.
638
639ipv6_addr("::") ->
640    ipv6_addr_done([], [], 0);
641ipv6_addr("::"++Cs) ->
642    ipv6_addr(hex(Cs), [], [], 0);
643ipv6_addr(Cs) ->
644    ipv6_addr(hex(Cs), [], 0).
645
646%% Before "::"
647ipv6_addr({Cs0,"%"++Cs1}, A, N) when N == 7 ->
648    ipv6_addr_scope(Cs1, [hex_to_int(Cs0)|A], [], N+1, []);
649ipv6_addr({Cs0,[]}, A, N) when N == 7 ->
650    ipv6_addr_done([hex_to_int(Cs0)|A]);
651ipv6_addr({Cs0,"::%"++Cs1}, A, N) when N =< 6 ->
652    ipv6_addr_scope(Cs1, [hex_to_int(Cs0)|A], [], N+1, []);
653ipv6_addr({Cs0,"::"}, A, N) when N =< 6 ->
654    ipv6_addr_done([hex_to_int(Cs0)|A], [], N+1);
655ipv6_addr({Cs0,"::"++Cs1}, A, N) when N =< 5 ->
656    ipv6_addr(hex(Cs1), [hex_to_int(Cs0)|A], [], N+1);
657ipv6_addr({Cs0,":"++Cs1}, A, N) when N =< 6 ->
658    ipv6_addr(hex(Cs1), [hex_to_int(Cs0)|A], N+1);
659ipv6_addr({Cs0,"."++_=Cs1}, A, N) when N == 6 ->
660    ipv6_addr_done(A, [], N, ipv4strict_addr(Cs0++Cs1));
661ipv6_addr(_, _, _) ->
662    erlang:error(badarg).
663
664%% After "::"
665ipv6_addr({Cs0,"%"++Cs1}, A, B, N) when N =< 6 ->
666    ipv6_addr_scope(Cs1, A, [hex_to_int(Cs0)|B], N+1, []);
667ipv6_addr({Cs0,[]}, A, B, N) when N =< 6 ->
668    ipv6_addr_done(A, [hex_to_int(Cs0)|B], N+1);
669ipv6_addr({Cs0,":"++Cs1}, A, B, N) when N =< 5 ->
670    ipv6_addr(hex(Cs1), A, [hex_to_int(Cs0)|B], N+1);
671ipv6_addr({Cs0,"."++_=Cs1}, A, B, N) when N =< 5 ->
672    ipv6_addr_done(A, B, N, ipv4strict_addr(Cs0++Cs1));
673ipv6_addr(_, _, _, _) ->
674    erlang:error(badarg).
675
676%% After "%"
677ipv6_addr_scope([], Ar, Br, N, Sr) ->
678    ScopeId =
679        case lists:reverse(Sr) of
680            %% Empty scope id
681            "" -> 0;
682            %% Scope id starts with 0
683            "0"++S -> dec16(S);
684            _ -> 0
685        end,
686    %% Suggested formats for scope id parsing:
687    %%   "" -> "0"
688    %%   "0" -> Scope id 0
689    %%   "1" - "9", "10" - "99" -> "0"++S
690    %%   "0"++DecimalScopeId -> decimal scope id
691    %%   "25"++PercentEncoded -> Percent encoded interface name
692    %%   S -> Interface name (Unicode?)
693    %% Missing: translation from interface name into integer scope id.
694    %% XXX: scope id is actually 32 bit, but we only have room for
695    %% 16 bit in the second address word - ignore or fix (how)?
696    ipv6_addr_scope(ScopeId, Ar, Br, N);
697ipv6_addr_scope([C|Cs], Ar, Br, N, Sr) ->
698    ipv6_addr_scope(Cs, Ar, Br, N, [C|Sr]).
699%%
700ipv6_addr_scope(ScopeId, [P], Br, N)
701  when N =< 7, P =:= 16#fe80;
702       N =< 7, P =:= 16#ff02 ->
703    %% Optimized special case
704    ipv6_addr_done([ScopeId,P], Br, N+1);
705ipv6_addr_scope(ScopeId, Ar, Br, N) ->
706    case lists:reverse(Br++dup(8-N, 0, Ar)) of
707        [P,0|Xs] when P =:= 16#fe80; P =:= 16#ff02 ->
708            list_to_tuple([P,ScopeId|Xs]);
709        _ ->
710            erlang:error(badarg)
711    end.
712
713ipv6_addr_done(Ar, Br, N, {D1,D2,D3,D4}) ->
714    ipv6_addr_done(Ar, [((D3 bsl 8) bor D4),((D1 bsl 8) bor D2)|Br], N+2).
715
716ipv6_addr_done(Ar, Br, N) ->
717    ipv6_addr_done(Br++dup(8-N, 0, Ar)).
718
719ipv6_addr_done(Ar) ->
720    list_to_tuple(lists:reverse(Ar)).
721
722%% Collect 1-4 Hex digits
723hex(Cs) -> hex(Cs, [], 4).
724%%
725hex([C|Cs], R, N) when C >= $0, C =< $9, N > 0 ->
726    hex(Cs, [C|R], N-1);
727hex([C|Cs], R, N) when C >= $a, C =< $f, N > 0 ->
728    hex(Cs, [C|R], N-1);
729hex([C|Cs], R, N) when C >= $A, C =< $F, N > 0 ->
730    hex(Cs, [C|R], N-1);
731hex(Cs, [_|_]=R, _) when is_list(Cs) ->
732    {lists:reverse(R),Cs};
733hex(_, _, _) ->
734    erlang:error(badarg).
735
736%% Parse a reverse decimal integer string, empty is 0
737dec16(Cs) -> dec16(Cs, 0).
738%%
739dec16([], I) -> I;
740dec16([C|Cs], I) when C >= $0, C =< $9 ->
741    case 10*I + (C - $0) of
742        J when 16#ffff < J ->
743            erlang:error(badarg);
744        J ->
745            dec16(Cs, J)
746    end;
747dec16(_, _) -> erlang:error(badarg).
748
749%% Hex string to integer
750hex_to_int(Cs) -> erlang:list_to_integer(Cs, 16).
751
752%% Dup onto head of existing list
753dup(0, _, L) ->
754    L;
755dup(N, E, L) when is_integer(N), N >= 1 ->
756    dup(N-1, E, [E|L]).
757
758
759
760%% Convert IPv4 address to ascii
761%% Convert IPv6 / IPV4 address to ascii (plain format)
762ntoa({A,B,C,D}) when (A band B band C band D band (bnot 16#ff)) =:= 0 ->
763    integer_to_list(A) ++ "." ++ integer_to_list(B) ++ "." ++
764	integer_to_list(C) ++ "." ++ integer_to_list(D);
765%% ANY
766ntoa({0,0,0,0,0,0,0,0}) -> "::";
767%% LOOPBACK
768ntoa({0,0,0,0,0,0,0,1}) -> "::1";
769%% IPV4 ipv6 host address
770ntoa({0,0,0,0,0,0,A,B}) when (A band B band (bnot 16#ffff)) =:= 0 ->
771    "::" ++ dig_to_dec(A) ++ "." ++ dig_to_dec(B);
772%% IPV4 non ipv6 host address
773ntoa({0,0,0,0,0,16#ffff,A,B}) when (A band B band (bnot 16#ffff)) =:= 0 ->
774    "::ffff:" ++ dig_to_dec(A) ++ "." ++ dig_to_dec(B);
775ntoa({A,B,C,D,E,F,G,H})
776  when (A band B band C band D band E band F band G band H band
777            (bnot 16#ffff)) =:= 0 ->
778    if
779        A =:= 16#fe80, B =/= 0;
780        A =:= 16#ff02, B =/= 0 ->
781            %% Find longest sequence of zeros, at least 2,
782            %% to replace with "::"
783            ntoa([A,0,C,D,E,F,G,H], []) ++ "%0" ++ integer_to_list(B);
784        true ->
785            %% Find longest sequence of zeros, at least 2,
786            %% to replace with "::"
787            ntoa([A,B,C,D,E,F,G,H], [])
788    end;
789ntoa(_) ->
790    {error, einval}.
791
792%% Find first double zero
793ntoa([], R) ->
794    ntoa_done(R);
795ntoa([0,0|T], R) ->
796    ntoa(T, R, 2);
797ntoa([D|T], R) ->
798    ntoa(T, [D|R]).
799
800%% Count consecutive zeros
801ntoa([], R, _) ->
802    ntoa_done(R, []);
803ntoa([0|T], R, N) ->
804    ntoa(T, R, N+1);
805ntoa([D|T], R, N) ->
806    ntoa(T, R, N, [D]).
807
808%% Find alternate double zero
809ntoa([], R1, _N1, R2) ->
810    ntoa_done(R1, R2);
811ntoa([0,0|T], R1, N1, R2) ->
812    ntoa(T, R1, N1, R2, 2);
813ntoa([D|T], R1, N1, R2) ->
814    ntoa(T, R1, N1, [D|R2]).
815
816%% Count consecutive alternate zeros
817ntoa(T, R1, N1, R2, N2) when N2 > N1 ->
818    %% Alternate zero sequence is longer - use it instead
819    ntoa(T, R2++dup(N1, 0, R1), N2);
820ntoa([], R1, _N1, R2, N2) ->
821    ntoa_done(R1, dup(N2, 0, R2));
822ntoa([0|T], R1, N1, R2, N2) ->
823    ntoa(T, R1, N1, R2, N2+1);
824ntoa([D|T], R1, N1, R2, N2) ->
825    ntoa(T, R1, N1, [D|dup(N2, 0, R2)]).
826
827ntoa_done(R1, R2) ->
828    lists:append(
829      separate(":", lists:map(fun dig_to_hex/1, lists:reverse(R1)))++
830      ["::"|separate(":", lists:map(fun dig_to_hex/1, lists:reverse(R2)))]).
831
832ntoa_done(R) ->
833    lists:append(separate(":", lists:map(fun dig_to_hex/1, lists:reverse(R)))).
834
835separate(_E, []) ->
836    [];
837separate(E, [_|_]=L) ->
838    separate(E, L, []).
839
840separate(E, [H|[_|_]=T], R) ->
841    separate(E, T, [E,H|R]);
842separate(_E, [H], R) ->
843    lists:reverse(R, [H]).
844
845%% convert to A.B decimal form
846dig_to_dec(0) -> "0.0";
847dig_to_dec(X) ->
848    integer_to_list((X bsr 8) band 16#ff) ++ "." ++
849	integer_to_list(X band 16#ff).
850
851%% Convert a integer to hex string (lowercase)
852dig_to_hex(0) -> "0";
853dig_to_hex(X) when is_integer(X), 0 < X ->
854    dig_to_hex(X, "").
855%%
856dig_to_hex(0, Acc) -> Acc;
857dig_to_hex(X, Acc) ->
858    dig_to_hex(
859      X bsr 4,
860      [case X band 15 of
861           D when D < 10 -> D + $0;
862           D -> D - 10 + $a
863       end|Acc]).
864
865%%
866%% Count number of '.' in a name
867%% return {Number of non-terminating dots, has-terminating dot?}
868%%        {integer, bool}
869%%
870dots(Name) -> dots(Name, 0).
871
872dots([$.], N) -> {N, true};
873dots([$. | T], N) -> dots(T, N+1);
874dots([_C | T], N) -> dots(T, N);
875dots([], N) -> {N, false}.
876
877
878split_line(Line) ->
879    split_line(Line, []).
880
881split_line([$# | _], Tokens) ->  reverse(Tokens);
882split_line([$\s| L], Tokens) ->  split_line(L, Tokens);
883split_line([$\t | L], Tokens) -> split_line(L, Tokens);
884split_line([$\n | L], Tokens) -> split_line(L, Tokens);
885split_line([], Tokens) -> reverse(Tokens);
886split_line([C|Cs], Tokens) -> split_mid(Cs, [C], Tokens).
887
888split_mid([$# | _Cs], Acc, Tokens) -> split_end(Acc, Tokens);
889split_mid([$\s | Cs], Acc, Tokens) -> split_line(Cs, [reverse(Acc) | Tokens]);
890split_mid([$\t | Cs], Acc, Tokens) -> split_line(Cs, [reverse(Acc) | Tokens]);
891split_mid([$\r, $\n | Cs], Acc, Tokens) -> split_line(Cs, [reverse(Acc) | Tokens]);
892split_mid([$\n | Cs], Acc, Tokens) -> split_line(Cs, [reverse(Acc) | Tokens]);
893split_mid([], Acc, Tokens) -> split_end(Acc, Tokens);
894split_mid([C|Cs], Acc, Tokens) -> split_mid(Cs, [C|Acc], Tokens).
895
896split_end(Acc, Tokens) -> reverse([reverse(Acc) | Tokens]).
897
898
899%% Split a comma separated tokens. Because we already have split on
900%% spaces we may have the cases
901%%
902%%        ",foo"
903%%        "foo,"
904%%        "foo,bar..."
905
906split_comma([]) ->
907    [];
908split_comma([Token | Tokens]) ->
909    split_comma(Token, []) ++ split_comma(Tokens).
910
911split_comma([], Tokens) ->       reverse(Tokens);
912split_comma([$, | L], Tokens) -> split_comma(L, Tokens);
913split_comma([C|Cs], Tokens) ->   split_mid_comma(Cs, [C], Tokens).
914
915split_mid_comma([$, | Cs], Acc, Tokens) ->
916    split_comma(Cs, [reverse(Acc) | Tokens]);
917split_mid_comma([], Acc, Tokens) ->
918    split_end(Acc, Tokens);
919split_mid_comma([C|Cs], Acc, Tokens) ->
920    split_mid_comma(Cs, [C|Acc], Tokens).
921
922%%
923
924warning(Fmt, Args) ->
925    case application:get_env(kernel,inet_warnings) of
926	{ok,on} ->
927	    error_logger:info_msg("inet_parse:" ++ Fmt, Args);
928	_ ->
929	    ok
930    end.
931
932error(Fmt, Args) ->
933    error_logger:info_msg("inet_parse:" ++ Fmt, Args).
934
935