threatinator 0.1.1

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.
Files changed (219) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/CONTRIBUTING.md +119 -0
  4. data/Gemfile +28 -0
  5. data/LICENSE +165 -0
  6. data/README.md +45 -0
  7. data/Rakefile +45 -0
  8. data/VERSION +1 -0
  9. data/bin/threatinator +5 -0
  10. data/lib/threatinator.rb +3 -0
  11. data/lib/threatinator/action.rb +14 -0
  12. data/lib/threatinator/actions/list.rb +2 -0
  13. data/lib/threatinator/actions/list/action.rb +53 -0
  14. data/lib/threatinator/actions/list/config.rb +10 -0
  15. data/lib/threatinator/actions/run.rb +2 -0
  16. data/lib/threatinator/actions/run/action.rb +45 -0
  17. data/lib/threatinator/actions/run/config.rb +32 -0
  18. data/lib/threatinator/actions/run/coverage_observer.rb +54 -0
  19. data/lib/threatinator/actions/run/output_config.rb +59 -0
  20. data/lib/threatinator/cli.rb +13 -0
  21. data/lib/threatinator/cli/action_builder.rb +33 -0
  22. data/lib/threatinator/cli/list_action_builder.rb +19 -0
  23. data/lib/threatinator/cli/parser.rb +113 -0
  24. data/lib/threatinator/cli/run_action_builder.rb +41 -0
  25. data/lib/threatinator/config.rb +6 -0
  26. data/lib/threatinator/config/base.rb +35 -0
  27. data/lib/threatinator/config/feed_search.rb +25 -0
  28. data/lib/threatinator/decoder.rb +24 -0
  29. data/lib/threatinator/decoders/gzip.rb +30 -0
  30. data/lib/threatinator/event.rb +27 -0
  31. data/lib/threatinator/event_builder.rb +41 -0
  32. data/lib/threatinator/exceptions.rb +61 -0
  33. data/lib/threatinator/feed.rb +82 -0
  34. data/lib/threatinator/feed_builder.rb +156 -0
  35. data/lib/threatinator/feed_registry.rb +47 -0
  36. data/lib/threatinator/feed_runner.rb +118 -0
  37. data/lib/threatinator/fetcher.rb +22 -0
  38. data/lib/threatinator/fetchers/http.rb +46 -0
  39. data/lib/threatinator/filter.rb +12 -0
  40. data/lib/threatinator/filters/block.rb +18 -0
  41. data/lib/threatinator/filters/comments.rb +16 -0
  42. data/lib/threatinator/filters/whitespace.rb +19 -0
  43. data/lib/threatinator/output.rb +50 -0
  44. data/lib/threatinator/parser.rb +23 -0
  45. data/lib/threatinator/parsers/csv.rb +7 -0
  46. data/lib/threatinator/parsers/csv/parser.rb +77 -0
  47. data/lib/threatinator/parsers/getline.rb +8 -0
  48. data/lib/threatinator/parsers/getline/parser.rb +45 -0
  49. data/lib/threatinator/parsers/json.rb +8 -0
  50. data/lib/threatinator/parsers/json/adapters/oj.rb +65 -0
  51. data/lib/threatinator/parsers/json/parser.rb +45 -0
  52. data/lib/threatinator/parsers/json/record.rb +20 -0
  53. data/lib/threatinator/parsers/xml.rb +8 -0
  54. data/lib/threatinator/parsers/xml/node.rb +79 -0
  55. data/lib/threatinator/parsers/xml/node_builder.rb +39 -0
  56. data/lib/threatinator/parsers/xml/parser.rb +44 -0
  57. data/lib/threatinator/parsers/xml/path.rb +70 -0
  58. data/lib/threatinator/parsers/xml/pattern.rb +53 -0
  59. data/lib/threatinator/parsers/xml/record.rb +14 -0
  60. data/lib/threatinator/parsers/xml/sax_document.rb +64 -0
  61. data/lib/threatinator/plugin_loader.rb +115 -0
  62. data/lib/threatinator/plugins/output/csv.rb +47 -0
  63. data/lib/threatinator/plugins/output/null.rb +17 -0
  64. data/lib/threatinator/plugins/output/rubydebug.rb +16 -0
  65. data/lib/threatinator/property_definer.rb +101 -0
  66. data/lib/threatinator/record.rb +22 -0
  67. data/lib/threatinator/registry.rb +53 -0
  68. data/lib/threatinator/util.rb +15 -0
  69. data/spec/feeds/ET_compromised-ip_reputation_spec.rb +50 -0
  70. data/spec/feeds/alienvault-ip_reputation_spec.rb +50 -0
  71. data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +50 -0
  72. data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +50 -0
  73. data/spec/feeds/autoshun_shunlist_spec.rb +42 -0
  74. data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +50 -0
  75. data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +50 -0
  76. data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +50 -0
  77. data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +50 -0
  78. data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +50 -0
  79. data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +50 -0
  80. data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +50 -0
  81. data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +50 -0
  82. data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +50 -0
  83. data/spec/feeds/ciarmy-ip_reputation_spec.rb +50 -0
  84. data/spec/feeds/cruzit-ip_reputation_spec.rb +50 -0
  85. data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +50 -0
  86. data/spec/feeds/data/ET_compromised-ip_reputation.txt +11 -0
  87. data/spec/feeds/data/alienvault-ip_reputation.txt +18 -0
  88. data/spec/feeds/data/arbor_domainlist.txt +11 -0
  89. data/spec/feeds/data/arbor_ssh.txt +16 -0
  90. data/spec/feeds/data/autoshun_shunlist.csv +20 -0
  91. data/spec/feeds/data/blocklist_de_apache-ip-reputation.txt +17 -0
  92. data/spec/feeds/data/blocklist_de_bots-ip-reputation.txt +15 -0
  93. data/spec/feeds/data/blocklist_de_ftp-ip-reputation.txt +7 -0
  94. data/spec/feeds/data/blocklist_de_imap-ip-reputation.txt +8 -0
  95. data/spec/feeds/data/blocklist_de_pop3-ip-reputation.txt +11 -0
  96. data/spec/feeds/data/blocklist_de_proftpd-ip-reputation.txt +12 -0
  97. data/spec/feeds/data/blocklist_de_sip-ip-reputation.txt +9 -0
  98. data/spec/feeds/data/blocklist_de_ssh-ip-reputation.txt +10 -0
  99. data/spec/feeds/data/blocklist_de_strongips-ip-reputation.txt +11 -0
  100. data/spec/feeds/data/ciarmy-ip-reputation.txt +11 -0
  101. data/spec/feeds/data/cruzit-ip-reputation.txt +14 -0
  102. data/spec/feeds/data/dan_me_uk_torlist-ip-reputation.txt +11 -0
  103. data/spec/feeds/data/dshield_topattackers.xml +4 -0
  104. data/spec/feeds/data/feodo_domainlist.txt +18 -0
  105. data/spec/feeds/data/feodo_iplist.txt +20 -0
  106. data/spec/feeds/data/infiltrated_iplist.txt +16 -0
  107. data/spec/feeds/data/malc0de_domainlist.txt +18 -0
  108. data/spec/feeds/data/malc0de_iplist.txt +14 -0
  109. data/spec/feeds/data/mirc_domainlist.txt +31 -0
  110. data/spec/feeds/data/nothink_irc_iplist.txt +14 -0
  111. data/spec/feeds/data/nothink_ssh_iplist.txt +10 -0
  112. data/spec/feeds/data/openbl_iplist.txt +12 -0
  113. data/spec/feeds/data/palevo_domainlist.txt +25 -0
  114. data/spec/feeds/data/palevo_iplist.txt +24 -0
  115. data/spec/feeds/data/phishtank-sample.json.gz +0 -0
  116. data/spec/feeds/data/spyeye_domainlist.txt +16 -0
  117. data/spec/feeds/data/spyeye_iplist.txt +19 -0
  118. data/spec/feeds/data/t-arend-de_ssh_iplist.txt +17 -0
  119. data/spec/feeds/data/the_haleys_ssh_iplist.txt +12 -0
  120. data/spec/feeds/data/yourcmc_ssh-ip_reputation.txt +27 -0
  121. data/spec/feeds/data/zeus-ip_reputation.txt +285 -0
  122. data/spec/feeds/data/zeus_domainlist.txt +27 -0
  123. data/spec/feeds/dshield_attackers-top1000_spec.rb +43 -0
  124. data/spec/feeds/feodo-domain_reputation_spec.rb +50 -0
  125. data/spec/feeds/feodo-ip_reputation_spec.rb +50 -0
  126. data/spec/feeds/infiltrated-ip_reputation_spec.rb +50 -0
  127. data/spec/feeds/malc0de-domain_reputation_spec.rb +50 -0
  128. data/spec/feeds/malc0de-ip_reputation_spec.rb +50 -0
  129. data/spec/feeds/mirc-domain_reputation_spec.rb +50 -0
  130. data/spec/feeds/nothink_irc-ip_reputation_spec.rb +50 -0
  131. data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +50 -0
  132. data/spec/feeds/openbl-ip_reputation_spec.rb +50 -0
  133. data/spec/feeds/palevo-domain_reputation_spec.rb +50 -0
  134. data/spec/feeds/palevo-ip_reputation_spec.rb +50 -0
  135. data/spec/feeds/phishtank_spec.rb +45 -0
  136. data/spec/feeds/spyeye-domain_reputation_spec.rb +50 -0
  137. data/spec/feeds/spyeye-ip_reputation_spec.rb +50 -0
  138. data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +50 -0
  139. data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +50 -0
  140. data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +50 -0
  141. data/spec/feeds/zeus-domain_reputation_spec.rb +50 -0
  142. data/spec/feeds/zeus-ip_reputation_spec.rb +50 -0
  143. data/spec/fixtures/feed/provider1/feed1.feed +6 -0
  144. data/spec/fixtures/parsers/test.xml +13 -0
  145. data/spec/fixtures/parsers/test_self_closing.xml +20 -0
  146. data/spec/fixtures/plugins/bad/threatinator/plugins/test_error1/plugin.rb +1 -0
  147. data/spec/fixtures/plugins/bad/threatinator/plugins/test_missing1/plugin.rb +0 -0
  148. data/spec/fixtures/plugins/fake.rb +19 -0
  149. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_a.rb +8 -0
  150. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_b.rb +8 -0
  151. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_c.rb +8 -0
  152. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_d.rb +8 -0
  153. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_e.rb +8 -0
  154. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_f.rb +8 -0
  155. data/spec/spec_helper.rb +52 -0
  156. data/spec/support/bad_feeds/missing_fetcher.feed +7 -0
  157. data/spec/support/bad_feeds/missing_name.feed +6 -0
  158. data/spec/support/bad_feeds/missing_parser.feed +3 -0
  159. data/spec/support/bad_feeds/missing_provider.feed +5 -0
  160. data/spec/support/factories/event.rb +27 -0
  161. data/spec/support/factories/feed.rb +32 -0
  162. data/spec/support/factories/feed_builder.rb +65 -0
  163. data/spec/support/factories/feed_registry.rb +8 -0
  164. data/spec/support/factories/output.rb +11 -0
  165. data/spec/support/factories/record.rb +17 -0
  166. data/spec/support/factories/xml_node.rb +33 -0
  167. data/spec/support/helpers/io.rb +11 -0
  168. data/spec/support/helpers/models.rb +13 -0
  169. data/spec/support/shared/action_builder.rb +47 -0
  170. data/spec/support/shared/decoder.rb +70 -0
  171. data/spec/support/shared/feeds.rb +218 -0
  172. data/spec/support/shared/fetcher.rb +48 -0
  173. data/spec/support/shared/filter.rb +14 -0
  174. data/spec/support/shared/io-like.rb +7 -0
  175. data/spec/support/shared/output.rb +120 -0
  176. data/spec/support/shared/parsers.rb +51 -0
  177. data/spec/support/shared/record.rb +111 -0
  178. data/spec/threatinator/actions/list/action_spec.rb +93 -0
  179. data/spec/threatinator/actions/run/action_spec.rb +89 -0
  180. data/spec/threatinator/actions/run/config_spec.rb +39 -0
  181. data/spec/threatinator/actions/run/coverage_observer_spec.rb +116 -0
  182. data/spec/threatinator/actions/run/output_config_spec.rb +89 -0
  183. data/spec/threatinator/cli/list_action_builder_spec.rb +57 -0
  184. data/spec/threatinator/cli/run_action_builder_spec.rb +133 -0
  185. data/spec/threatinator/cli_spec.rb +175 -0
  186. data/spec/threatinator/config/base_spec.rb +39 -0
  187. data/spec/threatinator/config/feed_search_spec.rb +76 -0
  188. data/spec/threatinator/decoders/gzip_spec.rb +75 -0
  189. data/spec/threatinator/event_builder_spec.rb +33 -0
  190. data/spec/threatinator/event_spec.rb +30 -0
  191. data/spec/threatinator/feed_builder_spec.rb +636 -0
  192. data/spec/threatinator/feed_registry_spec.rb +198 -0
  193. data/spec/threatinator/feed_runner_spec.rb +155 -0
  194. data/spec/threatinator/feed_spec.rb +169 -0
  195. data/spec/threatinator/fetcher_spec.rb +12 -0
  196. data/spec/threatinator/fetchers/http_spec.rb +32 -0
  197. data/spec/threatinator/filter_spec.rb +13 -0
  198. data/spec/threatinator/filters/block_spec.rb +16 -0
  199. data/spec/threatinator/filters/comments_spec.rb +13 -0
  200. data/spec/threatinator/filters/whitespace_spec.rb +12 -0
  201. data/spec/threatinator/parser_spec.rb +13 -0
  202. data/spec/threatinator/parsers/csv/parser_spec.rb +202 -0
  203. data/spec/threatinator/parsers/getline/parser_spec.rb +83 -0
  204. data/spec/threatinator/parsers/json/parser_spec.rb +106 -0
  205. data/spec/threatinator/parsers/json/record_spec.rb +30 -0
  206. data/spec/threatinator/parsers/xml/node_spec.rb +335 -0
  207. data/spec/threatinator/parsers/xml/parser_spec.rb +263 -0
  208. data/spec/threatinator/parsers/xml/path_spec.rb +209 -0
  209. data/spec/threatinator/parsers/xml/pattern_spec.rb +72 -0
  210. data/spec/threatinator/parsers/xml/record_spec.rb +27 -0
  211. data/spec/threatinator/plugin_loader_spec.rb +238 -0
  212. data/spec/threatinator/plugins/output/csv_spec.rb +46 -0
  213. data/spec/threatinator/plugins/output/null_spec.rb +17 -0
  214. data/spec/threatinator/plugins/output/rubydebug_spec.rb +37 -0
  215. data/spec/threatinator/property_definer_spec.rb +155 -0
  216. data/spec/threatinator/record_spec.rb +19 -0
  217. data/spec/threatinator/registry_spec.rb +97 -0
  218. data/spec/threatinator/runner_spec.rb +273 -0
  219. metadata +376 -0
