dns-sniper 0.0.1pre → 0.0.1.pre6
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 +3 -1
- data/MIT-LICENSE +20 -0
- data/README.md +77 -0
- data/Rakefile +5 -3
- data/bin/dns-sniper +22 -24
- data/dns-sniper.gemspec +28 -16
- data/lib/dns-sniper.rb +10 -3
- data/lib/dns-sniper/{formatter.rb → exporter.rb} +7 -3
- data/lib/dns-sniper/exporters.rb +30 -0
- data/lib/dns-sniper/exporters/bind8_exporter.rb +15 -0
- data/lib/dns-sniper/exporters/dnsmasq_exporter.rb +13 -0
- data/lib/dns-sniper/exporters/hosts_exporter.rb +13 -0
- data/lib/dns-sniper/exporters/netgear_exporter.rb +13 -0
- 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 +20 -162
- 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 +26 -28
- data/.gitignore +0 -9
- data/lib/dns-sniper/formatters.rb +0 -24
- data/lib/dns-sniper/formatters/text.rb +0 -12
- data/lib/dns-sniper/formatters/unbound.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f366147ef18ba1adc0449b75001bd217b18b27727fe81406980cb3dec815da82
|
4
|
+
data.tar.gz: b7ecdce1690d770cfc6cbe69c4745465e45d70d1704fe955cd17786ff5cc6b14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4ef85c2cbb26409ff3bd52097ff5c95c80ec1b8303c12ab0f871fe056a33fe7a5139272e0ea6e4fd9ea8cb2c459d528155250dee70b62174284a07baefba6b9
|
7
|
+
data.tar.gz: 9f2c3aa82e0f604314a9de321e5960bef302906d5dec42ab4a7801b14bbd932f11b8dd1a05fed346a75e3a895aee72ac7506085766b25e45fe228eca55ef6360
|
data/Gemfile
CHANGED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2020 Brody Hoskins
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# dns-sniper
|
2
|
+
|
3
|
+
dns-sniper is a command line utility that combines online DNS blacklists and combines them into the desired configuration format.
|
4
|
+
|
5
|
+
## Supported Formats
|
6
|
+
|
7
|
+
### Input formats
|
8
|
+
|
9
|
+
* HOSTS
|
10
|
+
* plaintext (hostnames as newline-seperated list)
|
11
|
+
|
12
|
+
### Output formats
|
13
|
+
|
14
|
+
* bind 8
|
15
|
+
* dnsmasq
|
16
|
+
* HOSTS
|
17
|
+
* plaintext (hostnames as newline-seperated list)
|
18
|
+
* unbound
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Using bundler, add to the `Gemfile`:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'dns-sniper'
|
26
|
+
```
|
27
|
+
|
28
|
+
Or standalone:
|
29
|
+
|
30
|
+
```
|
31
|
+
$ gem install dns-sniper
|
32
|
+
```
|
33
|
+
|
34
|
+
## Sample Usage
|
35
|
+
|
36
|
+
### From within Ruby
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
require 'dns-sniper'
|
40
|
+
|
41
|
+
hostnames = DNSSniper::Hostnames.new
|
42
|
+
|
43
|
+
# Block domain names
|
44
|
+
hostnames.add_from('https://pgl.yoyo.org/as/serverlist.php?hostformat=hosts;showintro=0;mimetype=plaintext') # From the web
|
45
|
+
hostnames.add_from('https://raw.githubusercontent.com/brodyhoskins/dns-blocklists/master/tracking.list')
|
46
|
+
hostnames.add_from('~/.config/dns-sniper/blocklists.list') # From filesystem
|
47
|
+
|
48
|
+
# Manually add domain name
|
49
|
+
hostnames.add('ads.yahoo.com')
|
50
|
+
hostnames.add(['ads.doubleclick.net', 'ads.msn.com'])
|
51
|
+
|
52
|
+
# Remove whitelisted domain names
|
53
|
+
hostnames.remove_from('~/.config/dns-sniper/whitelisted-hostnames.list')
|
54
|
+
hostnames.remove_from('https://example.com/whitelisted.hosts')
|
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')
|
63
|
+
```
|
64
|
+
|
65
|
+
### From CLI
|
66
|
+
|
67
|
+
See `dns-sniper --help`
|
68
|
+
|
69
|
+
Using the CLI version makes it easy to update configuration formats automatically. For example:
|
70
|
+
|
71
|
+
```bash
|
72
|
+
#!/usr/bin/env bash
|
73
|
+
|
74
|
+
/path/to/dns-sniper -f ~/.config/dns-sniper/blacklist.list -w ~/.config/dns-sniper/whitelist.list -o unbound > /etc/unbound/unbound.conf.t/blocklist.conf
|
75
|
+
|
76
|
+
service unbound reload
|
77
|
+
```
|
data/Rakefile
CHANGED
data/bin/dns-sniper
CHANGED
@@ -1,52 +1,50 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require 'optparse'
|
5
|
+
require 'set'
|
6
|
+
require 'dns-sniper'
|
6
7
|
|
7
|
-
VALID_FORMATS = [
|
8
|
-
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)
|
9
10
|
|
10
11
|
class Parser
|
11
12
|
def self.parse(options)
|
12
|
-
args = Options.new(
|
13
|
+
args = Options.new('world')
|
13
14
|
opt_parser = OptionParser.new do |opts|
|
14
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"
|
15
|
-
opts.on(
|
16
|
-
args.
|
16
|
+
opts.on('-c', '--conf=PATH', 'PATH to YAML configuration file') do |path|
|
17
|
+
args.conf_path = path
|
17
18
|
end
|
18
|
-
opts.on(
|
19
|
-
args.whitelisted_hosts_file = path
|
20
|
-
end
|
21
|
-
opts.on("-o", "--output=FORMAT", "FORMAT to output — one of #{VALID_FORMATS.join(", ")}") do |format|
|
19
|
+
opts.on('-o', '--output=FORMAT', "FORMAT to output — one of #{VALID_FORMATS.join(', ')}") do |format|
|
22
20
|
args.format = format
|
23
21
|
end
|
24
|
-
opts.on(
|
22
|
+
opts.on('-h', '--help', 'Prints this help') do
|
25
23
|
puts opts
|
26
24
|
exit
|
27
25
|
end
|
28
26
|
end
|
29
27
|
opt_parser.parse!(options)
|
30
|
-
|
28
|
+
args
|
31
29
|
end
|
32
30
|
end
|
33
31
|
|
34
32
|
options = Parser.parse ARGV
|
35
33
|
|
36
|
-
unless
|
37
|
-
|
34
|
+
unless File.exist?(options[:conf_path])
|
35
|
+
warn "Error: Unable to access ”#{options[:conf_path]}”"
|
38
36
|
exit(-1)
|
39
37
|
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
if options[:whitelisted_hosts_file] and not File.exist?(options[:whitelisted_hosts_file])
|
47
|
-
STDERR.puts "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]}"
|
48
42
|
exit(-1)
|
49
43
|
end
|
50
44
|
|
51
45
|
hostnames = DNSSniper::Hostnames.new
|
52
|
-
|
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/dns-sniper.gemspec
CHANGED
@@ -1,23 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
Gem::Specification.new do |spec|
|
2
|
-
spec.name =
|
3
|
-
spec.license =
|
4
|
-
spec.version =
|
5
|
-
spec.date =
|
6
|
-
|
7
|
-
spec.
|
4
|
+
spec.name = 'dns-sniper'
|
5
|
+
spec.license = 'MIT'
|
6
|
+
spec.version = '0.0.1.pre6'
|
7
|
+
spec.date = '2020-11-11'
|
8
|
+
|
9
|
+
spec.authors = ['Brody Hoskins']
|
10
|
+
spec.email = ['brody@brody.digital']
|
8
11
|
|
9
|
-
spec.summary =
|
10
|
-
spec.description =
|
11
|
-
|
12
|
+
spec.summary = 'Combine DNS blacklists into desired configuration format'
|
13
|
+
spec.description = <<~DESC.gsub(/\n/, ' ').strip
|
14
|
+
dns-sniper generates DNS configuration files based on various user-defined
|
15
|
+
blacklists online. Configuration files can be generated for use in Ruby
|
16
|
+
applications or from the command line.
|
17
|
+
DESC
|
18
|
+
spec.homepage = 'https://github.com/brodyhoskins/dns-sniper'
|
12
19
|
|
13
|
-
spec.
|
20
|
+
spec.metadata = {
|
21
|
+
'homepage_uri' => 'https://github.com/brodyhoskins/dns-sniper',
|
22
|
+
'source_code_uri' => 'https://github.com/brodyhoskins/dns-sniper'
|
23
|
+
}
|
14
24
|
|
15
|
-
spec.
|
16
|
-
spec.
|
17
|
-
spec.
|
25
|
+
spec.files = Dir['lib/**/*']
|
26
|
+
spec.files += Dir['[A-Z]*'] + Dir['test/**/*']
|
27
|
+
spec.files.reject! { |fn| fn.include? 'CVS' }
|
28
|
+
spec.require_paths = ['lib']
|
18
29
|
|
19
|
-
spec.
|
30
|
+
spec.bindir = 'bin'
|
31
|
+
spec.executables = 'dns-sniper'
|
20
32
|
|
21
|
-
spec.
|
22
|
-
spec.
|
33
|
+
spec.add_dependency 'down', '~> 5.1'
|
34
|
+
spec.add_dependency 'hosts_file', '~> 1.0'
|
23
35
|
end
|
data/lib/dns-sniper.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'resolv'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require 'dns-sniper/exporter'
|
7
|
+
require 'dns-sniper/exporters'
|
8
|
+
require 'dns-sniper/hostnames'
|
9
|
+
require 'dns-sniper/importer'
|
10
|
+
require 'dns-sniper/importers'
|
@@ -1,11 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DNSSniper
|
2
|
-
class
|
4
|
+
class Exporter
|
5
|
+
attr_accessor :data, :hostnames
|
6
|
+
|
3
7
|
def initialize(hostnames, options = {})
|
4
8
|
@hostnames = hostnames
|
5
|
-
@
|
9
|
+
@data = output(options)
|
6
10
|
end
|
7
11
|
|
8
|
-
def output
|
12
|
+
def output(*)
|
9
13
|
raise NotImplementedError, "Error: #output isn’t supported by #{self.class.name}"
|
10
14
|
end
|
11
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'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DNSSniper
|
4
|
+
class Bind8Exporter < Exporter
|
5
|
+
def output(options = {})
|
6
|
+
raise ArgumentError, 'zone_file is required' unless defined?(options[:zone_file])
|
7
|
+
|
8
|
+
str = ''.dup
|
9
|
+
@hostnames.each do |hostname|
|
10
|
+
str << "zone \"#{hostname}\" { type master; notify no; file \"#{options[:zone_file]}\"; };#{$/}"
|
11
|
+
end
|
12
|
+
str
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -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
@@ -1,176 +1,34 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'down'
|
4
|
+
require 'hosts_file'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'resolv'
|
5
7
|
|
6
8
|
module DNSSniper
|
7
9
|
class Hostnames
|
8
|
-
|
9
|
-
|
10
|
-
self
|
11
|
-
end
|
12
|
-
|
13
|
-
def add(hostname)
|
14
|
-
hostname = clean(hostname)
|
15
|
-
@hostnames << hostname if hostname
|
16
|
-
self
|
17
|
-
end
|
18
|
-
|
19
|
-
def add_many(hostnames)
|
20
|
-
hostnames.each { |hostname| add(hostname) }
|
21
|
-
self
|
22
|
-
end
|
23
|
-
|
24
|
-
def add_from(paths_or_urls)
|
25
|
-
return self unless paths_or_urls
|
26
|
-
if (paths_or_urls.class == String)
|
27
|
-
paths_or_urls = [paths_or_urls]
|
28
|
-
end
|
29
|
-
|
30
|
-
paths_or_urls.each do |path_or_url|
|
31
|
-
path_or_url = path_or_url.strip
|
32
|
-
|
33
|
-
if File.exist?(path_or_url)
|
34
|
-
contents = File.open(path_or_url).readlines
|
35
|
-
else
|
36
|
-
begin
|
37
|
-
down = Down.download(path_or_url)
|
38
|
-
path_or_url = down.path
|
39
|
-
contents = down.readlines
|
40
|
-
rescue Down::NotFound
|
41
|
-
STDERR.puts "\"#{path_or_url}\" does not exist"
|
42
|
-
return self
|
43
|
-
rescue Down::ResponseError
|
44
|
-
STDERR.puts "\"#{path_or_url}\": No data from server"
|
45
|
-
return self
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
case syntax(path_or_url, contents)
|
50
|
-
when nil
|
51
|
-
STDERR.puts "Error: Syntax: Syntax of \"#{path_or_url}\" not recognized, ignored"
|
52
|
-
return self
|
53
|
-
when "hosts"
|
54
|
-
add_many from_hosts_file(path_or_url)
|
55
|
-
when "hostnames"
|
56
|
-
add_many from_hostnames_file(contents)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
self
|
61
|
-
end
|
62
|
-
|
63
|
-
def remove(hostname)
|
64
|
-
hostname = clean(hostname)
|
65
|
-
@hostnames = @hostnames - [hostname] if hostname
|
66
|
-
self
|
67
|
-
end
|
68
|
-
|
69
|
-
def remove_many(hostnames)
|
70
|
-
hostnames.each { |hostname| remove(hostname) }
|
71
|
-
self
|
72
|
-
end
|
73
|
-
|
74
|
-
def remove_from(paths_or_urls)
|
75
|
-
return self unless paths_or_urls
|
76
|
-
if (paths_or_urls.class == String)
|
77
|
-
paths_or_urls = [paths_or_urls]
|
78
|
-
end
|
79
|
-
|
80
|
-
paths_or_urls.each do |path_or_url|
|
81
|
-
path_or_url = path_or_url.strip
|
82
|
-
|
83
|
-
if File.exist?(path_or_url)
|
84
|
-
contents = File.open(path_or_url).readlines
|
85
|
-
else
|
86
|
-
begin
|
87
|
-
down = Down.download(path_or_url)
|
88
|
-
path_or_url = down.path
|
89
|
-
contents = down.readlines
|
90
|
-
rescue Down::NotFound
|
91
|
-
STDERR.puts "\"#{path_or_url}\" does not exist"
|
92
|
-
return self
|
93
|
-
rescue Down::ResponseError
|
94
|
-
STDERR.puts "\"#{path_or_url}\": No data from server"
|
95
|
-
return self
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
case syntax(path_or_url, contents)
|
100
|
-
when nil
|
101
|
-
STDERR.puts "Error: Syntax: Syntax of \"#{path_or_url}\" not recognized, ignored"
|
102
|
-
return self
|
103
|
-
when "hosts"
|
104
|
-
remove_many from_hosts_file(path_or_url)
|
105
|
-
when "hostnames"
|
106
|
-
remove_many from_hostnames_file(contents)
|
107
|
-
end
|
108
|
-
end
|
10
|
+
attr_accessor :blacklist
|
11
|
+
attr_accessor :whitelist
|
109
12
|
|
13
|
+
def initialize
|
14
|
+
@blacklist = [].to_set
|
15
|
+
@whitelist = [].to_set
|
110
16
|
self
|
111
17
|
end
|
112
18
|
|
113
|
-
def
|
114
|
-
|
115
|
-
|
116
|
-
klass = Sniper.const_get(format)
|
117
|
-
klass.new(@hostnames.to_a).output
|
118
|
-
rescue NameError
|
119
|
-
return false
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def to_a
|
124
|
-
@hostnames.to_a
|
125
|
-
end
|
126
|
-
|
127
|
-
def to_text
|
128
|
-
@hostnames.to_a.join("\n")
|
129
|
-
end
|
130
|
-
|
131
|
-
def to_unbound
|
132
|
-
str = "server:\n"
|
133
|
-
@hostnames.each do |hostname|
|
134
|
-
str << " local-zone: \"#{hostname}\" static\n"
|
135
|
-
end
|
136
|
-
str
|
137
|
-
end
|
19
|
+
def blocklist
|
20
|
+
blacklist = @blacklist
|
21
|
+
whitelist = @whitelist
|
138
22
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
hostname_top_domain = "#{hostname.split(".")[-2]}.#{hostname.split(".")[-1]}"
|
145
|
-
|
146
|
-
if not hostname.include?("#") and not ["broadcasthost", "localhost", ""].include?(hostname) and not @hostnames.include?(hostname_top_domain)
|
147
|
-
hostname
|
148
|
-
else
|
149
|
-
nil
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def syntax(path_or_url, contents)
|
154
|
-
contents.each do |line|
|
155
|
-
next if line.include?("#")
|
156
|
-
line = line.downcase
|
157
|
-
|
158
|
-
if line.strip.split(/\s/).first =~ Regexp.union([Resolv::IPv4::Regex, Resolv::IPv6::Regex])
|
159
|
-
return "hosts"
|
160
|
-
elsif line.include? "." and not line.include? "http" and path_or_url.end_with?(".list")
|
161
|
-
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('.')]
|
162
28
|
end
|
163
29
|
end
|
164
|
-
nil
|
165
|
-
end
|
166
|
-
|
167
|
-
def from_hosts_file(path_or_url)
|
168
|
-
# TODO: Remove downloading file twice
|
169
|
-
HostsFile.load(path_or_url).map(&:name)
|
170
|
-
end
|
171
30
|
|
172
|
-
|
173
|
-
contents.each { |line| add(line) }
|
31
|
+
blacklist - whitelist
|
174
32
|
end
|
175
33
|
end
|
176
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,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dns-sniper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1.pre6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brody Hoskins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bundler
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 2.1.2
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 2.1.2
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: down
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -31,7 +17,7 @@ dependencies:
|
|
31
17
|
- - "~>"
|
32
18
|
- !ruby/object:Gem::Version
|
33
19
|
version: '5.1'
|
34
|
-
type: :
|
20
|
+
type: :runtime
|
35
21
|
prerelease: false
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
37
23
|
requirements:
|
@@ -45,15 +31,16 @@ dependencies:
|
|
45
31
|
- - "~>"
|
46
32
|
- !ruby/object:Gem::Version
|
47
33
|
version: '1.0'
|
48
|
-
type: :
|
34
|
+
type: :runtime
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
52
38
|
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '1.0'
|
55
|
-
description:
|
56
|
-
|
41
|
+
description: dns-sniper generates DNS configuration files based on various user-defined
|
42
|
+
blacklists online. Configuration files can be generated for use in Ruby applications
|
43
|
+
or from the command line.
|
57
44
|
email:
|
58
45
|
- brody@brody.digital
|
59
46
|
executables:
|
@@ -61,22 +48,34 @@ executables:
|
|
61
48
|
extensions: []
|
62
49
|
extra_rdoc_files: []
|
63
50
|
files:
|
64
|
-
- ".gitignore"
|
65
51
|
- Gemfile
|
66
52
|
- Gemfile.lock
|
53
|
+
- MIT-LICENSE
|
54
|
+
- README.md
|
67
55
|
- Rakefile
|
68
56
|
- bin/dns-sniper
|
69
57
|
- dns-sniper.gemspec
|
70
58
|
- lib/dns-sniper.rb
|
71
|
-
- lib/dns-sniper/
|
72
|
-
- lib/dns-sniper/
|
73
|
-
- lib/dns-sniper/
|
74
|
-
- lib/dns-sniper/
|
59
|
+
- lib/dns-sniper/exporter.rb
|
60
|
+
- lib/dns-sniper/exporters.rb
|
61
|
+
- lib/dns-sniper/exporters/bind8_exporter.rb
|
62
|
+
- lib/dns-sniper/exporters/dnsmasq_exporter.rb
|
63
|
+
- lib/dns-sniper/exporters/hosts_exporter.rb
|
64
|
+
- lib/dns-sniper/exporters/netgear_exporter.rb
|
65
|
+
- lib/dns-sniper/exporters/text_exporter.rb
|
66
|
+
- lib/dns-sniper/exporters/unbound_exporter.rb
|
75
67
|
- lib/dns-sniper/hostnames.rb
|
68
|
+
- lib/dns-sniper/importer.rb
|
69
|
+
- lib/dns-sniper/importers.rb
|
70
|
+
- lib/dns-sniper/importers/configuration_importer.rb
|
71
|
+
- lib/dns-sniper/importers/domains_importer.rb
|
72
|
+
- lib/dns-sniper/importers/hosts_importer.rb
|
76
73
|
homepage: https://github.com/brodyhoskins/dns-sniper
|
77
74
|
licenses:
|
78
75
|
- MIT
|
79
|
-
metadata:
|
76
|
+
metadata:
|
77
|
+
homepage_uri: https://github.com/brodyhoskins/dns-sniper
|
78
|
+
source_code_uri: https://github.com/brodyhoskins/dns-sniper
|
80
79
|
post_install_message:
|
81
80
|
rdoc_options: []
|
82
81
|
require_paths:
|
@@ -95,6 +94,5 @@ requirements: []
|
|
95
94
|
rubygems_version: 3.0.3
|
96
95
|
signing_key:
|
97
96
|
specification_version: 4
|
98
|
-
summary:
|
99
|
-
into the desired configuration format
|
97
|
+
summary: Combine DNS blacklists into desired configuration format
|
100
98
|
test_files: []
|
data/.gitignore
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
module DNSSniper
|
2
|
-
module Formatters
|
3
|
-
extend self
|
4
|
-
|
5
|
-
attr_reader :registered
|
6
|
-
@registered = []
|
7
|
-
|
8
|
-
def register(class_name, autoload_require)
|
9
|
-
DNSSniper.autoload(class_name, autoload_require)
|
10
|
-
self.registered << class_name
|
11
|
-
end
|
12
|
-
|
13
|
-
def all
|
14
|
-
Sniper::Formatters.registered.map { |name| Sniper.const_get(name) }
|
15
|
-
end
|
16
|
-
|
17
|
-
def find(name)
|
18
|
-
all.find { |c| c.name.downcase == name.to_s.downcase } or raise NameError, "unknown carrier #{name}"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
DNSSniper::Formatters.register :Text, "dns-sniper/formatters/text"
|
24
|
-
DNSSniper::Formatters.register :Unbound, "dns-sniper/formatters/unbound"
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module DNSSniper
|
2
|
-
class Unbound < Formatter
|
3
|
-
def initialize(hostnames, options = {})
|
4
|
-
@hostnames = hostnames
|
5
|
-
@options = options
|
6
|
-
end
|
7
|
-
|
8
|
-
def output
|
9
|
-
str = "server:#{$/}"
|
10
|
-
@hostnames.each do |hostname|
|
11
|
-
str << " local-zone: \"#{hostname}\" static#{$/}"
|
12
|
-
end
|
13
|
-
str
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|