123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166(*
* Copyright (c) 2012-2014 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2005 Fraser Research Inc. <djs@fraserresearch.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)(* Code to parse the standard /etc/resolv.conf file for compatability with the
* standard resolver. Note the file format is so simple we don't bother with
* a full-blown yacc-style parser.
*)(* File format described in
* http://mirbsd.bsdadvocacy.org/cman/man5/resolv.conf.htm
* It doesn't mention case - we assume case-insensitive
* The standard resolver supports overrides through environment vars. Not implemented.
*)(* Ignore everything on a line after a '#' or ';' *)letstrip_comments=letre=Re.Str.regexp"[#;].*"infunx->Re.Str.global_replacere""x(* Remove any whitespace prefix and suffix from a line *)letltrim=Re.Str.(replace_first(regexp"^[\t ]+")"")letrtrim=Re.Str.(replace_first(regexp"[\t ]+$")"")lettrimx=ltrim(rtrimx)letmap_linex=matchtrim(strip_commentsx)with|""->None|x->SomexmoduleLookupValue=structtypet=Bind|File|YpexceptionUnknownofstringletof_stringx=match(String.lowercase_asciix)with|"bind"->Bind|"file"->File|"yp"->Yp|x->raise(Unknownx)letto_string=function|Bind->"bind"|File->"file"|Yp->"yp"endmoduleOptionsValue=structtypet=Debug|Edns0|Inet6|Insecure1|Insecure2|NdotsofintexceptionUnknownofstringletof_stringx=letx=String.lowercase_asciixinifString.lengthx>=6&&(String.subx06="ndots:")thenbegintryNdots(int_of_string(String.subx6(String.lengthx-6)))withFailure_->raise(Unknownx)endelsematchxwith|"debug"->Debug|"edns0"->Edns0|"inet6"->Inet6|"insecure1"->Insecure1|"insecure2"->Insecure2|x->raise(Unknownx)letto_string=function|Debug->"debug"|Edns0->"edns0"|Inet6->"inet6"|Insecure1->"insecure1"|Insecure2->"insecure2"|Ndotsn->"ndots:"^(string_of_intn)endmoduleKeywordValue=structtypet=|NameserverofIpaddr.t*intoption(* ipv4 dotted quad or ipv6 hex and colon *)|Portofint|Domainofstring|LookupofLookupValue.tlist|Searchofstringlist|Sortlistofstringlist|OptionsofOptionsValue.tlistexceptionUnknownofstringletsplit=Re.Str.split(Re.Str.regexp"[\t ]+")letns_of_stringns=letopenRe.Strinmatchstring_match(regexp"\\[\\(.+\\)\\]:\\([0-9]+\\)")ns0with|false->Nameserver(Ipaddr.of_string_exnns,None)|true->letserver=Ipaddr.of_string_exn(matched_group1ns)inletport=trySome(int_of_string(matched_group2ns))with_->NoneinNameserver(server,port)letstring_of_nsns=matchnswith|ns,None->Ipaddr.to_stringns|ns,Somep->Printf.sprintf"[%s]:%d"(Ipaddr.to_stringns)pletof_stringx=matchsplit(String.lowercase_asciix)with|["nameserver";ns]->ns_of_stringns|["domain";domain]->Domaindomain|["port";port]->(tryPort(int_of_stringport)with_->raise(Unknownx))|"lookup"::lst->Lookup(List.mapLookupValue.of_stringlst)|"search"::lst->Searchlst|"sortlist"::lst->Sortlistlst|"options"::lst->Options(List.mapOptionsValue.of_stringlst)|_->raise(Unknownx)letto_string=letsc=String.concat" "infunction|Nameserver(n,p)->sc["nameserver";(string_of_ns(n,p))]|Portp->sc["port";(string_of_intp)]|Domaindomain->sc["domain";domain]|Lookupl->sc("lookup"::(List.mapLookupValue.to_stringl))|Searchlst->sc("search"::lst)|Sortlistlst->sc("sortlist"::lst)|Optionslst->sc("options"::(List.mapOptionsValue.to_stringlst))end(* The state of the resolver could be extended later *)typet=KeywordValue.tlist(* Choose a DNS port, which will default to 53 or can be overridden by the
nameserver entry *)letchoose_portconfig=List.fold_left(funport->function|KeywordValue.Portx->x|_->port)53configletall_serversconfig=letdefault_port=choose_portconfiginList.rev(List.fold_left(funa->function|KeywordValue.Nameserver(ns,Somep)->(ns,p)::a|KeywordValue.Nameserver(ns,None)->(ns,default_port)::a|_->a)[]config)(* Choose a DNS server to query. Might do some round-robin thingy later *)letchoose_serverconfig=match(all_serversconfig)with|[]->None|(ns,port)::_->Some(ns,port)(* Return a list of domain suffixes to search *)letsearch_domainsconfig=letrelevant_entries=List.fold_left(funa->function|KeywordValue.Domainx->[x]::a|KeywordValue.Searchxs->xs::a|_->a)[]configin(* entries are mutually-exclusive, last one overrides *)matchrelevant_entrieswith|[]->[]|x::_->x