@@ -0,0 +1,41 @@
1
+ require 'threatinator/cli/action_builder'
2
+ require 'threatinator/actions/run'
3
+ require 'threatinator/actions/run/coverage_observer'
4
+ require 'csv'
5
+
6
+ module Threatinator
7
+ module CLI
8
+ class RunActionBuilder < ActionBuilder
9
+ def initialize(opts, args, config_class)
10
+ super(opts, args)
11
+ @config_class = config_class
12
+ end
13
+
14
+ def build
15
+ Threatinator::Actions::Run::Action.new(feed_registry, config)
16
+ end
17
+
18
+ def config
19
+ run_hash = config_hash["run"] || {}
20
+ run_hash['observers'] ||= []
21
+
22
+ if filename = run_hash['coverage_output']
23
+ observer = Threatinator::Actions::Run::CoverageObserver.new(filename)
24
+ run_hash['observers'] << observer
25
+ end
26
+
27
+ config = @config_class.new(run_hash)
28
+
29
+ if config.feed_provider.nil? && provider = extra_args.shift
30
+ config.feed_provider = provider
31
+ end
32
+
33
+ if config.feed_name.nil? && name = extra_args.shift
34
+ config.feed_name = name
35
+ end
36
+ config
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ module Threatinator
2
+ module Config
3
+ require 'threatinator/config/feed_search'
4
+ end
5
+ end
6
+
@@ -0,0 +1,35 @@
1
+ require 'virtus'
2
+ module Threatinator
3
+ module Config
4
+ class Base
5
+ include Virtus.model
6
+
7
+ def self.properties(namespace = nil)
8
+ ret = {}
9
+ self.attribute_set.each do |attribute|
10
+ name = attribute.name.to_s
11
+ unless namespace.nil?
12
+ name = [namespace, name].join('.')
13
+ end
14
+
15
+ if attribute.primitive.ancestors.include?(Threatinator::Config::Base)
16
+ ret.merge!(attribute.primitive.properties(name))
17
+ next
18
+ end
19
+
20
+ desc = attribute.options[:description]
21
+ case desc
22
+ when nil
23
+ next
24
+ when ::Proc
25
+ desc = desc.call(self, attribute)
26
+ end
27
+ ret[name] = [desc, attribute.type]
28
+ end
29
+ ret
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+
@@ -0,0 +1,25 @@
1
+ require 'threatinator/config/base'
2
+
3
+ module Threatinator
4
+ module Config
5
+ class FeedSearch < Threatinator::Config::Base
6
+ DEFAULT_FEED_PATH = File.expand_path("../../../../feeds", __FILE__)
7
+
8
+ attribute :exclude_default, Boolean, default: false,
9
+ description: 'Exclude default path from feed search path'
10
+
11
+ attribute :path, Array[String],
12
+ description: 'The paths to search for feeds'
13
+
14
+ # @return [Array<String>] An array of paths to search
15
+ def search_path
16
+ ret = self.path
17
+ if self.exclude_default == false
18
+ ret = ret + [DEFAULT_FEED_PATH]
19
+ end
20
+ ret
21
+ end
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,24 @@
1
+ module Threatinator
2
+ # Decodes/Extracts data from an input IO, producing a new IO. The decoder is
3
+ # initialized with a configuration, and then #decode is called upon an IO
4
+ # object.
5
+ class Decoder
6
+ attr_reader :encoding
7
+
8
+ # @param [Hash] opts An options hash
9
+ # @option opts [String] :encoding The encoding for the output IO. Defaults
10
+ # to "utf-8"
11
+ def initialize(opts = {})
12
+ @encoding = opts[:encoding] || "utf-8"
13
+ end
14
+
15
+ # Decodes an input IO, returning a brand new IO.
16
+ # @param [IO] io The IO to decode
17
+ # @return [IO] A new IO.
18
+ def decode(io)
19
+ #:nocov:
20
+ raise NotImplementedError.new("#{self.class}#decode not implemented!")
21
+ #:nocov:
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ require 'threatinator/decoder'
2
+ require 'threatinator/exceptions'
3
+ require 'zlib'
4
+ require 'tempfile'
5
+ require 'pp'
6
+
7
+ module Threatinator
8
+ module Decoders
9
+ class Gzip < Threatinator::Decoder
10
+ # Decompresses the io using Gzip.
11
+ # @param (see Threatinator::Decoder#decode)
12
+ def decode(io)
13
+ zio = Zlib::GzipReader.new(io, encoding: "binary")
14
+ tempfile = Tempfile.new("threatinator", encoding: "binary")
15
+ while chunk = zio.read(10240)
16
+ tempfile.write(chunk)
17
+ end
18
+
19
+ zio.close
20
+ io.close unless io.closed?
21
+ tempfile.rewind
22
+ tempfile.set_encoding(self.encoding)
23
+ tempfile
24
+ rescue Zlib::GzipFile::Error => e
25
+ raise Threatinator::Exceptions::DecoderError.new
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ require 'threatinator/property_definer'
2
+
3
+ module Threatinator
4
+ class Event
5
+ include Threatinator::PropertyDefiner
6
+
7
+ VALID_TYPES = Set.new([:c2, :attacker, :malware_host, :spamming, :scanning, :phishing])
8
+
9
+ def initialize(opts = {})
10
+ _parse_properties(opts)
11
+ end
12
+
13
+ property :feed_provider, type: String
14
+ property :feed_name, type: String
15
+ property :type, type: Symbol, validate: lambda { |obj, val| VALID_TYPES.include?(val) }
16
+ property :ipv4s, type: Array, default: lambda { Array.new }
17
+ property :fqdns, type: Array, default: lambda { Array.new }
18
+
19
+ def add_ipv4(ipv4)
20
+ self.ipv4s << ipv4
21
+ end
22
+
23
+ def add_fqdn(fqdn)
24
+ self.fqdns << fqdn
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ require 'threatinator/event'
2
+
3
+ module Threatinator
4
+ class EventBuilder
5
+ attr_reader :total
6
+ def initialize(feed)
7
+ @feed = feed
8
+ @built_events = []
9
+ @total = 0
10
+ end
11
+
12
+ def each_built_event
13
+ @built_events.each do |event|
14
+ yield event
15
+ end
16
+ @built_events.clear
17
+ end
18
+
19
+ def count
20
+ @built_events.count
21
+ end
22
+
23
+ def clear
24
+ @built_events.clear
25
+ end
26
+
27
+ def create_event_proc
28
+ self.method(:create_event).to_proc
29
+ end
30
+
31
+ def create_event
32
+ event = Threatinator::Event.new
33
+ event.feed_provider = @feed.provider
34
+ event.feed_name = @feed.name
35
+ yield(event)
36
+ @total += 1
37
+ @built_events << event
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,61 @@
1
+ module Threatinator
2
+ module Exceptions
3
+ # Indicates that a fetch failed.
4
+ class FetchFailed < StandardError
5
+ end
6
+
7
+ # Indicates that the decode operation failed
8
+ class DecoderError < StandardError
9
+ end
10
+
11
+ class ParseError < StandardError
12
+ end
13
+
14
+ class PluginLoadError < StandardError
15
+ attr_reader :cause
16
+ def initialize(message, cause = nil)
17
+ @cause = cause
18
+ unless cause.nil?
19
+ message = "#{message} : #{cause.class} : #{cause}"
20
+ self.set_backtrace cause.backtrace
21
+ end
22
+ super(message)
23
+ end
24
+ end
25
+
26
+ class UnknownPlugin < StandardError
27
+ end
28
+
29
+ class CouldNotFindOutputConfigError < StandardError
30
+ end
31
+
32
+ class InvalidAttributeError < StandardError
33
+ attr_reader :attribute, :got
34
+ def initialize(attribute, got)
35
+ @attribute = attribute
36
+ @got = got
37
+ super("Invalid value for attribute '#{attribute}'. Got " + got.inspect)
38
+ end
39
+ end
40
+
41
+ class AlreadyRegisteredError < StandardError
42
+ end
43
+
44
+ class UnknownFeed < StandardError
45
+ attr_reader :provider, :name
46
+ def initialize(provider, name)
47
+ @provider = provider
48
+ @name = name
49
+ super("Failed to find feed with provider '#{provider}' and name '#{name}'")
50
+ end
51
+ end
52
+
53
+ class FeedFileNotFoundError < StandardError
54
+ def initialize(filename)
55
+ @filename = filename
56
+ super("Failed to open/read feed file '#{filename}'")
57
+ end
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,82 @@
1
+ require 'threatinator/exceptions'
2
+
3
+ module Threatinator
4
+ class Feed
5
+ # @param [Hash] opts Options hash
6
+ # @option opts [String] :provider The name of the provider
7
+ # @option opts [String] :name The name of the feed
8
+ # @option opts [Proc] :parser_block A block that will be called by the
9
+ # parser each time it processes a record.
10
+ # @option opts [Proc] :parser_builder A proc that, when called, will
11
+ # return a brand new instance of a Threatinator::Parser.
12
+ # @option opts [Proc] :fetcher_builder A proc that, when called, will
13
+ # return a brand new instance of a Threatinator::Fetcher.
14
+ # @option opts [Array<Proc>] :filter_builders An array of procs that,
15
+ # when called, will each return an instance of a filter (something that
16
+ # responds to :filter?)
17
+ # @option opts [Array<Proc>] :decoder_builders An array of procs that,
18
+ # when called, will each return an instance of a Threatinator::Decoder
19
+ def initialize(opts = {})
20
+ @provider = opts.delete(:provider)
21
+ @name = opts.delete(:name)
22
+ @parser_block = opts.delete(:parser_block)
23
+
24
+ @parser_builder = opts.delete(:parser_builder)
25
+ @fetcher_builder = opts.delete(:fetcher_builder)
26
+ @filter_builders = opts.delete(:filter_builders) || []
27
+ @decoder_builders = opts.delete(:decoder_builders) || []
28
+ validate!
29
+ end
30
+
31
+ def provider
32
+ @provider.dup
33
+ end
34
+
35
+ def name
36
+ @name.dup
37
+ end
38
+
39
+ def parser_block
40
+ @parser_block
41
+ end
42
+
43
+ def fetcher_builder
44
+ @fetcher_builder
45
+ end
46
+
47
+ def parser_builder
48
+ @parser_builder
49
+ end
50
+
51
+ def filter_builders
52
+ @filter_builders.dup
53
+ end
54
+
55
+ def decoder_builders
56
+ @decoder_builders.dup
57
+ end
58
+
59
+ def validate!
60
+ validate_attribute!(:provider, @provider) { |x| x.kind_of?(::String) }
61
+ validate_attribute!(:name, @name) { |x| x.kind_of?(::String) }
62
+ validate_attribute!(:parser_block, @parser_block) { |x| x.kind_of?(::Proc) }
63
+ validate_attribute!(:fetcher_builder, @fetcher_builder) { |x| x.kind_of?(::Proc) }
64
+ validate_attribute!(:parser_builder, @parser_builder) { |x| x.kind_of?(::Proc) }
65
+ validate_attribute!(:filter_builders, @filter_builders) do |x|
66
+ x.kind_of?(::Array) &&
67
+ x.all? { |e| e.kind_of?(::Proc) }
68
+ end
69
+
70
+ validate_attribute!(:decoder_builders, @decoder_builders) do |x|
71
+ x.kind_of?(::Array) &&
72
+ x.all? { |e| e.kind_of?(::Proc) }
73
+ end
74
+ end
75
+
76
+ def validate_attribute!(name, val, &block)
77
+ unless block.call(val) == true
78
+ raise Threatinator::Exceptions::InvalidAttributeError.new(name, val)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,156 @@
1
+ require 'docile'
2
+ require 'threatinator/feed'
3
+ require 'threatinator/exceptions'
4
+ require 'threatinator/decoders/gzip'
5
+ require 'threatinator/fetchers/http'
6
+ require 'threatinator/parsers/getline'
7
+ require 'threatinator/parsers/csv'
8
+ require 'threatinator/parsers/json'
9
+ require 'threatinator/parsers/xml'
10
+ require 'threatinator/filters/block'
11
+ require 'threatinator/filters/whitespace'
12
+ require 'threatinator/filters/comments'
13
+
14
+ module Threatinator
15
+ class FeedBuilder
16
+ def provider(provider_name)
17
+ @provider = provider_name
18
+ self
19
+ end
20
+
21
+ def name(name)
22
+ @name = name
23
+ self
24
+ end
25
+
26
+ def fetch_http(url, opts = {})
27
+ opts[:url] = url
28
+ @fetcher_builder = lambda do
29
+ opts_dup = Marshal.load(Marshal.dump(opts))
30
+ Threatinator::Fetchers::Http.new(opts_dup)
31
+ end
32
+ self
33
+ end
34
+
35
+ def parse_xml(pattern_string, opts = {}, &block)
36
+ @parser_builder = lambda do
37
+ pattern = Threatinator::Parsers::XML::Pattern.new(pattern_string)
38
+ opts_dup = Marshal.load(Marshal.dump(opts))
39
+ opts_dup[:pattern] = pattern
40
+ Threatinator::Parsers::XML::Parser.new(opts_dup, &block)
41
+ end
42
+ @parser_block = block
43
+ self
44
+ end
45
+
46
+ def parse_json(opts = {}, &block)
47
+ @parser_builder = lambda do
48
+ opts_dup = Marshal.load(Marshal.dump(opts))
49
+ Threatinator::Parsers::JSON::Parser.new(opts_dup, &block)
50
+ end
51
+ @parser_block = block
52
+ self
53
+ end
54
+
55
+ def parse_eachline(opts = {}, &block)
56
+ @parser_builder = lambda do
57
+ opts_dup = Marshal.load(Marshal.dump(opts))
58
+ Threatinator::Parsers::Getline::Parser.new(opts_dup, &block)
59
+ end
60
+ @parser_block = block
61
+ self
62
+ end
63
+
64
+ def parse_csv(opts = {}, &block)
65
+ @parser_builder = lambda do
66
+ opts_dup = Marshal.load(Marshal.dump(opts))
67
+ Threatinator::Parsers::CSV::Parser.new(opts_dup, &block)
68
+ end
69
+ @parser_block = block
70
+ self
71
+ end
72
+
73
+ # Specify a block filter for the parser
74
+ def filter(&block)
75
+ @filter_builders ||= []
76
+ @filter_builders << lambda { Threatinator::Filters::Block.new(block) }
77
+ self
78
+ end
79
+
80
+ # Filter out whitespace lines. Only works on line-based text.
81
+ def filter_whitespace
82
+ @filter_builders ||= []
83
+ @filter_builders << lambda { Threatinator::Filters::Whitespace.new }
84
+ self
85
+ end
86
+
87
+ # Filter out whitespace lines. Only works on line-based text.
88
+ def filter_comments
89
+ @filter_builders ||= []
90
+ @filter_builders << lambda { Threatinator::Filters::Comments.new }
91
+ self
92
+ end
93
+
94
+ # Add the Gzip decoder
95
+ def decode_gzip
96
+ decoder_builders << lambda { Threatinator::Decoders::Gzip.new }
97
+ self
98
+ end
99
+ alias_method :extract_gzip, :decode_gzip
100
+ alias_method :gunzip, :decode_gzip
101
+
102
+ def decoder_builders
103
+ @decoder_builders ||= []
104
+ end
105
+ private :decoder_builders
106
+
107
+ def build
108
+ Feed.new(
109
+ :provider => @provider,
110
+ :name => @name,
111
+ :parser_block => @parser_block,
112
+ :fetcher_builder => @fetcher_builder,
113
+ :parser_builder => @parser_builder,
114
+ :filter_builders => @filter_builders,
115
+ :decoder_builders => decoder_builders
116
+ )
117
+ end
118
+
119
+ # Loads the provided file, and generates a builder from it.
120
+ # @param [String] filename The name of the file to read the feed from
121
+ # @raise [FeedFileNotFoundError] if the file is not found
122
+ def self.from_file(filename)
123
+ begin
124
+ filedata = File.read(filename)
125
+ rescue Errno::ENOENT
126
+ raise Threatinator::Exceptions::FeedFileNotFoundError.new(filename)
127
+ end
128
+ from_string(filedata, filename, 0)
129
+ end
130
+
131
+ # Generates a builder from a string via eval.
132
+ # @param [String] str The DSL code that specifies the feed.
133
+ # @param [String] filename (nil) Passed to eval.
134
+ # @param [String] lineno (nil) Passed to eval.
135
+ # @raise [FeedFileNotFoundError] if the file is not found
136
+ # @see Kernel#eval for details on filename and lineno
137
+ def self.from_string(str, filename = nil, lineno = nil)
138
+ from_dsl do
139
+ args = [str, binding]
140
+ unless filename.nil?
141
+ args << filename
142
+ unless lineno.nil?
143
+ args << lineno
144
+ end
145
+ end
146
+ eval(*args)
147
+ end
148
+ end
149
+
150
+ # Executes the block parameter within DSL scope
151
+ def self.from_dsl(&block)
152
+ Docile.dsl_eval(self.new, &block)
153
+ end
154
+ end
155
+ end
156
+