1 # -*- encoding: utf-8 -*-
5 desc 'Dump and merge dnsmasq host cache'
11 pid = %x(ps axo pid,ucomm).lines.find { |l| l.strip =~ /\bdnsmasq\z/ }.to_i
14 # Create a table of hostnames to addresses, eliding any aliases of localhost
15 parse_hosts = lambda do |buf|
16 hosts, locals = {}, Set.new(%w[127.0.0.1 ::1 0.0.0.0 ::])
18 addr, host = l.chomp.split "\t"
19 next if locals.include? addr
20 hosts[host] ||= Set.new
26 # Record current cache
27 cache = File.expand_path 'hosts.cache'
28 hosts = File.exists?(cache) ? parse_hosts.call(File.read cache) : {}
30 # Send USR1 and wait for dnsmasq to close the cache file
31 user = Etc.getpwnam 'dnsmasq'
33 File.chown user.uid, user.gid, cache
34 Process.kill 'SIGUSR1', pid
35 sleep 0.1 until %x(lsof -p #{pid} 2>/dev/null).lines.grep(Regexp.new cache).empty?
37 # Merge old cache records with new ones so we don't lose any records
38 hosts.merge! parse_hosts.call(File.read cache)
39 File.open cache, 'w' do |f|
40 f.puts hosts.sort.reduce('') { |buf, (host, addrs)|
41 buf << addrs.sort.map { |a| "#{a}\t#{host}\n" }.join
46 def blacklisted? blacklist, host
48 blacklist.each do |bhost|
51 when 1 then return true if b0 === h0
52 when 2 then return true if b0 === h0 and b1 === h1
53 else return true if b0 === h0 and b1 === h1 and b2 === h2
63 desc 'Create a dnsmasq whitelist conf file from hosts.cache'
67 # Host to direct whitelisted DNS queries
68 nameserver = '192.168.1.1'
70 # Domains that should never enter the whitelist.
72 # Each entry must be an array of three case matchers (i.e. match is done
73 # with ===) that correspond to the top three levels of a domain name in
76 # ['com', 'google', /\A(www|images|video)\z/],
80 # Cached hosts as arrays of hierarchical domains
81 hosts = File.readlines('hosts.cache').map do |l|
82 l.strip.split.last.split('.').reverse
85 oldlist = if File.exists? 'whitelist.conf'
86 File.readlines('whitelist.conf').map { |l| l[%r{/(.*)/}, 1] }
91 # Reduce cached hosts to a set of secondary domains. If the secondary domain
92 # is blacklisted and the tertiary domain is not, then the tertiary domain is
94 whitelist = hosts.reduce Set.new(oldlist) do |s, host|
97 warn "Rejecting #{name host}"
99 # Try the secondary first
100 elsif blacklisted? blacklist, host.take(2)
101 # Then try the tertiary (when in doubt, use brute force)
102 if blacklisted? blacklist, host.take(3)
103 warn "Rejecting #{name host}"
106 s << name(host.take 3)
109 s << name(host.take 2)
113 File.open 'whitelist.conf', 'w' do |f|
114 f.puts whitelist.map { |host| "server=/#{host}/#{nameserver}" }.sort