Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python3
- """
- This script will run on a linux system which uses dnsmasq.
- It extracts cached DNS entries from dnsmasq's logs and writes them to
- your system's hosts file.
- It also looks for CNAME entries and attempts to "dig" them, then writes
- the resolved IP addresses to your system's hosts file as well.
- This will result in your own persistent DNS cache, eliminating the need
- to resolve domains and CNAME entries multiple times.
- *** WARNING ***
- Do not use this script if you do not understand the potential pitfalls
- involved with writing entries to your hosts file.
- Most obviously, DNS entries are subject to change.
- If a website changes its IP address and you keep the old address in your
- hosts file, you may become unable to view the website altogether.
- Any changes made by this script can easily be undone by deleting the entry
- in your hosts file. But if you are not confident that you can identify and
- fix any such problems, you should not use this script.
- """
- import argparse
- import subprocess
- import os
- from os import access
- from os.path import exists
- from os.path import isfile
- parser = argparse.ArgumentParser()
- parser.add_argument('-v', '--verbose', default=False, help='Verbose output')
- parser.add_argument('-i', '--input', default='/var/log/dnsmasq', help='Input file')
- parser.add_argument('-w', '--write', default=False, help='Write without prompting')
- parser.add_argument('-d', '--delete', default=False, help='Empty the input file after writing')
- args = parser.parse_args()
- def canReadFile(f):
- return exists(f) and isfile(f) and access(f, os.R_OK)
- def isIp4(ip):
- chunks = ip.split('.')
- if len(chunks) != 4:
- return False
- for c in chunks:
- if not c.isdigit():
- return False
- return True
- def isIp6(ip):
- chunks = ip.split(':')
- return len(chunks) >= 4
- mapped = {}
- cached = {}
- toWrite = []
- alreadyDug = []
- hosts = "/etc/hosts"
- if not canReadFile(args.input):
- print("Can't read input file: '%s'" % args.input)
- exit()
- if not canReadFile(hosts):
- print("Can't read hosts file: '%s'" % hosts)
- exit()
- if args.delete and not access(args.input, os.W_OK):
- print("Can't use 'delete' flag if input file isn't writable")
- exit()
- #Read the hosts file and look for domain/ip pairs we already know about,
- #so we don't end up with duplicate entries.
- with open(hosts, 'r') as f:
- for line in f:
- if len(line) > 0:
- if not line[0] == '#':
- chunks = line.split()
- if len(chunks) == 2:
- ip = chunks[0]
- domain = chunks[1]
- if domain not in cached:
- cached[domain] = []
- if ip not in cached[domain]:
- cached[domain].append(ip)
- if args.verbose:
- print("Cached domain %s is %s" % (domain, ip))
- with open(args.input, 'r') as f:
- for line in f:
- #eg. Jan 19 10:34:17 dnsmasq[1499]: cached sconsentit9.trafficmanager.net is <CNAME>
- #[0] = Jan
- #[1] = 19
- #[2] = 10:34:17
- #[3] = dnsmasq[1499]:
- #[4] = cached
- #[5] = sconsentit9.trafficmanager.net
- #[6] = is
- #[7] = <CNAME>
- chunks = line.split()
- if len(chunks) == 8:
- domain = chunks[5]
- ip = chunks[7]
- isCname = ip == '<CNAME>' and domain not in cached
- #Hosts files only take IP addresses, not CNAME, so
- #convert CNAME into IP address by using "dig"
- #Don't bother digging domains which are already in hosts
- if isCname:
- if domain in alreadyDug:
- continue
- alreadyDug.append(domain)
- #only if not in hosts?
- bashCommand = "dig +short %s" % domain
- if args.verbose:
- print("Running %s" % bashCommand)
- process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
- output, error = process.communicate()
- if error:
- if args.verbose:
- print("Error: %s" % error)
- else:
- for line in output.split():
- if isIp4(line) or isIp6(line):
- ip = line
- if args.verbose:
- print("Dig resolved %s to %s" % (domain, ip))
- break
- #Only check cache hits and CNAME entries we've run through "dig".
- #This is so we get actually resolved domain entries, rather than
- #forwarded responses, replies from localhost, etc.
- #The downside is we're only reading from dnsmasq cache, and querying
- #CNAME entries. So if a domain can't/won't be cached by dnsmasq,
- #and it isn't a CNAME entry, it won't be picked up by this script.
- if chunks[4] == 'cached' or isCname:
- if isIp4(ip) or isIp6(ip):
- if domain not in mapped:
- mapped[domain] = []
- if ip not in mapped[domain]:
- mapped[domain].append(ip)
- for domain, val in mapped.iteritems():
- if domain not in cached:
- cached[domain] = []
- for ip in val:
- if ip not in cached[domain]:
- toWrite.append("%s\t%s" % (ip, domain))
- if args.verbose:
- print("Add %s:%s to %s" % (domain, ip, hosts))
- elif args.verbose:
- print("Skipping %s:%s, already in %s" % (domain, ip, hosts))
- #If there are any new domain/ip pairs not already found in the hosts file,
- #prompt the user to save these pairs.
- #That's assuming they have write permission for /etc/hosts, of course.
- if len(toWrite) > 0:
- print("Found %d domain/ip pairs not present in %s" % (len(toWrite), hosts))
- if access(hosts, os.W_OK):
- confirm = 'Y' if args.write else 'N'
- if confirm == 'N':
- confirm = raw_input("Do you want to write these changes? [y/N]: ")
- if confirm == 'y' or confirm == 'Y':
- with open("/etc/hosts", "a") as f:
- for line in toWrite:
- f.write("%s\n" % line)
- print("Changes written to file")
- if args.delete:
- with open(args.input, "w") as f:
- f.write("")
- print("Input file emptied")
- else:
- print("Changes not saved")
- else:
- print("Run this program as root to write these changes")
- else:
- print("No new domain/ip pairs found")
Add Comment
Please, Sign In to add comment