dns-sniper 0.0.1.pre2 → 0.0.1.pre7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +6 -7
- data/README.md +14 -22
- data/bin/dns-sniper +14 -17
- data/config.yml.example +9 -0
- data/dns-sniper.gemspec +2 -2
- data/lib/dns-sniper.rb +7 -2
- data/lib/dns-sniper/{formatter.rb → exporter.rb} +5 -3
- data/lib/dns-sniper/exporters.rb +30 -0
- data/lib/dns-sniper/{formatters/bind8.rb → exporters/bind8_exporter.rb} +2 -7
- data/lib/dns-sniper/exporters/dnsmasq_exporter.rb +13 -0
- data/lib/dns-sniper/exporters/hosts_exporter.rb +13 -0
- data/lib/dns-sniper/{formatters/netgear.rb → exporters/netgear_exporter.rb} +2 -7
- data/lib/dns-sniper/exporters/text_exporter.rb +9 -0
- data/lib/dns-sniper/exporters/unbound_exporter.rb +14 -0
- data/lib/dns-sniper/hostnames.rb +14 -137
- data/lib/dns-sniper/importer.rb +54 -0
- data/lib/dns-sniper/importers.rb +27 -0
- data/lib/dns-sniper/importers/configuration_importer.rb +38 -0
- data/lib/dns-sniper/importers/domains_importer.rb +22 -0
- data/lib/dns-sniper/importers/hosts_importer.rb +26 -0
- metadata +20 -14
- data/lib/dns-sniper/formatters.rb +0 -31
- data/lib/dns-sniper/formatters/dnsmasq.rb +0 -18
- data/lib/dns-sniper/formatters/hosts.rb +0 -18
- data/lib/dns-sniper/formatters/text.rb +0 -14
- data/lib/dns-sniper/formatters/unbound.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fdb713f65960730a1ac99906c65aeb79b30b69514eedab948ee20eb27bf30920
|
4
|
+
data.tar.gz: 28d04385a9759e9bada850e6c4dde26d7f3d983ddedd9d5782d31c6001cab19d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c02d1bb299780f04041d8899aeb72b7457ec8954184c95a0bae61731c1b36763f44055b41d4e8115c42bf0ccfdb68b407db3aef5ff71a3dc0e01d8dde0bcee99
|
7
|
+
data.tar.gz: fd3f08b34762d39e3610ad23810736a667e83f7b77459d8508e625f67007b0fa7adcd028091c65c4f05e28f39eacc6d805313e6733c9d8592f4657b5ade50e2b
|
data/Gemfile.lock
CHANGED
@@ -1,26 +1,25 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
sniper (0.0.1)
|
4
|
+
dns-sniper (0.0.1.pre6)
|
5
|
+
down (~> 5.1)
|
6
|
+
hosts_file (~> 1.0)
|
5
7
|
|
6
8
|
GEM
|
7
9
|
remote: https://rubygems.org/
|
8
10
|
specs:
|
9
11
|
addressable (2.7.0)
|
10
12
|
public_suffix (>= 2.0.2, < 5.0)
|
11
|
-
down (5.
|
13
|
+
down (5.2.2)
|
12
14
|
addressable (~> 2.5)
|
13
15
|
hosts_file (1.0.3)
|
14
|
-
public_suffix (4.0.
|
16
|
+
public_suffix (4.0.6)
|
15
17
|
|
16
18
|
PLATFORMS
|
17
19
|
ruby
|
18
20
|
|
19
21
|
DEPENDENCIES
|
20
|
-
|
21
|
-
down (~> 5.1)
|
22
|
-
hosts_file (~> 1.0)
|
23
|
-
sniper!
|
22
|
+
dns-sniper!
|
24
23
|
|
25
24
|
BUNDLED WITH
|
26
25
|
2.1.2
|
data/README.md
CHANGED
@@ -40,26 +40,19 @@ require 'dns-sniper'
|
|
40
40
|
|
41
41
|
hostnames = DNSSniper::Hostnames.new
|
42
42
|
|
43
|
-
#
|
44
|
-
hostnames.
|
45
|
-
hostnames.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
hostnames.
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# Manually remove domain name
|
57
|
-
hostnames.remove('favoritewebsite.com')
|
58
|
-
hostnames.remove(['favoritewebsite.com', 'otherfavoritewebsite.com'])
|
59
|
-
|
60
|
-
# Convert to configuration file
|
61
|
-
hostnames.to_format('dnsmasq')
|
62
|
-
hostnames.to_format('unbound')
|
43
|
+
# Manually add blacklisted or whitelisted domains
|
44
|
+
hostnames.blacklist += 'ads.yahoo.com'
|
45
|
+
hostnames.whitelist += 'favoritewebsite.com'
|
46
|
+
|
47
|
+
# Use an Importer to process external lists
|
48
|
+
hostnames.blacklist += DNSSniper::DomainsImporter.new('https://raw.githubusercontent.com/brodyhoskins/dns-blocklists/master/tracking.list').hostnames
|
49
|
+
hostnames.blacklist += DNSSniper::HostsImporter.new('https://pgl.yoyo.org/as/serverlist.php?hostformat=hosts;showintro=0;mimetype=plaintext').hostnames
|
50
|
+
|
51
|
+
# Blocklist is accessible as an Array
|
52
|
+
hostnames.blocklist
|
53
|
+
|
54
|
+
# Use an Exporter to convert to other formats
|
55
|
+
UnboundExporter.new(hostnames.blocklist).data
|
63
56
|
```
|
64
57
|
|
65
58
|
### From CLI
|
@@ -71,7 +64,6 @@ Using the CLI version makes it easy to update configuration formats automaticall
|
|
71
64
|
```bash
|
72
65
|
#!/usr/bin/env bash
|
73
66
|
|
74
|
-
/path/to/dns-sniper
|
75
|
-
|
67
|
+
/path/to/dns-sniper --conf ~/.config/dns-sniper/dns-sniper.yml --output unbound > /etc/unbound/unbound.conf.d/blocklist.conf
|
76
68
|
service unbound reload
|
77
69
|
```
|
data/bin/dns-sniper
CHANGED
@@ -5,19 +5,16 @@ require 'optparse'
|
|
5
5
|
require 'set'
|
6
6
|
require 'dns-sniper'
|
7
7
|
|
8
|
-
VALID_FORMATS = DNSSniper::
|
9
|
-
Options = Struct.new(:
|
8
|
+
VALID_FORMATS = DNSSniper::Exporters.all.inject([]) { |arr, f| arr << f.name.to_s.sub('DNSSniper::', '').sub('Exporter', '').downcase }
|
9
|
+
Options = Struct.new(:conf_path, :format)
|
10
10
|
|
11
11
|
class Parser
|
12
12
|
def self.parse(options)
|
13
13
|
args = Options.new('world')
|
14
14
|
opt_parser = OptionParser.new do |opts|
|
15
15
|
opts.banner = "Usage: #{File.basename(__FILE__)} -f PATH -o FORMAT [options]\n#{File.basename(__FILE__)} combines online DNS blacklists and combines them into the desired configuration FORMAT.\n\n"
|
16
|
-
opts.on('-
|
17
|
-
args.
|
18
|
-
end
|
19
|
-
opts.on('-w', '--whitelist=path', 'Path to file with line-separated list of whitelisted hostnames') do |path|
|
20
|
-
args.whitelisted_hosts_file = path
|
16
|
+
opts.on('-c', '--conf=PATH', 'PATH to YAML configuration file') do |path|
|
17
|
+
args.conf_path = path
|
21
18
|
end
|
22
19
|
opts.on('-o', '--output=FORMAT', "FORMAT to output — one of #{VALID_FORMATS.join(', ')}") do |format|
|
23
20
|
args.format = format
|
@@ -34,20 +31,20 @@ end
|
|
34
31
|
|
35
32
|
options = Parser.parse ARGV
|
36
33
|
|
37
|
-
unless
|
38
|
-
warn
|
34
|
+
unless File.exist?(options[:conf_path])
|
35
|
+
warn "Error: Unable to access ”#{options[:conf_path]}”"
|
39
36
|
exit(-1)
|
40
37
|
end
|
41
38
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
if options[:whitelisted_hosts_file] && !File.exist?(options[:whitelisted_hosts_file])
|
48
|
-
warn "Error: Unable to access ”#{options[:whitelisted_hosts_file]}”"
|
39
|
+
exporter = DNSSniper.const_get("#{options[:format].to_s.split('_').map(&:capitalize).join}Exporter")
|
40
|
+
if !exporter
|
41
|
+
warn "Error: Invalid format #{options[:format]}"
|
49
42
|
exit(-1)
|
50
43
|
end
|
51
44
|
|
52
45
|
hostnames = DNSSniper::Hostnames.new
|
53
|
-
|
46
|
+
hostnames.blacklist += DNSSniper::ConfigurationImporter.new(options[:conf_path], list: :reject).hostnames
|
47
|
+
hostnames.whitelist += DNSSniper::ConfigurationImporter.new(options[:conf_path], list: :allow).hostnames
|
48
|
+
|
49
|
+
exporter = exporter.new(hostnames.blocklist)
|
50
|
+
puts exporter.data
|
data/config.yml.example
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
:sources:
|
2
|
+
:allow:
|
3
|
+
- :importer: :domains
|
4
|
+
:uri: https://example.com/whitelisted.domains.list
|
5
|
+
:reject:
|
6
|
+
- :importer: :domains
|
7
|
+
:uri: https://raw.githubusercontent.com/brodyhoskins/dns-blocklists/master/tracking.list
|
8
|
+
- :importer: :hosts
|
9
|
+
:uri: https://pgl.yoyo.org/as/serverlist.php?hostformat=hosts;showintro=0;mimetype=plaintext
|
data/dns-sniper.gemspec
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = 'dns-sniper'
|
5
5
|
spec.license = 'MIT'
|
6
|
-
spec.version = '0.0.1.
|
7
|
-
spec.date = '
|
6
|
+
spec.version = '0.0.1.pre7'
|
7
|
+
spec.date = '2021-07-26'
|
8
8
|
|
9
9
|
spec.authors = ['Brody Hoskins']
|
10
10
|
spec.email = ['brody@brody.digital']
|
data/lib/dns-sniper.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'resolv'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require 'dns-sniper/exporter'
|
7
|
+
require 'dns-sniper/exporters'
|
3
8
|
require 'dns-sniper/hostnames'
|
4
|
-
require 'dns-sniper/
|
5
|
-
require 'dns-sniper/
|
9
|
+
require 'dns-sniper/importer'
|
10
|
+
require 'dns-sniper/importers'
|
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DNSSniper
|
4
|
-
class
|
4
|
+
class Exporter
|
5
|
+
attr_accessor :data, :hostnames
|
6
|
+
|
5
7
|
def initialize(hostnames, options = {})
|
6
8
|
@hostnames = hostnames
|
7
|
-
@
|
9
|
+
@data = output(options)
|
8
10
|
end
|
9
11
|
|
10
|
-
def output
|
12
|
+
def output(*)
|
11
13
|
raise NotImplementedError, "Error: #output isn’t supported by #{self.class.name}"
|
12
14
|
end
|
13
15
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DNSSniper
|
4
|
+
module Exporters
|
5
|
+
module_function
|
6
|
+
|
7
|
+
attr_reader :registered
|
8
|
+
@registered = []
|
9
|
+
|
10
|
+
def register(class_name, autoload_require)
|
11
|
+
DNSSniper.autoload(class_name, autoload_require)
|
12
|
+
@registered << class_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def all
|
16
|
+
@registered.map { |name| DNSSniper.const_get(name) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def find(name)
|
20
|
+
all.find { |c| c.name.downcase == name.to_s.downcase } or raise NameError, "Unknown exporter \"#{name}\""
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
DNSSniper::Exporters.register :Bind8Exporter, 'dns-sniper/exporters/bind8_exporter'
|
26
|
+
DNSSniper::Exporters.register :DnsmasqExporter, 'dns-sniper/exporters/dnsmasq_exporter'
|
27
|
+
DNSSniper::Exporters.register :HostsExporter, 'dns-sniper/exporters/hosts_exporter'
|
28
|
+
DNSSniper::Exporters.register :NetgearExporter, 'dns-sniper/exporters/netgear_exporter'
|
29
|
+
DNSSniper::Exporters.register :TextExporter, 'dns-sniper/exporters/text_exporter'
|
30
|
+
DNSSniper::Exporters.register :UnboundExporter, 'dns-sniper/exporters/unbound_exporter'
|
@@ -1,18 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DNSSniper
|
4
|
-
class
|
5
|
-
def initialize(hostnames, options = {})
|
6
|
-
@hostnames = hostnames
|
7
|
-
@options = options
|
8
|
-
end
|
9
|
-
|
4
|
+
class Bind8Exporter < Exporter
|
10
5
|
def output(options = {})
|
11
6
|
raise ArgumentError, 'zone_file is required' unless defined?(options[:zone_file])
|
12
7
|
|
13
8
|
str = ''.dup
|
14
9
|
@hostnames.each do |hostname|
|
15
|
-
str << "zone \"#{hostname}\" { type master; notify no; file \"#{options[:zone_file]}\"; };#{
|
10
|
+
str << "zone \"#{hostname}\" { type master; notify no; file \"#{options[:zone_file]}\"; };#{$/}"
|
16
11
|
end
|
17
12
|
str
|
18
13
|
end
|
@@ -1,13 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DNSSniper
|
4
|
-
class
|
5
|
-
def
|
6
|
-
@hostnames = hostnames
|
7
|
-
@options = options
|
8
|
-
end
|
9
|
-
|
10
|
-
def output(_options = {})
|
4
|
+
class NetgearExporter < Exporter
|
5
|
+
def output(*)
|
11
6
|
str = ''.dup
|
12
7
|
@hostnames.each_with_index do |hostname, i|
|
13
8
|
str << "[517003_e]: #{i + 1}) #{hostname}\n"
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DNSSniper
|
4
|
+
class UnboundExporter < Exporter
|
5
|
+
def output(*)
|
6
|
+
str = ''.dup
|
7
|
+
str << "server:#{$/}"
|
8
|
+
@hostnames.each do |hostname|
|
9
|
+
str << " local-zone: \"#{hostname}\" static#{$/}"
|
10
|
+
end
|
11
|
+
str
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/dns-sniper/hostnames.rb
CHANGED
@@ -7,151 +7,28 @@ require 'resolv'
|
|
7
7
|
|
8
8
|
module DNSSniper
|
9
9
|
class Hostnames
|
10
|
-
|
11
|
-
|
12
|
-
self
|
13
|
-
end
|
14
|
-
|
15
|
-
def add(hostnames)
|
16
|
-
hostnames = clean(hostnames.class == String ? [hostnames] : hostnames)
|
17
|
-
@hostnames += hostnames unless hostnames.empty?
|
18
|
-
self
|
19
|
-
end
|
20
|
-
|
21
|
-
def add_from(paths_or_urls)
|
22
|
-
return self unless paths_or_urls
|
23
|
-
|
24
|
-
paths_or_urls = [paths_or_urls] if paths_or_urls.class == String
|
25
|
-
|
26
|
-
paths_or_urls.each do |path_or_url|
|
27
|
-
path_or_url = path_or_url.strip
|
28
|
-
|
29
|
-
if File.exist?(path_or_url)
|
30
|
-
contents = File.open(path_or_url).readlines
|
31
|
-
else
|
32
|
-
begin
|
33
|
-
down = Down.download(path_or_url)
|
34
|
-
path_or_url = down.path
|
35
|
-
contents = down.readlines
|
36
|
-
rescue Down::NotFound
|
37
|
-
warn "\"#{path_or_url}\" does not exist"
|
38
|
-
return self
|
39
|
-
rescue Down::ResponseError
|
40
|
-
warn "\"#{path_or_url}\": No data from server"
|
41
|
-
return self
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
case syntax(path_or_url, contents)
|
46
|
-
when nil
|
47
|
-
warn "Error: Syntax: Syntax of \"#{path_or_url}\" not recognized, ignored"
|
48
|
-
return self
|
49
|
-
when 'hosts'
|
50
|
-
add from_hosts_file(path_or_url)
|
51
|
-
when 'hostnames'
|
52
|
-
add from_hostnames_file(contents)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
self
|
57
|
-
end
|
58
|
-
|
59
|
-
def remove(hostnames)
|
60
|
-
hostnames = clean(hostnames.class == String ? [hostnames] : hostnames)
|
61
|
-
@hostnames -= hostnames unless hostnames.empty?
|
62
|
-
self
|
63
|
-
end
|
64
|
-
|
65
|
-
def remove_from(paths_or_urls)
|
66
|
-
return self unless paths_or_urls
|
67
|
-
|
68
|
-
paths_or_urls = [paths_or_urls] if paths_or_urls.class == String
|
69
|
-
|
70
|
-
paths_or_urls.each do |path_or_url|
|
71
|
-
path_or_url = path_or_url.strip
|
72
|
-
|
73
|
-
if File.exist?(path_or_url)
|
74
|
-
contents = File.open(path_or_url).readlines
|
75
|
-
else
|
76
|
-
begin
|
77
|
-
down = Down.download(path_or_url)
|
78
|
-
path_or_url = down.path
|
79
|
-
contents = down.readlines
|
80
|
-
rescue Down::NotFound
|
81
|
-
warn "\"#{path_or_url}\" does not exist"
|
82
|
-
return self
|
83
|
-
rescue Down::ResponseError
|
84
|
-
warn "\"#{path_or_url}\": No data from server"
|
85
|
-
return self
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
case syntax(path_or_url, contents)
|
90
|
-
when nil
|
91
|
-
warn "Error: Syntax: Syntax of \"#{path_or_url}\" not recognized, ignored"
|
92
|
-
return self
|
93
|
-
when 'hosts'
|
94
|
-
remove from_hosts_file(path_or_url)
|
95
|
-
when 'hostnames'
|
96
|
-
remove from_hostnames_file(contents)
|
97
|
-
end
|
98
|
-
end
|
10
|
+
attr_accessor :blacklist
|
11
|
+
attr_accessor :whitelist
|
99
12
|
|
13
|
+
def initialize
|
14
|
+
@blacklist = []
|
15
|
+
@whitelist = []
|
100
16
|
self
|
101
17
|
end
|
102
18
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
106
|
-
klass = DNSSniper.const_get(format)
|
107
|
-
klass.new(@hostnames.to_a).output(options)
|
108
|
-
rescue NameError
|
109
|
-
false
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def to_a
|
114
|
-
@hostnames.to_a
|
115
|
-
end
|
116
|
-
|
117
|
-
private
|
19
|
+
def blocklist
|
20
|
+
blacklist = @blacklist
|
21
|
+
whitelist = @whitelist
|
118
22
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
hostname_top_domain = "#{hostname.split('.')[-2]}.#{hostname.split('.')[-1]}"
|
125
|
-
|
126
|
-
if !hostname.include?('#') && !['broadcasthost', 'localhost', ''].include?(hostname) && !@hostnames.include?(hostname_top_domain)
|
127
|
-
cleaned_hostnames << hostname
|
128
|
-
end
|
129
|
-
end
|
130
|
-
cleaned_hostnames
|
131
|
-
end
|
132
|
-
|
133
|
-
def syntax(path_or_url, contents)
|
134
|
-
contents.each do |line|
|
135
|
-
next if line.include?('#')
|
136
|
-
|
137
|
-
line = line.downcase
|
138
|
-
|
139
|
-
if line.strip.split(/\s/).first =~ Regexp.union([Resolv::IPv4::Regex, Resolv::IPv6::Regex])
|
140
|
-
return 'hosts'
|
141
|
-
elsif line.include?('.') && (!line.include? 'http') && path_or_url.end_with?('.list')
|
142
|
-
return 'hostnames'
|
23
|
+
whitelist.each do |domain|
|
24
|
+
domain_parts = domain.split('.')
|
25
|
+
domain_parts.count.times do |count|
|
26
|
+
next if count == 1
|
27
|
+
whitelist += [domain_parts[count - 1, domain_parts.length].join('.')]
|
143
28
|
end
|
144
29
|
end
|
145
|
-
nil
|
146
|
-
end
|
147
|
-
|
148
|
-
def from_hosts_file(path_or_url)
|
149
|
-
# TODO: Remove downloading file twice
|
150
|
-
HostsFile.load(path_or_url).map(&:name)
|
151
|
-
end
|
152
30
|
|
153
|
-
|
154
|
-
contents.each { |line| add(line) }
|
31
|
+
blacklist - whitelist
|
155
32
|
end
|
156
33
|
end
|
157
34
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DNSSniper
|
4
|
+
class Importer
|
5
|
+
attr_accessor :uri, :hostnames
|
6
|
+
|
7
|
+
def initialize(uri, *)
|
8
|
+
@uri = uri
|
9
|
+
@hostnames = File.exist?(uri) ? import_file(uri) : import_uri(uri)
|
10
|
+
end
|
11
|
+
|
12
|
+
def import_file(*)
|
13
|
+
raise NotImplementedError, "#{self.class.name}: #import_file not supported"
|
14
|
+
end
|
15
|
+
|
16
|
+
def import_uri(*)
|
17
|
+
raise NotImplementedError, "#{self.class.name}: #import_uri not supported"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Helper methods
|
21
|
+
|
22
|
+
def clean(domain)
|
23
|
+
domain = domain.split('#')[0] if domain[0] != '#' && domain.include?('#')
|
24
|
+
domain = domain.split(':')[0] if domain.include?(':') && !ip_addr?(domain)
|
25
|
+
domain = domain.sub('www.', '') if domain.start_with?('www.') && domain.scan('www.').count == 1
|
26
|
+
|
27
|
+
domain.chomp.gsub(/\s+/, '').downcase
|
28
|
+
end
|
29
|
+
|
30
|
+
def rejector(domain)
|
31
|
+
!domain?(domain)
|
32
|
+
end
|
33
|
+
|
34
|
+
def domain?(domain)
|
35
|
+
return false if domain == ''
|
36
|
+
return false if domain.gsub('#', '').gsub(/\s+/, '').empty?
|
37
|
+
return false if domain[0] == '#'
|
38
|
+
return false if domain.include?(':')
|
39
|
+
return false if domain.include?('?')
|
40
|
+
return false if ip_addr?(domain)
|
41
|
+
return false unless /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/.match?(domain)
|
42
|
+
return false unless /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}$/.match?(domain)
|
43
|
+
|
44
|
+
begin
|
45
|
+
return false if URI.parse(domain).is_a?(URI::HTTP)
|
46
|
+
rescue URI::InvalidURIError; end
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def ip_addr?(domain)
|
51
|
+
domain =~ Regexp.union([Resolv::IPv4::Regex, Resolv::IPv6::Regex])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DNSSniper
|
4
|
+
module Importers
|
5
|
+
module_function
|
6
|
+
|
7
|
+
attr_reader :registered
|
8
|
+
@registered = []
|
9
|
+
|
10
|
+
def register(class_name, autoload_require)
|
11
|
+
DNSSniper.autoload(class_name, autoload_require)
|
12
|
+
@registered << class_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def all
|
16
|
+
@registered.map { |name| DNSSniper.const_get(name) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def find(name)
|
20
|
+
all.find { |c| c.name.downcase == name.to_s.downcase } or raise NameError, "Unknown Importer \"#{name}\""
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
DNSSniper::Importers.register :ConfigurationImporter, 'dns-sniper/importers/configuration_importer'
|
26
|
+
DNSSniper::Importers.register :DomainsImporter, 'dns-sniper/importers/domains_importer'
|
27
|
+
DNSSniper::Importers.register :HostsImporter, 'dns-sniper/importers/hosts_importer'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DNSSniper
|
4
|
+
class ConfigurationImporter < Importer
|
5
|
+
def initialize(uri, list:)
|
6
|
+
@uri = uri
|
7
|
+
@hostnames = File.exist?(uri) ? import_file(uri, list: list) : import_uri(uri, list: list)
|
8
|
+
end
|
9
|
+
|
10
|
+
def import_file(path, list:)
|
11
|
+
raise ArgumentError, "#{self.class.name}: #from_path requies list to be defined" unless list
|
12
|
+
return [].to_set unless File.exist?(path)
|
13
|
+
|
14
|
+
yaml = YAML.safe_load(File.read(path), permitted_classes: [Symbol])
|
15
|
+
return [].to_set unless yaml.dig(:sources)&.dig(list.to_sym)
|
16
|
+
|
17
|
+
hostnames = [].to_set
|
18
|
+
yaml.dig(:sources).dig(list.to_sym).each do |source|
|
19
|
+
return [].to_set unless source.dig(:importer)
|
20
|
+
return [].to_set unless source.dig(:uri)
|
21
|
+
|
22
|
+
importer = DNSSniper.const_get("#{source.dig(:importer).to_s.split('_').map(&:capitalize).join}Importer")
|
23
|
+
if !importer
|
24
|
+
next
|
25
|
+
else
|
26
|
+
importer = importer.new(source.dig(:uri))
|
27
|
+
hostnames += importer.hostnames
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
hostnames
|
32
|
+
end
|
33
|
+
|
34
|
+
def import_uri(_uri, list:)
|
35
|
+
raise NotImplementedError, "#{self.class.name}: #from_uri not supported"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DNSSniper
|
4
|
+
class DomainsImporter < Importer
|
5
|
+
def import_file(path)
|
6
|
+
return [].to_set unless File.exist?(path)
|
7
|
+
|
8
|
+
File.open(path).readlines(chomp: true).map { |hostname| clean(hostname) }.reject { |hostname| rejector(hostname) }.to_set
|
9
|
+
end
|
10
|
+
|
11
|
+
def import_uri(uri)
|
12
|
+
begin
|
13
|
+
down = Down.download(uri)
|
14
|
+
return down.readlines(chomp: true).map { |hostname| clean(hostname) }.reject { |hostname| rejector(hostname) }.to_set
|
15
|
+
rescue Down::InvalidUrl => e
|
16
|
+
warn "#{self.class.name}: #{e}"
|
17
|
+
end
|
18
|
+
|
19
|
+
[].to_set
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DNSSniper
|
4
|
+
class HostsImporter < Importer
|
5
|
+
def import_file(path, *)
|
6
|
+
return [].to_set unless File.exist?(path)
|
7
|
+
|
8
|
+
HostsFile.load(path).map(&:name).map { |hostname| clean(hostname) }.reject { |hostname| rejector(hostname) }.to_set
|
9
|
+
end
|
10
|
+
|
11
|
+
def import_uri(uri, *)
|
12
|
+
begin
|
13
|
+
down = Down.download(uri)
|
14
|
+
path = down.path
|
15
|
+
rescue Down::InvalidUrl => e
|
16
|
+
warn "#{self.class.name}: #{e}"
|
17
|
+
end
|
18
|
+
|
19
|
+
if path
|
20
|
+
return HostsFile.load(path).map(&:name).map { |hostname| clean(hostname) }.reject { |hostname| rejector(hostname) }.to_set
|
21
|
+
end
|
22
|
+
|
23
|
+
[].to_set
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dns-sniper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1.
|
4
|
+
version: 0.0.1.pre7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brody Hoskins
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: down
|
@@ -54,24 +54,30 @@ files:
|
|
54
54
|
- README.md
|
55
55
|
- Rakefile
|
56
56
|
- bin/dns-sniper
|
57
|
+
- config.yml.example
|
57
58
|
- dns-sniper.gemspec
|
58
59
|
- lib/dns-sniper.rb
|
59
|
-
- lib/dns-sniper/
|
60
|
-
- lib/dns-sniper/
|
61
|
-
- lib/dns-sniper/
|
62
|
-
- lib/dns-sniper/
|
63
|
-
- lib/dns-sniper/
|
64
|
-
- lib/dns-sniper/
|
65
|
-
- lib/dns-sniper/
|
66
|
-
- lib/dns-sniper/
|
60
|
+
- lib/dns-sniper/exporter.rb
|
61
|
+
- lib/dns-sniper/exporters.rb
|
62
|
+
- lib/dns-sniper/exporters/bind8_exporter.rb
|
63
|
+
- lib/dns-sniper/exporters/dnsmasq_exporter.rb
|
64
|
+
- lib/dns-sniper/exporters/hosts_exporter.rb
|
65
|
+
- lib/dns-sniper/exporters/netgear_exporter.rb
|
66
|
+
- lib/dns-sniper/exporters/text_exporter.rb
|
67
|
+
- lib/dns-sniper/exporters/unbound_exporter.rb
|
67
68
|
- lib/dns-sniper/hostnames.rb
|
69
|
+
- lib/dns-sniper/importer.rb
|
70
|
+
- lib/dns-sniper/importers.rb
|
71
|
+
- lib/dns-sniper/importers/configuration_importer.rb
|
72
|
+
- lib/dns-sniper/importers/domains_importer.rb
|
73
|
+
- lib/dns-sniper/importers/hosts_importer.rb
|
68
74
|
homepage: https://github.com/brodyhoskins/dns-sniper
|
69
75
|
licenses:
|
70
76
|
- MIT
|
71
77
|
metadata:
|
72
78
|
homepage_uri: https://github.com/brodyhoskins/dns-sniper
|
73
79
|
source_code_uri: https://github.com/brodyhoskins/dns-sniper
|
74
|
-
post_install_message:
|
80
|
+
post_install_message:
|
75
81
|
rdoc_options: []
|
76
82
|
require_paths:
|
77
83
|
- lib
|
@@ -86,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
92
|
- !ruby/object:Gem::Version
|
87
93
|
version: 1.3.1
|
88
94
|
requirements: []
|
89
|
-
rubygems_version: 3.
|
90
|
-
signing_key:
|
95
|
+
rubygems_version: 3.2.15
|
96
|
+
signing_key:
|
91
97
|
specification_version: 4
|
92
98
|
summary: Combine DNS blacklists into desired configuration format
|
93
99
|
test_files: []
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module DNSSniper
|
4
|
-
module Formatters
|
5
|
-
extend self
|
6
|
-
# module_function
|
7
|
-
|
8
|
-
attr_reader :registered
|
9
|
-
@registered = []
|
10
|
-
|
11
|
-
def register(class_name, autoload_require)
|
12
|
-
DNSSniper.autoload(class_name, autoload_require)
|
13
|
-
@registered << class_name
|
14
|
-
end
|
15
|
-
|
16
|
-
def all
|
17
|
-
@registered.map { |name| DNSSniper.const_get(name) }
|
18
|
-
end
|
19
|
-
|
20
|
-
def find(name)
|
21
|
-
all.find { |c| c.name.downcase == name.to_s.downcase } or raise NameError, "Unknown formatter \"#{name}\""
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
DNSSniper::Formatters.register :Bind8, 'dns-sniper/formatters/bind8'
|
27
|
-
DNSSniper::Formatters.register :Dnsmasq, 'dns-sniper/formatters/dnsmasq'
|
28
|
-
DNSSniper::Formatters.register :Hosts, 'dns-sniper/formatters/hosts'
|
29
|
-
DNSSniper::Formatters.register :Netgear, 'dns-sniper/formatters/netgear'
|
30
|
-
DNSSniper::Formatters.register :Text, 'dns-sniper/formatters/text'
|
31
|
-
DNSSniper::Formatters.register :Unbound, 'dns-sniper/formatters/unbound'
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module DNSSniper
|
4
|
-
class Dnsmasq < Formatter
|
5
|
-
def initialize(hostnames, options = {})
|
6
|
-
@hostnames = hostnames
|
7
|
-
@options = options
|
8
|
-
end
|
9
|
-
|
10
|
-
def output(_options = {})
|
11
|
-
str = ''.dup
|
12
|
-
@hostnames.each do |hostname|
|
13
|
-
str << "server=/#{hostname}/#{$INPUT_RECORD_SEPARATOR}"
|
14
|
-
end
|
15
|
-
str
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module DNSSniper
|
4
|
-
class Hosts < Formatter
|
5
|
-
def initialize(hostnames, options = {})
|
6
|
-
@hostnames = hostnames
|
7
|
-
@options = options
|
8
|
-
end
|
9
|
-
|
10
|
-
def output(_options = {})
|
11
|
-
str = ''.dup
|
12
|
-
@hostnames.each do |hostname|
|
13
|
-
str << "127.0.0.1\t#{hostname}#{$INPUT_RECORD_SEPARATOR}"
|
14
|
-
end
|
15
|
-
str
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module DNSSniper
|
4
|
-
class Text < Formatter
|
5
|
-
def initialize(hostnames, options = {})
|
6
|
-
@hostnames = hostnames
|
7
|
-
@options = options
|
8
|
-
end
|
9
|
-
|
10
|
-
def output(_options = {})
|
11
|
-
@hostnames.to_a.join($INPUT_RECORD_SEPARATOR)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module DNSSniper
|
4
|
-
class Unbound < Formatter
|
5
|
-
def initialize(hostnames, options = {})
|
6
|
-
@hostnames = hostnames
|
7
|
-
@options = options
|
8
|
-
end
|
9
|
-
|
10
|
-
def output(_options = {})
|
11
|
-
str = ''.dup
|
12
|
-
str << "server:#{$INPUT_RECORD_SEPARATOR}"
|
13
|
-
@hostnames.each do |hostname|
|
14
|
-
str << " local-zone: \"#{hostname}\" static#{$INPUT_RECORD_SEPARATOR}"
|
15
|
-
end
|
16
|
-
str
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|