X-Git-Url: http://www.lcore.org/git/lcore.git/blobdiff_plain/b60a32da0a15deb572474b1f66a6c63695ed7491..560d0547386a23a8fb79d1919d60dfdd04b49f62:/dnscore.pas diff --git a/dnscore.pas b/dnscore.pas index 08f99d1..ac985e6 100644 --- a/dnscore.pas +++ b/dnscore.pas @@ -51,6 +51,23 @@ callers may use setstate_failure to mark the state as failed themselves before passing it on to other code, for example this may be done in the event of a timeout. + + + hosts file support: + + code is here to do hosts file lookups. this is not done automatically by + dnscore, the caller (this is dnssync and dnsasync) has to call the hosts file + functions if it wants hosts file lookups. + + the current implementation will automatically periodically reload the file into + a fast lookup cache if the file changed. + + both forward and reverse lookups are supported. + + a single IP having multiple hostnames, so "192.0.2.1 foo bar" is supported. + + local hostnames under the locally configured domain (so domain "example.org", + a "192.0.2.1 foo" entry causing "foo.example.org" to resolve) is not supported. } unit dnscore; @@ -221,12 +238,467 @@ var function getquerytype(s:ansistring):integer; +//optionally do any time consuming initing in advance, in case one wants the first dns lookup to be as fast as possible +procedure dnscore_preinit(alsoreverse:boolean); + +var + hostsfile_inited:boolean; + hostsfile_reverseinited:boolean; + hostsfile_filename:ansistring; //the app can change this, to use a custom hosts file + hostsfile_entries:tstringlist; + hostsfile_lastfileage:longint; //fileage is longint on freepascal, integer on delphi + hostsfile_lastcheckts:integer; + hostsfile_reverseentries:tstringlist; + + //parameter settings + hostsfile_disabled:boolean; //the app can set this to disable all hosts file lookup functionality, including localhost + hostsfile_onlylocalhost:boolean; //the app can set this to disable the hosts file but keep localhost + hostsfile_alsocustomserver:boolean; //the app can set this to use hosts file functionality even when a custom nameserver is set + hostsfile_manualreload:boolean; //don't check if the hosts file changed and auto reload it, the app can call hostsfile_reload to reload + +procedure hostsfile_init; +procedure hostsfile_initreverse; +procedure hostsfile_reload; +procedure hostsfile_reloadifneeded; +procedure hostsfile_add(const name:ansistring;const ip:tbinip); +function hostsfile_forwardlookuplist(const name:ansistring):tbiniplist; +function hostsfile_reverselookup(ip:tbinip):ansistring; +function gethostsfilename:ansistring; + implementation uses + {$ifdef mswindows}windows,{$endif} + {$ifdef unix}unix,{$endif} lcorelocalips, + readtxt2, + ltimevalstuff, sysutils; +type + pbiniplist=^tbiniplist; + thostsfile_entry=record + l:tbiniplist; + end; + phostsfile_entry=^thostsfile_entry; + + thostsfile_reverseentry=record + name:ansistring; + end; + phostsfile_reverseentry=^thostsfile_reverseentry; + + +function hostsfile_findbyname(const name:ansistring):integer; +begin + if not hostsfile_entries.Find(name,result) then begin + if (copy(name,length(name),1) = '.') then begin + //if the name has a trailing dot, try to find without it + if not hostsfile_entries.Find(copy(name,1,length(name)-1),result) then result := -1; + end else begin + //if the name has no trailing dot, try to find with it + if not hostsfile_entries.Find(name + '.',result) then result := -1; + end; + end; +end; + + +function hostsfile_forwardlookuplist(const name:ansistring):tbiniplist; +var + index:integer; + l:tbiniplist; +begin + hostsfile_init; + index := hostsfile_findbyname(name); + + result := biniplist_new; + + if (index >= 0) then begin + l := phostsfile_entry(hostsfile_entries.objects[index]).l; + + {$ifdef ipv6} + if (useaf <> useaf_v6) and (useaf <> useaf_preferv6) then + {$endif} + begin + addipsoffamily(result,l,af_inet); + end; + {$ifdef ipv6} + if (useaf <> useaf_v4) then begin + addipsoffamily(result,l,af_inet6); + if (useaf = useaf_preferv6) then begin + addipsoffamily(result,l,af_inet); + end; + end; + {$endif} + end; +end; + +procedure hostsfile_clearreverse; +var + index:integer; +begin + for index := hostsfile_reverseentries.count-1 downto 0 do begin + phostsfile_reverseentry(hostsfile_reverseentries.objects[index]).name := ''; + dispose(phostsfile_reverseentry(hostsfile_reverseentries.objects[index])); + end; + hostsfile_reverseentries.clear; +end; + + +procedure hostsfile_initreverse; +var + index,index2:integer; + l:tbiniplist; + a,countbefore:integer; + ip:tbinip; + s:ansistring; + he:phostsfile_reverseentry; +begin + hostsfile_init; + if hostsfile_reverseinited then exit; + hostsfile_reverseinited := true; + + hostsfile_clearreverse; + + //build fast search table for reverse lookups + for index := hostsfile_entries.count-1 downto 0 do begin + l := phostsfile_entry(hostsfile_entries.objects[index]).l; + for a := biniplist_getcount(l)-1 downto 0 do begin + ip := biniplist_get(l,a); + s := ipbintostr(ip); + + countbefore := hostsfile_reverseentries.count; + index2 := hostsfile_reverseentries.Add(s); + if (hostsfile_reverseentries.count > countbefore) then begin + new(he); + hostsfile_reverseentries.objects[index2] := tobject(he); + he.name := hostsfile_entries[index]; + end; + + end; + end; +end; + +function hostsfile_reverselookup(ip:tbinip):ansistring; +var + index:integer; + s:ansistring; +begin + hostsfile_initreverse; + result := ''; + s := ipbintostr(ip); + + if hostsfile_reverseentries.find(s,index) then begin + result := phostsfile_reverseentry(hostsfile_reverseentries.objects[index]).name; + end; +end; + +procedure hostsfile_clear; +var + index:integer; +begin + for index := hostsfile_entries.count-1 downto 0 do begin + biniplist_free(phostsfile_entry(hostsfile_entries.objects[index]).l); + dispose(phostsfile_entry(hostsfile_entries.objects[index])); + end; + hostsfile_entries.clear; + + hostsfile_clearreverse; + + hostsfile_lastfileage := 0; + hostsfile_lastcheckts := 0; +end; + +procedure hostsfile_add(const name:ansistring;const ip:tbinip); +var + + a,index,countbefore:integer; + + ip2:tbinip; + he:phostsfile_entry; + l:tbiniplist; +begin + + countbefore := hostsfile_entries.count; + //add, with dupignore, will add it if it's not in the list. if it is in the list, it returns the index + //to know if it was added, see if the count went up. this saves on duplicate searches in the list, for speed + index := hostsfile_entries.add(name); + + if (hostsfile_entries.count > countbefore) then begin + // writeln('name newly added ',name,' ',ipbintostr(ip),' ',index); + + new(he); + hostsfile_entries.objects[index] := tobject(he); + he.l := biniplist_new; + //he.name := name; + end else begin + // writeln('name found ',name,' ',ipbintostr(ip),' ',index); + //search for IP match + + he := phostsfile_entry(hostsfile_entries.objects[index]); + l := he.l; + for a := biniplist_getcount(l)-1 downto 0 do begin + ip2 := biniplist_get(l,a); + if comparebinip(ip,ip2) then begin + // writeln('duplicate ip ',name,' ',ipbintostr(ip)); + exit; //duplicate + end; + end; + end; + //add it + biniplist_add(he.l,ip); +end; + + +function getts:integer; +{$ifdef mswindows} +begin + result := GetTickCount; +{$else} +var + temp:ttimeval; +begin + gettimemonotonic(temp); + result := ((temp.tv_usec div 1000) + (temp.tv_sec * 1000)); +{$endif} +end; + + +function gethostsfilename:ansistring; +var +{$ifdef mswindows} + windir:array [0..255] of ansichar; + + GetSystemWindowsDirectoryA:function(buffer:pansichar;size:integer):integer; stdcall; + dllhandle:thandle; + OsVersion : TOSVersionInfo; +{$endif} + filenamesuffix:ansistring; +begin + {$ifdef mswindows} + + ZeroMemory(@OsVersion, SizeOf(OsVersion)); + OsVersion.dwOSVersionInfoSize := SizeOf(TOSVERSIONINFO); + + if ((GetVersionEx(OsVersion)) and (OsVersion.dwPlatformId = VER_PLATFORM_WIN32_NT)) then begin + filenamesuffix := '\system32\drivers\etc\hosts'; + end else begin + filenamesuffix := '\hosts'; + end; + + //first try "user" windows directory. on a multiuser this may not be c:\windows + GetWindowsDirectoryA(windir,255); + result := windir; + if (copy(result,length(result),1) = '\') then result := copy(result,1,length(result)-1); + result := result + filenamesuffix; + + if not fileexists(result) then begin + //then try "system" windows directory which is typically c:\windows on a multiuser system + dllhandle := loadlibrary('kernel32.dll'); + if (dllhandle <> 0) then begin + GetSystemWindowsDirectoryA := getprocaddress(dllhandle,'GetSystemWindowsDirectoryA'); + if assigned(GetSystemWindowsDirectoryA) then begin + GetSystemWindowsDirectoryA(windir,255); + freelibrary(dllhandle); + result := windir; + if (copy(result,length(result),1) = '\') then result := copy(result,1,length(result)-1); + result := result + filenamesuffix; + end; + end; + end; + + {$else} + result := '/etc/hosts'; + {$endif} +end; + +procedure hostsfile_reload; +label lineend; +var + t:treadtxt; + + validchar:array[0..255] of boolean; + ipv4char:array[0..255] of boolean; + s:ansistring; + ch:ansichar; + a,len,field,startindex,labellen:integer; + lastwasspace,onlyipv4chars:boolean; + ipstring,hostname:ansistring; + biniptemp:tbinip; +begin + hostsfile_clear; + + if hostsfile_disabled then exit; + hostsfile_reverseinited := false; + + //add builtin entries + hostsfile_add('localhost',ipstrtobinf('127.0.0.1')); + {$ifdef ipv6} + hostsfile_add('localhost',ipstrtobinf('::1')); + {$endif} + + if hostsfile_onlylocalhost then exit; + + if (hostsfile_filename = '') then hostsfile_filename := gethostsfilename; + + //DNS names can only contain lower and uppercase, digits, dash, and dot + fillchar(validchar,sizeof(validchar),0); + validchar[ord('.')] := true; + validchar[ord('-')] := true; + fillchar(validchar[48],10,1); + fillchar(validchar[65],26,1); + fillchar(validchar[97],26,1); + + //chars that can be in an ipv4 address: digits and dot + fillchar(ipv4char,sizeof(ipv4char),0); + ipv4char[ord('.')] := true; + fillchar(ipv4char[48],10,1); + + hostsfile_lastfileage := fileage(hostsfile_filename); + hostsfile_lastcheckts := getts; + //writeln('------ reloading ',hostsfile_lastfileage); + try + t := treadtxt.createf(hostsfile_filename); + except + exit; + end; + if not assigned(t) then exit; + + while not t.eof do begin + s := t.readline; + + len := length(s); + if (len > 512) then goto lineend; //sanity check + field := -1; + lastwasspace := true; + + onlyipv4chars := true; + + //one extra loop iteration at the end with a "pretend space" for easy parsing + inc(len); + ipstring := ''; + hostname := ''; + a := 0; + while (a <= len) do begin + inc(a); + if (a >= len) then ch := ' ' else ch := s[a]; + + if (ch = '#') then begin + //pretend the start of a comment is a space and the end of the line + ch := ' '; + len := a; + end; + + if (ch = #9) or (ch = ' ') then begin + if not (lastwasspace) then begin + if (field = 0) then begin + ipstring := copy(s,startindex,a - startindex); + end else if (field >= 1) then begin + //maximum length of hostname + if (a - startindex) > 253 then goto lineend; + + //remove a trailing dot + //if (labellen = 0) then dec(a); + + //hostname must not be an empty string + if (a - startindex) < 1 then goto lineend; + + hostname := copy(s,startindex,a - startindex); + + //reject a hosts entry with a name that is a valid ipv4 address. + //we don't need to check for ipv6 addresses because they have a colon and so aren't valid hostsnames + //the windows resolver does allow it, but i think it has potential security issues + if onlyipv4chars then if ipstrtobin(hostname,biniptemp) then goto lineend; + + if ipstrtobin(ipstring,biniptemp) then begin + //writeln('!!!hosts file adding ip=',ipstring,'@host=',hostname,'@'); + + hostsfile_add(hostname,biniptemp); + end; + + //break scan loop + //a := len; + end; + + end; + + lastwasspace := true; + end else begin + if lastwasspace then begin + inc(field); + startindex := a; + lastwasspace := false; + labellen := 0; + end; + //enforce valid characters in hostname + if (field = 1) then begin + if not validchar[ord(ch)] then goto lineend; + onlyipv4chars := onlyipv4chars and ipv4char[ord(ch)]; + if (ch = '.') then begin + if (labellen = 0) then goto lineend; + labellen := 0; + end else begin + inc(labellen); + if (labellen > 63) then goto lineend; + end; + end; + end; + end; +lineend: + end; + t.destroy; + +end; + + +procedure hostsfile_reloadifneeded; +var + ts:integer; +begin + if (hostsfile_disabled or hostsfile_onlylocalhost or hostsfile_manualreload) then exit; + if hostsfile_filename = '' then exit; + + ts := getts; + //writeln('reloadifneeded ts=',ts,' oldts=',hostsfile_lastcheckts); + if not ((ts > hostsfile_lastcheckts + 10000) or (ts < hostsfile_lastcheckts)) then exit; + + hostsfile_lastcheckts := ts; + + //writeln('reloadifneeded new=',fileage(hostsfile_filename),' old=',hostsfile_lastfileage); + if fileage(hostsfile_filename) = hostsfile_lastfileage then exit; + hostsfile_reload; +end; + +procedure hostsfile_init; +begin + //writeln('init ',hostsfile_inited); + if hostsfile_inited then begin + hostsfile_reloadifneeded; + exit; + end; + hostsfile_inited := true; + hostsfile_entries := tstringlist.create; + hostsfile_entries.casesensitive := false; + hostsfile_entries.sorted := true; + hostsfile_entries.duplicates := dupignore; + + hostsfile_reverseentries := tstringlist.create; + hostsfile_reverseentries.casesensitive := true; + hostsfile_reverseentries.sorted := true; + hostsfile_reverseentries.duplicates := dupignore; + + hostsfile_reload; +end; + +procedure dnscore_preinit(alsoreverse:boolean); +begin + {$ifdef ipv6} + initpreferredmode; + {$endif} + hostsfile_init; + populatednsserverlist; + randomdword; + if alsoreverse then hostsfile_initreverse; +end; function getquerytype(s:ansistring):integer; @@ -597,7 +1069,6 @@ recursed: goto failure; end; - {do /etc/hosts lookup here} state.sendpacketlen := buildrequest(state.queryname,state.sendpacket,state.requesttype); if state.sendpacketlen = 0 then begin failurereason := 'building request packet failed';