|
| 1 | +from __future__ import absolute_import |
| 2 | + |
| 3 | +import os.path |
| 4 | +import sys |
| 5 | +from datetime import timedelta |
| 6 | + |
| 7 | +from dns.name import from_text |
| 8 | +from dns.rdataclass import IN |
| 9 | +from dns.rdatatype import A, AAAA |
| 10 | + |
| 11 | +from dns.resolver import Cache |
| 12 | + |
| 13 | +from reconfigure.configs import HostsConfig |
| 14 | + |
| 15 | +from .dnspython import create_answer, create_simple_rrset |
| 16 | +from .expiration import NoExpirationCacheBase |
| 17 | +from .persistence import _DeserializeOnGetCacheBase |
| 18 | + |
| 19 | +_year_in_seconds = timedelta(days=365).total_seconds() |
| 20 | + |
| 21 | +# References to https://en.wikipedia.org/wiki/Hosts_(file) |
| 22 | +_WINDOWS_PATHS = [ |
| 23 | + # Microsoft Windows NT, 2000, XP,[5] 2003, Vista, 2008, 7, 2012, 8, 10 |
| 24 | + r"${SystemRoot}\System32\drivers\etc\hosts", |
| 25 | + # Microsoft Windows 95, 98, ME |
| 26 | + r"${WinDir}\hosts", |
| 27 | + # Microsoft Windows 3.1 |
| 28 | + r"${WinDir}\HOSTS", |
| 29 | + # Symbian OS 6.1-9.0 |
| 30 | + r"C:\system\data\hosts", |
| 31 | + # Symbian OS 9.1+ |
| 32 | + r"C:\private\10000882\hosts", |
| 33 | +] |
| 34 | +_UNIX_PATHS = [ |
| 35 | + # Unix, Unix-like, POSIX, Apple Macintosh Mac OS X 10.2 and newer, |
| 36 | + # Android, iOS 2.0 and newer |
| 37 | + "/etc/hosts", |
| 38 | + # openSUSE |
| 39 | + "/usr/etc/hosts", |
| 40 | +] |
| 41 | +_OTHER_PARTS = [ |
| 42 | + # Plan 9 |
| 43 | + "/lib/ndb/hosts", |
| 44 | + # BeOS |
| 45 | + "/boot/beos/etc/hosts", |
| 46 | + # Haiku |
| 47 | + "/boot/common/settings/network/hosts", |
| 48 | +] |
| 49 | + |
| 50 | + |
| 51 | +def guess_hosts_path(): |
| 52 | + if sys.platform == "win32": |
| 53 | + possible_paths = _WINDOWS_PATHS + _UNIX_PATHS + _OTHER_PARTS |
| 54 | + else: |
| 55 | + possible_paths = _UNIX_PATHS + _OTHER_PARTS + _WINDOWS_PATHS |
| 56 | + |
| 57 | + for path in possible_paths: |
| 58 | + path = os.path.expandvars(os.path.expanduser(path)) |
| 59 | + if os.path.exists(path): |
| 60 | + return path |
| 61 | + |
| 62 | + raise RuntimeError() |
| 63 | + |
| 64 | + |
| 65 | +def _convert_entries(entries, expiration=None): |
| 66 | + out_data = [] |
| 67 | + |
| 68 | + for entry in entries: |
| 69 | + if ":" in entry.address: |
| 70 | + rdtype = AAAA |
| 71 | + elif "." in entry.address: |
| 72 | + rdtype = A |
| 73 | + else: |
| 74 | + continue |
| 75 | + |
| 76 | + names = [entry.name] + [ |
| 77 | + alias.name for alias in entry.aliases |
| 78 | + ] |
| 79 | + for name in names: |
| 80 | + name = from_text(name) |
| 81 | + |
| 82 | + ip = entry.address |
| 83 | + rrset = create_simple_rrset(name, ip, rdtype, rdclass=IN) |
| 84 | + rrset.ttl = _year_in_seconds |
| 85 | + out_entry = create_answer(name, rrset) |
| 86 | + if expiration: |
| 87 | + out_entry.expiration = expiration |
| 88 | + |
| 89 | + out_data.append(out_entry) |
| 90 | + |
| 91 | + return out_data |
| 92 | + |
| 93 | + |
| 94 | +def loads(filename=None): |
| 95 | + if not filename: |
| 96 | + filename = guess_hosts_path() |
| 97 | + |
| 98 | + mtime = os.path.getmtime(filename) |
| 99 | + |
| 100 | + config = HostsConfig(path=filename) |
| 101 | + config.load() |
| 102 | + |
| 103 | + expiration = mtime + _year_in_seconds |
| 104 | + dnspython_data = _convert_entries(config.tree.hosts, expiration) |
| 105 | + |
| 106 | + return dnspython_data |
| 107 | + |
| 108 | + |
| 109 | +class HostsCacheBase(_DeserializeOnGetCacheBase, NoExpirationCacheBase): |
| 110 | + def __init__( |
| 111 | + self, |
| 112 | + filename, |
| 113 | + *args, |
| 114 | + **kwargs |
| 115 | + ): |
| 116 | + super(HostsCacheBase, self).__init__( |
| 117 | + *args, |
| 118 | + filename=filename, |
| 119 | + deserializer=loads, |
| 120 | + **kwargs) |
| 121 | + |
| 122 | + |
| 123 | +class HostsCache(HostsCacheBase, Cache): |
| 124 | + def __init__(self, *args, **kwargs): |
| 125 | + super(HostsCache, self).__init__(*args, **kwargs) |
0 commit comments