dns-sniper 0.0.1pre → 0.0.1.pre6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c6bab67f9579e0762d197d29dd188ced90277fa05573bb84e472be2c42a5a3e
4
- data.tar.gz: 4123ba5a1c02a7f5d4972055b4b1743aae7bd4f995cd1336626a3f8a68ca7e23
3
+ metadata.gz: f366147ef18ba1adc0449b75001bd217b18b27727fe81406980cb3dec815da82
4
+ data.tar.gz: b7ecdce1690d770cfc6cbe69c4745465e45d70d1704fe955cd17786ff5cc6b14
5
5
  SHA512:
6
- metadata.gz: 479adbba0f5d557ef178acadc65461ee0be27684f4a4ab95bdd8f2b129a15b2583658fcea86193ee1c30519c5dba712b6c784619d79eaa345b7fcd993a1a19c1
7
- data.tar.gz: b5ef2952418417f81d8c47b7cfe3052fe5e18242d5d87c3328f9713b5175bf94f48dc0ac3e3a9c8b4ddcc944637f2d8aea07fef00d15dc2b84c24e8f72fc3b8c
6
+ metadata.gz: b4ef85c2cbb26409ff3bd52097ff5c95c80ec1b8303c12ab0f871fe056a33fe7a5139272e0ea6e4fd9ea8cb2c459d528155250dee70b62174284a07baefba6b9
7
+ data.tar.gz: 9f2c3aa82e0f604314a9de321e5960bef302906d5dec42ab4a7801b14bbd932f11b8dd1a05fed346a75e3a895aee72ac7506085766b25e45fe228eca55ef6360
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  gemspec
@@ -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.
@@ -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
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -1,52 +1,50 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "optparse"
4
- require "set"
5
- require "dns-sniper"
4
+ require 'optparse'
5
+ require 'set'
6
+ require 'dns-sniper'
6
7
 
7
- VALID_FORMATS = ["text", "unbound"]
8
- Options = Struct.new(:blacklist_urls_file, :whitelisted_hosts_file, :format)
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("world")
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("-f", "--blacklist=PATH", "PATH to file with line-separated list of URL’s of blacklists") do |path|
16
- args.blacklist_urls_file = path
16
+ opts.on('-c', '--conf=PATH', 'PATH to YAML configuration file') do |path|
17
+ args.conf_path = path
17
18
  end
18
- opts.on("-w", "--whitelist=path", "Path to file with line-separated list of whitelisted hostnames") do |path|
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("-h", "--help", "Prints this help") do
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
- return args
28
+ args
31
29
  end
32
30
  end
33
31
 
34
32
  options = Parser.parse ARGV
35
33
 
36
- unless VALID_FORMATS.include?(options[:format])
37
- STDERR.puts "Error: Format ”#{options[:format]}” unrecognized"
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
- unless File.exist?(options[:blacklist_urls_file])
42
- STDERR.puts "Error: Unable to access ”#{options[:blacklist_urls_file]}”"
43
- exit(-1)
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
- puts hostnames.add_from(File.open(options[:blacklist_urls_file]).readlines).remove_from(options[:whitelisted_hosts_file]).to_format(options[:format])
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
@@ -1,23 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Gem::Specification.new do |spec|
2
- spec.name = "dns-sniper"
3
- spec.license = "MIT"
4
- spec.version = "0.0.1pre"
5
- spec.date = "2020-08-13"
6
- spec.authors = ["Brody Hoskins"]
7
- spec.email = ["brody@brody.digital"]
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 = "Command line utility that combines online DNS blacklists and combines them into the desired configuration format"
10
- spec.description = "Command line utility that combines online DNS blacklists and combines them into the desired configuration format"
11
- spec.homepage = "https://github.com/brodyhoskins/dns-sniper"
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.files = `git ls-files`.split($/)
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.bindir = "bin"
16
- spec.executables = "dns-sniper"
17
- spec.require_paths = ["lib"]
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.add_development_dependency "bundler", "~> 2.1.2"
30
+ spec.bindir = 'bin'
31
+ spec.executables = 'dns-sniper'
20
32
 
21
- spec.add_development_dependency "down", "~> 5.1"
22
- spec.add_development_dependency "hosts_file", "~> 1.0"
33
+ spec.add_dependency 'down', '~> 5.1'
34
+ spec.add_dependency 'hosts_file', '~> 1.0'
23
35
  end
@@ -1,3 +1,10 @@
1
- require "dns-sniper/hostnames"
2
- require "dns-sniper/formatter"
3
- require "dns-sniper/formatters"
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 Formatter
4
+ class Exporter
5
+ attr_accessor :data, :hostnames
6
+
3
7
  def initialize(hostnames, options = {})
4
8
  @hostnames = hostnames
5
- @options = options
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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DNSSniper
4
+ class DnsmasqExporter < Exporter
5
+ def output(*)
6
+ str = ''.dup
7
+ @hostnames.each do |hostname|
8
+ str << "server=/#{hostname}/#{$/}"
9
+ end
10
+ str
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DNSSniper
4
+ class HostsExporter < Exporter
5
+ def output(*)
6
+ str = ''.dup
7
+ @hostnames.each do |hostname|
8
+ str << "127.0.0.1\t#{hostname}#{$/}"
9
+ end
10
+ str
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DNSSniper
4
+ class NetgearExporter < Exporter
5
+ def output(*)
6
+ str = ''.dup
7
+ @hostnames.each_with_index do |hostname, i|
8
+ str << "[517003_e]: #{i + 1}) #{hostname}\n"
9
+ end
10
+ str
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DNSSniper
4
+ class TextExporter < Exporter
5
+ def output(_options = {})
6
+ @hostnames.to_a.join($/)
7
+ end
8
+ end
9
+ 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
@@ -1,176 +1,34 @@
1
- require "down"
2
- require "hosts_file"
3
- require "open-uri"
4
- require "resolv"
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
- def initialize(options = {})
9
- @hostnames = [].to_set
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 to_format(format)
114
- format = format.capitalize
115
- begin
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
- private
140
-
141
- def clean(hostname)
142
- hostname = hostname.downcase.strip
143
- hostname = hostname.sub("www.", "")
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
- def from_hostnames_file(contents)
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.1pre
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-08-13 00:00:00.000000000 Z
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: :development
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: :development
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: Command line utility that combines online DNS blacklists and combines
56
- them into the desired configuration format
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/formatter.rb
72
- - lib/dns-sniper/formatters.rb
73
- - lib/dns-sniper/formatters/text.rb
74
- - lib/dns-sniper/formatters/unbound.rb
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: Command line utility that combines online DNS blacklists and combines them
99
- into the desired configuration format
97
+ summary: Combine DNS blacklists into desired configuration format
100
98
  test_files: []
data/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- .DS_Store
2
- .DS_Store?
3
- ._*
4
- .Spotlight-V100
5
- .Trashes
6
- ehthumbs.db
7
- Thumbs.db
8
-
9
- .ruby-version
@@ -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,12 +0,0 @@
1
- module DNSSniper
2
- class Text < Formatter
3
- def initialize(hostnames, options = {})
4
- @hostnames = hostnames
5
- @options = options
6
- end
7
-
8
- def output
9
- @hostnames.to_a.join($/)
10
- end
11
- end
12
- end
@@ -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