win32util.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import sys
  2. if sys.platform == 'win32':
  3. import dns.name
  4. _prefer_wmi = True
  5. import winreg
  6. try:
  7. try:
  8. import threading as _threading
  9. except ImportError: # pragma: no cover
  10. import dummy_threading as _threading # type: ignore
  11. import pythoncom
  12. import wmi
  13. _have_wmi = True
  14. except Exception:
  15. _have_wmi = False
  16. def _config_domain(domain):
  17. # Sometimes DHCP servers add a '.' prefix to the default domain, and
  18. # Windows just stores such values in the registry (see #687).
  19. # Check for this and fix it.
  20. if domain.startswith('.'):
  21. domain = domain[1:]
  22. return dns.name.from_text(domain)
  23. class DnsInfo:
  24. def __init__(self):
  25. self.domain = None
  26. self.nameservers = []
  27. self.search = []
  28. if _have_wmi:
  29. class _WMIGetter(_threading.Thread):
  30. def __init__(self):
  31. super().__init__()
  32. self.info = DnsInfo()
  33. def run(self):
  34. pythoncom.CoInitialize()
  35. try:
  36. system = wmi.WMI()
  37. for interface in system.Win32_NetworkAdapterConfiguration():
  38. if interface.IPEnabled:
  39. self.info.domain = _config_domain(interface.DNSDomain)
  40. self.info.nameservers = list(interface.DNSServerSearchOrder)
  41. self.info.search = [dns.name.from_text(x) for x in
  42. interface.DNSDomainSuffixSearchOrder]
  43. break
  44. finally:
  45. pythoncom.CoUninitialize()
  46. def get(self):
  47. # We always run in a separate thread to avoid any issues with
  48. # the COM threading model.
  49. self.start()
  50. self.join()
  51. return self.info
  52. else:
  53. class _WMIGetter:
  54. pass
  55. class _RegistryGetter:
  56. def __init__(self):
  57. self.info = DnsInfo()
  58. def _determine_split_char(self, entry):
  59. #
  60. # The windows registry irritatingly changes the list element
  61. # delimiter in between ' ' and ',' (and vice-versa) in various
  62. # versions of windows.
  63. #
  64. if entry.find(' ') >= 0:
  65. split_char = ' '
  66. elif entry.find(',') >= 0:
  67. split_char = ','
  68. else:
  69. # probably a singleton; treat as a space-separated list.
  70. split_char = ' '
  71. return split_char
  72. def _config_nameservers(self, nameservers):
  73. split_char = self._determine_split_char(nameservers)
  74. ns_list = nameservers.split(split_char)
  75. for ns in ns_list:
  76. if ns not in self.info.nameservers:
  77. self.info.nameservers.append(ns)
  78. def _config_search(self, search):
  79. split_char = self._determine_split_char(search)
  80. search_list = search.split(split_char)
  81. for s in search_list:
  82. s = dns.name.from_text(s)
  83. if s not in self.info.search:
  84. self.info.search.append(s)
  85. def _config_fromkey(self, key, always_try_domain):
  86. try:
  87. servers, _ = winreg.QueryValueEx(key, 'NameServer')
  88. except WindowsError:
  89. servers = None
  90. if servers:
  91. self._config_nameservers(servers)
  92. if servers or always_try_domain:
  93. try:
  94. dom, _ = winreg.QueryValueEx(key, 'Domain')
  95. if dom:
  96. self.info.domain = _config_domain(dom)
  97. except WindowsError:
  98. pass
  99. else:
  100. try:
  101. servers, _ = winreg.QueryValueEx(key, 'DhcpNameServer')
  102. except WindowsError:
  103. servers = None
  104. if servers:
  105. self._config_nameservers(servers)
  106. try:
  107. dom, _ = winreg.QueryValueEx(key, 'DhcpDomain')
  108. if dom:
  109. self.info.domain = _config_domain(dom)
  110. except WindowsError:
  111. pass
  112. try:
  113. search, _ = winreg.QueryValueEx(key, 'SearchList')
  114. except WindowsError:
  115. search = None
  116. if search is None:
  117. try:
  118. search, _ = winreg.QueryValueEx(key, 'DhcpSearchList')
  119. except WindowsError:
  120. search = None
  121. if search:
  122. self._config_search(search)
  123. def _is_nic_enabled(self, lm, guid):
  124. # Look in the Windows Registry to determine whether the network
  125. # interface corresponding to the given guid is enabled.
  126. #
  127. # (Code contributed by Paul Marks, thanks!)
  128. #
  129. try:
  130. # This hard-coded location seems to be consistent, at least
  131. # from Windows 2000 through Vista.
  132. connection_key = winreg.OpenKey(
  133. lm,
  134. r'SYSTEM\CurrentControlSet\Control\Network'
  135. r'\{4D36E972-E325-11CE-BFC1-08002BE10318}'
  136. r'\%s\Connection' % guid)
  137. try:
  138. # The PnpInstanceID points to a key inside Enum
  139. (pnp_id, ttype) = winreg.QueryValueEx(
  140. connection_key, 'PnpInstanceID')
  141. if ttype != winreg.REG_SZ:
  142. raise ValueError # pragma: no cover
  143. device_key = winreg.OpenKey(
  144. lm, r'SYSTEM\CurrentControlSet\Enum\%s' % pnp_id)
  145. try:
  146. # Get ConfigFlags for this device
  147. (flags, ttype) = winreg.QueryValueEx(
  148. device_key, 'ConfigFlags')
  149. if ttype != winreg.REG_DWORD:
  150. raise ValueError # pragma: no cover
  151. # Based on experimentation, bit 0x1 indicates that the
  152. # device is disabled.
  153. #
  154. # XXXRTH I suspect we really want to & with 0x03 so
  155. # that CONFIGFLAGS_REMOVED devices are also ignored,
  156. # but we're shifting to WMI as ConfigFlags is not
  157. # supposed to be used.
  158. return not flags & 0x1
  159. finally:
  160. device_key.Close()
  161. finally:
  162. connection_key.Close()
  163. except Exception: # pragma: no cover
  164. return False
  165. def get(self):
  166. """Extract resolver configuration from the Windows registry."""
  167. lm = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
  168. try:
  169. tcp_params = winreg.OpenKey(lm,
  170. r'SYSTEM\CurrentControlSet'
  171. r'\Services\Tcpip\Parameters')
  172. try:
  173. self._config_fromkey(tcp_params, True)
  174. finally:
  175. tcp_params.Close()
  176. interfaces = winreg.OpenKey(lm,
  177. r'SYSTEM\CurrentControlSet'
  178. r'\Services\Tcpip\Parameters'
  179. r'\Interfaces')
  180. try:
  181. i = 0
  182. while True:
  183. try:
  184. guid = winreg.EnumKey(interfaces, i)
  185. i += 1
  186. key = winreg.OpenKey(interfaces, guid)
  187. try:
  188. if not self._is_nic_enabled(lm, guid):
  189. continue
  190. self._config_fromkey(key, False)
  191. finally:
  192. key.Close()
  193. except EnvironmentError:
  194. break
  195. finally:
  196. interfaces.Close()
  197. finally:
  198. lm.Close()
  199. return self.info
  200. if _have_wmi and _prefer_wmi:
  201. _getter_class = _WMIGetter
  202. else:
  203. _getter_class = _RegistryGetter
  204. def get_dns_info():
  205. """Extract resolver configuration."""
  206. getter = _getter_class()
  207. return getter.get()