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 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