win32util.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import sys
  2. import dns._features
  3. if sys.platform == "win32":
  4. from typing import Any
  5. import dns.name
  6. _prefer_wmi = True
  7. import winreg # pylint: disable=import-error
  8. # Keep pylint quiet on non-windows.
  9. try:
  10. _ = WindowsError # pylint: disable=used-before-assignment
  11. except NameError:
  12. WindowsError = Exception
  13. if dns._features.have("wmi"):
  14. import threading
  15. import pythoncom # pylint: disable=import-error
  16. import wmi # pylint: disable=import-error
  17. _have_wmi = True
  18. else:
  19. _have_wmi = False
  20. def _config_domain(domain):
  21. # Sometimes DHCP servers add a '.' prefix to the default domain, and
  22. # Windows just stores such values in the registry (see #687).
  23. # Check for this and fix it.
  24. if domain.startswith("."):
  25. domain = domain[1:]
  26. return dns.name.from_text(domain)
  27. class DnsInfo:
  28. def __init__(self):
  29. self.domain = None
  30. self.nameservers = []
  31. self.search = []
  32. if _have_wmi:
  33. class _WMIGetter(threading.Thread):
  34. # pylint: disable=possibly-used-before-assignment
  35. def __init__(self):
  36. super().__init__()
  37. self.info = DnsInfo()
  38. def run(self):
  39. pythoncom.CoInitialize()
  40. try:
  41. system = wmi.WMI()
  42. for interface in system.Win32_NetworkAdapterConfiguration():
  43. if interface.IPEnabled and interface.DNSServerSearchOrder:
  44. self.info.nameservers = list(interface.DNSServerSearchOrder)
  45. if interface.DNSDomain:
  46. self.info.domain = _config_domain(interface.DNSDomain)
  47. if interface.DNSDomainSuffixSearchOrder:
  48. self.info.search = [
  49. _config_domain(x)
  50. for x in interface.DNSDomainSuffixSearchOrder
  51. ]
  52. break
  53. finally:
  54. pythoncom.CoUninitialize()
  55. def get(self):
  56. # We always run in a separate thread to avoid any issues with
  57. # the COM threading model.
  58. self.start()
  59. self.join()
  60. return self.info
  61. else:
  62. class _WMIGetter: # type: ignore
  63. pass
  64. class _RegistryGetter:
  65. def __init__(self):
  66. self.info = DnsInfo()
  67. def _split(self, text):
  68. # The windows registry has used both " " and "," as a delimiter, and while
  69. # it is currently using "," in Windows 10 and later, updates can seemingly
  70. # leave a space in too, e.g. "a, b". So we just convert all commas to
  71. # spaces, and use split() in its default configuration, which splits on
  72. # all whitespace and ignores empty strings.
  73. return text.replace(",", " ").split()
  74. def _config_nameservers(self, nameservers):
  75. for ns in self._split(nameservers):
  76. if ns not in self.info.nameservers:
  77. self.info.nameservers.append(ns)
  78. def _config_search(self, search):
  79. for s in self._split(search):
  80. s = _config_domain(s)
  81. if s not in self.info.search:
  82. self.info.search.append(s)
  83. def _config_fromkey(self, key, always_try_domain):
  84. try:
  85. servers, _ = winreg.QueryValueEx(key, "NameServer")
  86. except WindowsError:
  87. servers = None
  88. if servers:
  89. self._config_nameservers(servers)
  90. if servers or always_try_domain:
  91. try:
  92. dom, _ = winreg.QueryValueEx(key, "Domain")
  93. if dom:
  94. self.info.domain = _config_domain(dom)
  95. except WindowsError:
  96. pass
  97. else:
  98. try:
  99. servers, _ = winreg.QueryValueEx(key, "DhcpNameServer")
  100. except WindowsError:
  101. servers = None
  102. if servers:
  103. self._config_nameservers(servers)
  104. try:
  105. dom, _ = winreg.QueryValueEx(key, "DhcpDomain")
  106. if dom:
  107. self.info.domain = _config_domain(dom)
  108. except WindowsError:
  109. pass
  110. try:
  111. search, _ = winreg.QueryValueEx(key, "SearchList")
  112. except WindowsError:
  113. search = None
  114. if search is None:
  115. try:
  116. search, _ = winreg.QueryValueEx(key, "DhcpSearchList")
  117. except WindowsError:
  118. search = None
  119. if search:
  120. self._config_search(search)
  121. def _is_nic_enabled(self, lm, guid):
  122. # Look in the Windows Registry to determine whether the network
  123. # interface corresponding to the given guid is enabled.
  124. #
  125. # (Code contributed by Paul Marks, thanks!)
  126. #
  127. try:
  128. # This hard-coded location seems to be consistent, at least
  129. # from Windows 2000 through Vista.
  130. connection_key = winreg.OpenKey(
  131. lm,
  132. r"SYSTEM\CurrentControlSet\Control\Network"
  133. r"\{4D36E972-E325-11CE-BFC1-08002BE10318}"
  134. rf"\{guid}\Connection",
  135. )
  136. try:
  137. # The PnpInstanceID points to a key inside Enum
  138. (pnp_id, ttype) = winreg.QueryValueEx(
  139. connection_key, "PnpInstanceID"
  140. )
  141. if ttype != winreg.REG_SZ:
  142. raise ValueError # pragma: no cover
  143. device_key = winreg.OpenKey(
  144. lm, rf"SYSTEM\CurrentControlSet\Enum\{pnp_id}"
  145. )
  146. try:
  147. # Get ConfigFlags for this device
  148. (flags, ttype) = winreg.QueryValueEx(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(
  170. lm, r"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters"
  171. )
  172. try:
  173. self._config_fromkey(tcp_params, True)
  174. finally:
  175. tcp_params.Close()
  176. interfaces = winreg.OpenKey(
  177. lm,
  178. r"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces",
  179. )
  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 OSError:
  194. break
  195. finally:
  196. interfaces.Close()
  197. finally:
  198. lm.Close()
  199. return self.info
  200. _getter_class: Any
  201. if _have_wmi and _prefer_wmi:
  202. _getter_class = _WMIGetter
  203. else:
  204. _getter_class = _RegistryGetter
  205. def get_dns_info():
  206. """Extract resolver configuration."""
  207. getter = _getter_class()
  208. return getter.get()