Guest User

Untitled

a guest
Jan 20th, 2019
93
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.54 KB | None | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. """
  4. This script will run on a linux system which uses dnsmasq.
  5.  
  6. It extracts cached DNS entries from dnsmasq's logs and writes them to
  7. your system's hosts file.
  8.  
  9. It also looks for CNAME entries and attempts to "dig" them, then writes
  10. the resolved IP addresses to your system's hosts file as well.
  11.  
  12. This will result in your own persistent DNS cache, eliminating the need
  13. to resolve domains and CNAME entries multiple times.
  14.  
  15. *** WARNING ***
  16.  
  17. Do not use this script if you do not understand the potential pitfalls
  18. involved with writing entries to your hosts file.
  19.  
  20. Most obviously, DNS entries are subject to change.
  21. If a website changes its IP address and you keep the old address in your
  22. hosts file, you may become unable to view the website altogether.
  23.  
  24. Any changes made by this script can easily be undone by deleting the entry
  25. in your hosts file. But if you are not confident that you can identify and
  26. fix any such problems, you should not use this script.
  27.  
  28. """
  29.  
  30. import argparse
  31. import subprocess
  32. import os
  33. from os import access
  34. from os.path import exists
  35. from os.path import isfile
  36.  
  37. parser = argparse.ArgumentParser()
  38. parser.add_argument('-v', '--verbose', default=False, help='Verbose output')
  39. parser.add_argument('-i', '--input', default='/var/log/dnsmasq', help='Input file')
  40. parser.add_argument('-w', '--write', default=False, help='Write without prompting')
  41. parser.add_argument('-d', '--delete', default=False, help='Empty the input file after writing')
  42. args = parser.parse_args()
  43.  
  44. def canReadFile(f):
  45. return exists(f) and isfile(f) and access(f, os.R_OK)
  46.  
  47. def isIp4(ip):
  48. chunks = ip.split('.')
  49.  
  50. if len(chunks) != 4:
  51. return False
  52.  
  53. for c in chunks:
  54. if not c.isdigit():
  55. return False
  56.  
  57. return True
  58.  
  59. def isIp6(ip):
  60. chunks = ip.split(':')
  61. return len(chunks) >= 4
  62.  
  63. mapped = {}
  64. cached = {}
  65. toWrite = []
  66. alreadyDug = []
  67. hosts = "/etc/hosts"
  68.  
  69. if not canReadFile(args.input):
  70. print("Can't read input file: '%s'" % args.input)
  71. exit()
  72.  
  73. if not canReadFile(hosts):
  74. print("Can't read hosts file: '%s'" % hosts)
  75. exit()
  76.  
  77. if args.delete and not access(args.input, os.W_OK):
  78. print("Can't use 'delete' flag if input file isn't writable")
  79. exit()
  80.  
  81. #Read the hosts file and look for domain/ip pairs we already know about,
  82. #so we don't end up with duplicate entries.
  83. with open(hosts, 'r') as f:
  84. for line in f:
  85. if len(line) > 0:
  86. if not line[0] == '#':
  87. chunks = line.split()
  88. if len(chunks) == 2:
  89. ip = chunks[0]
  90. domain = chunks[1]
  91. if domain not in cached:
  92. cached[domain] = []
  93. if ip not in cached[domain]:
  94. cached[domain].append(ip)
  95. if args.verbose:
  96. print("Cached domain %s is %s" % (domain, ip))
  97.  
  98. with open(args.input, 'r') as f:
  99. for line in f:
  100. #eg. Jan 19 10:34:17 dnsmasq[1499]: cached sconsentit9.trafficmanager.net is <CNAME>
  101. #[0] = Jan
  102. #[1] = 19
  103. #[2] = 10:34:17
  104. #[3] = dnsmasq[1499]:
  105. #[4] = cached
  106. #[5] = sconsentit9.trafficmanager.net
  107. #[6] = is
  108. #[7] = <CNAME>
  109. chunks = line.split()
  110.  
  111. if len(chunks) == 8:
  112.  
  113. domain = chunks[5]
  114. ip = chunks[7]
  115. isCname = ip == '<CNAME>' and domain not in cached
  116.  
  117. #Hosts files only take IP addresses, not CNAME, so
  118. #convert CNAME into IP address by using "dig"
  119. #Don't bother digging domains which are already in hosts
  120. if isCname:
  121.  
  122. if domain in alreadyDug:
  123. continue
  124. alreadyDug.append(domain)
  125.  
  126. #only if not in hosts?
  127. bashCommand = "dig +short %s" % domain
  128. if args.verbose:
  129. print("Running %s" % bashCommand)
  130. process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
  131. output, error = process.communicate()
  132.  
  133. if error:
  134. if args.verbose:
  135. print("Error: %s" % error)
  136. else:
  137. for line in output.split():
  138. if isIp4(line) or isIp6(line):
  139. ip = line
  140. if args.verbose:
  141. print("Dig resolved %s to %s" % (domain, ip))
  142. break
  143.  
  144. #Only check cache hits and CNAME entries we've run through "dig".
  145. #This is so we get actually resolved domain entries, rather than
  146. #forwarded responses, replies from localhost, etc.
  147. #The downside is we're only reading from dnsmasq cache, and querying
  148. #CNAME entries. So if a domain can't/won't be cached by dnsmasq,
  149. #and it isn't a CNAME entry, it won't be picked up by this script.
  150. if chunks[4] == 'cached' or isCname:
  151. if isIp4(ip) or isIp6(ip):
  152. if domain not in mapped:
  153. mapped[domain] = []
  154. if ip not in mapped[domain]:
  155. mapped[domain].append(ip)
  156.  
  157. for domain, val in mapped.iteritems():
  158. if domain not in cached:
  159. cached[domain] = []
  160. for ip in val:
  161. if ip not in cached[domain]:
  162. toWrite.append("%s\t%s" % (ip, domain))
  163. if args.verbose:
  164. print("Add %s:%s to %s" % (domain, ip, hosts))
  165. elif args.verbose:
  166. print("Skipping %s:%s, already in %s" % (domain, ip, hosts))
  167.  
  168. #If there are any new domain/ip pairs not already found in the hosts file,
  169. #prompt the user to save these pairs.
  170. #That's assuming they have write permission for /etc/hosts, of course.
  171. if len(toWrite) > 0:
  172.  
  173. print("Found %d domain/ip pairs not present in %s" % (len(toWrite), hosts))
  174.  
  175. if access(hosts, os.W_OK):
  176.  
  177. confirm = 'Y' if args.write else 'N'
  178. if confirm == 'N':
  179. confirm = raw_input("Do you want to write these changes? [y/N]: ")
  180.  
  181. if confirm == 'y' or confirm == 'Y':
  182. with open("/etc/hosts", "a") as f:
  183. for line in toWrite:
  184. f.write("%s\n" % line)
  185. print("Changes written to file")
  186.  
  187. if args.delete:
  188. with open(args.input, "w") as f:
  189. f.write("")
  190. print("Input file emptied")
  191. else:
  192. print("Changes not saved")
  193. else:
  194. print("Run this program as root to write these changes")
  195.  
  196. else:
  197. print("No new domain/ip pairs found")
Add Comment
Please, Sign In to add comment