1=begin
2= Win32 DNS and DHCP I/F
3
4=end
5
6require "fiddle/import"
7require 'win32/registry'
8
9module Win32
10  module Resolv
11    API = Registry::API
12
13    def self.get_hosts_path
14      path = get_hosts_dir
15      path = File.expand_path('hosts', path)
16      File.exist?(path) ? path : nil
17    end
18
19    def self.get_resolv_info
20      search, nameserver = get_info
21      if search.empty?
22        search = nil
23      else
24        search.delete("")
25        search.uniq!
26      end
27      if nameserver.empty?
28        nameserver = nil
29      else
30        nameserver.delete("")
31        nameserver.delete("0.0.0.0")
32        nameserver.uniq!
33      end
34      [ search, nameserver ]
35    end
36
37module Kernel32
38  extend Fiddle::Importer
39  dlload "kernel32"
40end
41getv = Kernel32.extern "int GetVersionExA(void *)", :stdcall
42info = [ 148, 0, 0, 0, 0 ].pack('V5') + "\0" * 128
43getv.call(info)
44if info.unpack('V5')[4] == 2  # VER_PLATFORM_WIN32_NT
45#====================================================================
46# Windows NT
47#====================================================================
48  module_eval <<-'__EOS__', __FILE__, __LINE__+1
49    TCPIP_NT = 'SYSTEM\CurrentControlSet\Services\Tcpip\Parameters'
50
51    class << self
52      private
53      def get_hosts_dir
54        Registry::HKEY_LOCAL_MACHINE.open(TCPIP_NT) do |reg|
55          reg.read_s_expand('DataBasePath')
56        end
57      end
58
59      def get_info
60        search = nil
61        nameserver = []
62        Registry::HKEY_LOCAL_MACHINE.open(TCPIP_NT) do |reg|
63          begin
64            slist = reg.read_s('SearchList')
65            search = slist.split(/,\s*/) unless slist.empty?
66          rescue Registry::Error
67          end
68
69          if add_search = search.nil?
70            search = []
71            begin
72              nvdom = reg.read_s('NV Domain')
73              unless nvdom.empty?
74                @search = [ nvdom ]
75                if reg.read_i('UseDomainNameDevolution') != 0
76                  if /^\w+\./ =~ nvdom
77                    devo = $'
78                  end
79                end
80              end
81            rescue Registry::Error
82            end
83          end
84
85          reg.open('Interfaces') do |h|
86            h.each_key do |iface,|
87              h.open(iface) do |regif|
88                begin
89                  [ 'NameServer', 'DhcpNameServer' ].each do |key|
90                    begin
91                      ns = regif.read_s(key)
92                    rescue
93                    else
94                      unless ns.empty?
95                        nameserver.concat(ns.split(/[,\s]\s*/))
96                        break
97                      end
98                    end
99                  end
100                rescue Registry::Error
101                end
102
103                if add_search
104                  begin
105                    [ 'Domain', 'DhcpDomain' ].each do |key|
106                      dom = regif.read_s(key)
107                      unless dom.empty?
108                        search.concat(dom.split(/,\s*/))
109                        break
110                      end
111                    end
112                  rescue Registry::Error
113                  end
114                end
115              end
116            end
117          end
118          search << devo if add_search and devo
119        end
120        [ search.uniq, nameserver.uniq ]
121      end
122    end
123  __EOS__
124else
125#====================================================================
126# Windows 9x
127#====================================================================
128  module_eval <<-'__EOS__', __FILE__, __LINE__+1
129    TCPIP_9X = 'SYSTEM\CurrentControlSet\Services\VxD\MSTCP'
130    DHCP_9X = 'SYSTEM\CurrentControlSet\Services\VxD\DHCP'
131    WINDOWS = 'Software\Microsoft\Windows\CurrentVersion'
132
133    class << self
134   #   private
135
136      def get_hosts_dir
137        Registry::HKEY_LOCAL_MACHINE.open(WINDOWS) do |reg|
138          reg.read_s_expand('SystemRoot')
139        end
140      end
141
142      def get_info
143        search = []
144        nameserver = []
145        begin
146          Registry::HKEY_LOCAL_MACHINE.open(TCPIP_9X) do |reg|
147            if reg.read_s("EnableDNS") == "1"
148              domain = reg.read_s("Domain")
149              ns = reg.read_s("NameServer")
150              slist = reg.read_s("SearchList")
151              search << domain unless domain.empty?
152              search.concat(slist.split(/,\s*/))
153              nameserver.concat(ns.split(/[,\s]\s*/))
154            end
155          end
156        rescue Registry::Error
157        end
158
159        dhcpinfo = get_dhcpinfo
160        search.concat(dhcpinfo[0])
161        nameserver.concat(dhcpinfo[1])
162        [ search, nameserver ]
163      end
164
165      def get_dhcpinfo
166        macaddrs = {}
167        ipaddrs = {}
168        WsControl.get_iflist.each do |index, macaddr, *ipaddr|
169          macaddrs[macaddr] = 1
170          ipaddr.each { |ipaddr| ipaddrs[ipaddr] = 1 }
171        end
172        iflist = [ macaddrs, ipaddrs ]
173
174        search = []
175        nameserver = []
176        version = -1
177        Registry::HKEY_LOCAL_MACHINE.open(DHCP_9X) do |reg|
178          begin
179            version = API.unpackdw(reg.read_bin("Version"))
180          rescue Registry::Error
181          end
182
183          reg.each_key do |key,|
184            catch(:not_used) do
185              reg.open(key) do |regdi|
186                dom, ns = get_dhcpinfo_key(version, regdi, iflist)
187                search << dom if dom
188                nameserver.concat(ns) if ns
189              end
190            end
191          end
192        end
193        [ search, nameserver ]
194      end
195
196      def get_dhcpinfo_95(reg)
197        dhcp = reg.read_bin("DhcpInfo")
198        [
199          API.unpackdw(dhcp[4..7]),
200          API.unpackdw(dhcp[8..11]),
201          1,
202          dhcp[45..50],
203          reg.read_bin("OptionInfo"),
204        ]
205      end
206
207      def get_dhcpinfo_98(reg)
208        [
209          API.unpackdw(reg.read_bin("DhcpIPAddress")),
210          API.unpackdw(reg.read_bin("DhcpSubnetMask")),
211          API.unpackdw(reg.read_bin("HardwareType")),
212          reg.read_bin("HardwareAddress"),
213          reg.read_bin("OptionInfo"),
214        ]
215      end
216
217      def get_dhcpinfo_key(version, reg, iflist)
218        info = case version
219               when 1
220                 get_dhcpinfo_95(reg)
221               when 2
222                 get_dhcpinfo_98(reg)
223               else
224                 begin
225                   get_dhcpinfo_98(reg)
226                 rescue Registry::Error
227                   get_dhcpinfo_95(reg)
228                 end
229               end
230        ipaddr, netmask, hwtype, macaddr, opt = info
231        throw :not_used unless
232          ipaddr and ipaddr != 0 and
233          netmask and netmask != 0 and
234          macaddr and macaddr.size == 6 and
235          hwtype == 1 and
236          iflist[0][macaddr] and iflist[1][ipaddr]
237
238        size = opt.size
239        idx = 0
240        while idx <= size
241          opttype = opt[idx]
242          optsize = opt[idx + 1]
243          optval  = opt[idx + 2, optsize]
244          case opttype
245          when 0xFF    ## term
246            break
247          when 0x0F    ## domain
248            domain = optval.chomp("\0")
249          when 0x06    ## dns
250            nameserver = optval.scan(/..../).collect { |addr|
251              "%d.%d.%d.%d" % addr.unpack('C4')
252            }
253          end
254          idx += optsize + 2
255        end
256        [ domain, nameserver ]
257      rescue Registry::Error
258        throw :not_used
259      end
260    end
261
262    module WsControl
263      module WSock32
264        extend Fiddle::Importer
265        dlload "wsock32.dll"
266      end
267      WsControl = WSock32.extern "int WsControl(int, int, void *, void *, void *, void *", :stdcall
268      WSAGetLastError = WSock32.extern "int WSAGetLastError(void)", :stdcall
269
270      MAX_TDI_ENTITIES = 512
271      IPPROTO_TCP = 6
272      WSCTL_TCP_QUERY_INFORMATION = 0
273      INFO_CLASS_GENERIC = 0x100
274      INFO_CLASS_PROTOCOL = 0x200
275      INFO_TYPE_PROVIDER = 0x100
276      ENTITY_LIST_ID = 0
277      GENERIC_ENTITY = 0
278      CL_NL_ENTITY = 0x301
279      IF_ENTITY = 0x200
280      ENTITY_TYPE_ID = 1
281      CL_NL_IP = 0x303
282      IF_MIB = 0x202
283      IF_MIB_STATS_ID = 1
284      IP_MIB_ADDRTABLE_ENTRY_ID = 0x102
285
286      def self.wsctl(tei_entity, tei_instance,
287                     toi_class, toi_type, toi_id,
288                     buffsize)
289        reqinfo = [
290                  ## TDIEntityID
291                    tei_entity, tei_instance,
292                  ## TDIObjectID
293                    toi_class, toi_type, toi_id,
294                  ## TCP_REQUEST_INFORMATION_EX
295                    ""
296                  ].pack('VVVVVa16')
297        reqsize = API.packdw(reqinfo.size)
298        buff = "\0" * buffsize
299        buffsize = API.packdw(buffsize)
300        result = WsControl.call(
301                   IPPROTO_TCP,
302                   WSCTL_TCP_QUERY_INFORMATION,
303                   reqinfo, reqsize,
304                   buff, buffsize)
305        if result != 0
306          raise RuntimeError, "WsControl failed.(#{result})"
307        end
308        [ buff, API.unpackdw(buffsize) ]
309      end
310      private_class_method :wsctl
311
312      def self.get_iflist
313        # Get TDI Entity List
314        entities, size =
315          wsctl(GENERIC_ENTITY, 0,
316                INFO_CLASS_GENERIC,
317                INFO_TYPE_PROVIDER,
318                ENTITY_LIST_ID,
319                MAX_TDI_ENTITIES * 8)  # sizeof(TDIEntityID)
320        entities = entities[0, size].
321                     scan(/.{8}/).
322                     collect { |e| e.unpack('VV') }
323
324        # Get MIB Interface List
325        iflist = []
326        ifcount = 0
327        entities.each do |entity, instance|
328          if( (entity & IF_ENTITY)>0 )
329            ifcount += 1
330            etype, = wsctl(entity, instance,
331                           INFO_CLASS_GENERIC,
332                           INFO_TYPE_PROVIDER,
333                           ENTITY_TYPE_ID,
334                           4)
335            if( (API.unpackdw(etype) & IF_MIB)==IF_MIB )
336              ifentry, = wsctl(entity, instance,
337                               INFO_CLASS_PROTOCOL,
338                               INFO_TYPE_PROVIDER,
339                               IF_MIB_STATS_ID,
340                               21 * 4 + 8 + 130)  # sizeof(IFEntry)
341              iflist << [
342                API.unpackdw(ifentry[0,4]),
343                ifentry[20, 6]
344              ]
345            end
346          end
347        end
348
349        # Get IP Addresses
350        entities.each do |entity, instance|
351          if entity == CL_NL_ENTITY
352            etype, = wsctl(entity, instance,
353                           INFO_CLASS_GENERIC,
354                           INFO_TYPE_PROVIDER,
355                           ENTITY_TYPE_ID,
356                           4)
357            if API.unpackdw(etype) == CL_NL_IP
358              ipentries, = wsctl(entity, instance,
359                                 INFO_CLASS_PROTOCOL,
360                                 INFO_TYPE_PROVIDER,
361                                 IP_MIB_ADDRTABLE_ENTRY_ID,
362                                 24 * (ifcount+1))  # sizeof(IPAddrEntry)
363              ipentries.scan(/.{24}/) do |ipentry|
364                ipaddr, index = ipentry.unpack('VV')
365                if ifitem = iflist.assoc(index)
366                  ifitem << ipaddr
367                end
368              end
369            end
370          end
371        end
372        iflist
373      end
374    end
375  __EOS__
376end
377#====================================================================
378  end
379end
380