ronin-recon 0.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.document +4 -0
  3. data/.github/workflows/ruby.yml +46 -0
  4. data/.gitignore +20 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +44 -0
  7. data/.ruby-version +1 -0
  8. data/.yardopts +1 -0
  9. data/COPYING.txt +165 -0
  10. data/ChangeLog.md +36 -0
  11. data/Gemfile +62 -0
  12. data/README.md +391 -0
  13. data/Rakefile +74 -0
  14. data/bin/ronin-recon +16 -0
  15. data/data/completions/ronin-recon +95 -0
  16. data/data/templates/worker.rb.erb +67 -0
  17. data/data/wordlists/raft-small-directories.txt.gz +0 -0
  18. data/data/wordlists/subdomains-1000.txt.gz +0 -0
  19. data/examples/recon.rb +24 -0
  20. data/gemspec.yml +57 -0
  21. data/lib/ronin/recon/builtin/dns/lookup.rb +65 -0
  22. data/lib/ronin/recon/builtin/dns/mailservers.rb +64 -0
  23. data/lib/ronin/recon/builtin/dns/nameservers.rb +61 -0
  24. data/lib/ronin/recon/builtin/dns/reverse_lookup.rb +63 -0
  25. data/lib/ronin/recon/builtin/dns/srv_enum.rb +178 -0
  26. data/lib/ronin/recon/builtin/dns/subdomain_enum.rb +105 -0
  27. data/lib/ronin/recon/builtin/dns/suffix_enum.rb +168 -0
  28. data/lib/ronin/recon/builtin/net/ip_range_enum.rb +65 -0
  29. data/lib/ronin/recon/builtin/net/port_scan.rb +84 -0
  30. data/lib/ronin/recon/builtin/net/service_id.rb +75 -0
  31. data/lib/ronin/recon/builtin/ssl/cert_enum.rb +109 -0
  32. data/lib/ronin/recon/builtin/ssl/cert_grab.rb +76 -0
  33. data/lib/ronin/recon/builtin/ssl/cert_sh.rb +77 -0
  34. data/lib/ronin/recon/builtin/web/dir_enum.rb +121 -0
  35. data/lib/ronin/recon/builtin/web/email_addresses.rb +70 -0
  36. data/lib/ronin/recon/builtin/web/spider.rb +93 -0
  37. data/lib/ronin/recon/builtin.rb +34 -0
  38. data/lib/ronin/recon/cli/command.rb +40 -0
  39. data/lib/ronin/recon/cli/commands/completion.rb +61 -0
  40. data/lib/ronin/recon/cli/commands/irb.rb +57 -0
  41. data/lib/ronin/recon/cli/commands/new.rb +203 -0
  42. data/lib/ronin/recon/cli/commands/run.rb +420 -0
  43. data/lib/ronin/recon/cli/commands/test.rb +99 -0
  44. data/lib/ronin/recon/cli/commands/worker.rb +114 -0
  45. data/lib/ronin/recon/cli/commands/workers.rb +80 -0
  46. data/lib/ronin/recon/cli/debug_option.rb +45 -0
  47. data/lib/ronin/recon/cli/printing.rb +122 -0
  48. data/lib/ronin/recon/cli/ruby_shell.rb +51 -0
  49. data/lib/ronin/recon/cli/worker_command.rb +105 -0
  50. data/lib/ronin/recon/cli.rb +50 -0
  51. data/lib/ronin/recon/config.rb +371 -0
  52. data/lib/ronin/recon/dns_worker.rb +41 -0
  53. data/lib/ronin/recon/engine.rb +639 -0
  54. data/lib/ronin/recon/exceptions.rb +45 -0
  55. data/lib/ronin/recon/graph.rb +127 -0
  56. data/lib/ronin/recon/importer.rb +224 -0
  57. data/lib/ronin/recon/input_file.rb +81 -0
  58. data/lib/ronin/recon/message/job_completed.rb +60 -0
  59. data/lib/ronin/recon/message/job_failed.rb +69 -0
  60. data/lib/ronin/recon/message/job_started.rb +60 -0
  61. data/lib/ronin/recon/message/shutdown.rb +38 -0
  62. data/lib/ronin/recon/message/value.rb +76 -0
  63. data/lib/ronin/recon/message/worker_started.rb +51 -0
  64. data/lib/ronin/recon/message/worker_stopped.rb +51 -0
  65. data/lib/ronin/recon/mixins/dns.rb +639 -0
  66. data/lib/ronin/recon/mixins/http.rb +58 -0
  67. data/lib/ronin/recon/mixins.rb +21 -0
  68. data/lib/ronin/recon/output_formats/dir.rb +94 -0
  69. data/lib/ronin/recon/output_formats/dot.rb +155 -0
  70. data/lib/ronin/recon/output_formats/graph_format.rb +48 -0
  71. data/lib/ronin/recon/output_formats/graphviz_format.rb +115 -0
  72. data/lib/ronin/recon/output_formats/pdf.rb +43 -0
  73. data/lib/ronin/recon/output_formats/png.rb +43 -0
  74. data/lib/ronin/recon/output_formats/svg.rb +43 -0
  75. data/lib/ronin/recon/output_formats.rb +48 -0
  76. data/lib/ronin/recon/registry.rb +35 -0
  77. data/lib/ronin/recon/root.rb +33 -0
  78. data/lib/ronin/recon/scope.rb +112 -0
  79. data/lib/ronin/recon/value/parser.rb +113 -0
  80. data/lib/ronin/recon/value.rb +110 -0
  81. data/lib/ronin/recon/value_status.rb +87 -0
  82. data/lib/ronin/recon/values/cert.rb +168 -0
  83. data/lib/ronin/recon/values/domain.rb +88 -0
  84. data/lib/ronin/recon/values/email_address.rb +114 -0
  85. data/lib/ronin/recon/values/host.rb +137 -0
  86. data/lib/ronin/recon/values/ip.rb +123 -0
  87. data/lib/ronin/recon/values/ip_range.rb +155 -0
  88. data/lib/ronin/recon/values/mailserver.rb +61 -0
  89. data/lib/ronin/recon/values/nameserver.rb +61 -0
  90. data/lib/ronin/recon/values/open_port.rb +190 -0
  91. data/lib/ronin/recon/values/url.rb +218 -0
  92. data/lib/ronin/recon/values/website.rb +200 -0
  93. data/lib/ronin/recon/values/wildcard.rb +140 -0
  94. data/lib/ronin/recon/values.rb +32 -0
  95. data/lib/ronin/recon/version.rb +26 -0
  96. data/lib/ronin/recon/web_worker.rb +35 -0
  97. data/lib/ronin/recon/worker.rb +433 -0
  98. data/lib/ronin/recon/worker_pool.rb +203 -0
  99. data/lib/ronin/recon/workers.rb +260 -0
  100. data/lib/ronin/recon.rb +22 -0
  101. data/man/ronin-recon-completion.1 +76 -0
  102. data/man/ronin-recon-completion.1.md +78 -0
  103. data/man/ronin-recon-irb.1 +27 -0
  104. data/man/ronin-recon-irb.1.md +26 -0
  105. data/man/ronin-recon-new.1 +58 -0
  106. data/man/ronin-recon-new.1.md +59 -0
  107. data/man/ronin-recon-run.1 +137 -0
  108. data/man/ronin-recon-run.1.md +115 -0
  109. data/man/ronin-recon-test.1 +53 -0
  110. data/man/ronin-recon-test.1.md +55 -0
  111. data/man/ronin-recon-worker.1 +32 -0
  112. data/man/ronin-recon-worker.1.md +34 -0
  113. data/man/ronin-recon-workers.1 +29 -0
  114. data/man/ronin-recon-workers.1.md +31 -0
  115. data/man/ronin-recon.1 +57 -0
  116. data/man/ronin-recon.1.md +57 -0
  117. data/ronin-recon.gemspec +62 -0
  118. data/scripts/setup +58 -0
  119. metadata +364 -0
@@ -0,0 +1,639 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-recon - A micro-framework and tool for performing reconnaissance.
4
+ #
5
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
6
+ #
7
+ # ronin-recon is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-recon is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-recon. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/recon/config'
22
+ require 'ronin/recon/workers'
23
+ require 'ronin/recon/worker_pool'
24
+ require 'ronin/recon/value_status'
25
+ require 'ronin/recon/graph'
26
+ require 'ronin/recon/scope'
27
+ require 'ronin/recon/message/worker_started'
28
+ require 'ronin/recon/message/worker_stopped'
29
+ require 'ronin/recon/message/job_started'
30
+ require 'ronin/recon/message/job_completed'
31
+ require 'ronin/recon/message/job_failed'
32
+ require 'ronin/recon/message/value'
33
+ require 'ronin/recon/message/shutdown'
34
+
35
+ require 'set'
36
+ require 'console/logger'
37
+ require 'async'
38
+ require 'async/queue'
39
+
40
+ module Ronin
41
+ module Recon
42
+ #
43
+ # The recon engine which enqueues and dequeues values from workers.
44
+ #
45
+ class Engine
46
+
47
+ # The configuration for the engine.
48
+ #
49
+ # @return [Config]
50
+ attr_reader :config
51
+
52
+ # The workers to use.
53
+ #
54
+ # @return [Workers]
55
+ attr_reader :workers
56
+
57
+ # The scope to constrain recon to.
58
+ #
59
+ # @return [Scope]
60
+ attr_reader :scope
61
+
62
+ # The maximum depth to recon.
63
+ #
64
+ # @return [Integer, nil]
65
+ #
66
+ # @api public
67
+ attr_reader :max_depth
68
+
69
+ # The status of all values in the queue.
70
+ #
71
+ # @return [ValueStatus]
72
+ attr_reader :value_status
73
+
74
+ # The recon engine graph of discovered values.
75
+ #
76
+ # @return [Graph]
77
+ #
78
+ # @api public
79
+ attr_reader :graph
80
+
81
+ # The common logger for the engine.
82
+ #
83
+ # @return [Console::Logger]
84
+ #
85
+ # @api private
86
+ attr_reader :logger
87
+
88
+ #
89
+ # Initializes the recon engine.
90
+ #
91
+ # @param [Array<Values::Value>] values
92
+ # The values to start performing recon on.
93
+ #
94
+ # @param [Array<Values::Value>] ignore
95
+ # The values to ignore while performing recon.
96
+ #
97
+ # @param [Integer, nil] max_depth
98
+ # The maximum depth to limit recon to. If not specified recon will
99
+ # continue until there are no more new values discovered.
100
+ #
101
+ # @param [String, nil] config_file
102
+ # The path to the configuration file.
103
+ #
104
+ # @param [Config, nil] config
105
+ # The configuration for the engine. If specified, it will override
106
+ # `config_file:`.
107
+ #
108
+ # @param [Workers, Array<Class<Worker>>, nil] workers
109
+ # The worker classes to use. If specified, it will override the workers
110
+ # specified in `config.workers`.
111
+ #
112
+ # @param [Console::Logger] logger
113
+ # The common logger for the recon engine.
114
+ #
115
+ # @yield [self]
116
+ # If a block is given it will be passed the newly created engine.
117
+ #
118
+ # @yieldparam [Engine] self
119
+ # The newly initialized engine.
120
+ #
121
+ # @yieldparam [Values::Value] parent
122
+ # The parent value which is associated to the discovered value.
123
+ #
124
+ # @api public
125
+ #
126
+ def initialize(values, ignore: [],
127
+ max_depth: nil,
128
+ config: nil,
129
+ config_file: nil,
130
+ workers: nil,
131
+ logger: Console.logger)
132
+ @config = if config then config
133
+ elsif config_file then Config.load(config_file)
134
+ else Config.default
135
+ end
136
+ @workers = workers || Workers.load(@config.workers)
137
+ @logger = logger
138
+
139
+ @scope = Scope.new(values, ignore: ignore)
140
+ @max_depth = max_depth
141
+
142
+ @on_value_callbacks = []
143
+ @on_connection_callbacks = []
144
+ @on_job_started_callbacks = []
145
+ @on_job_completed_callbacks = []
146
+ @on_job_failed_callbacks = []
147
+
148
+ @value_status = ValueStatus.new
149
+ @graph = Graph.new
150
+
151
+ yield self if block_given?
152
+
153
+ @worker_classes = {}
154
+ @worker_pools = {}
155
+ @worker_pool_count = 0
156
+ @output_queue = Async::Queue.new
157
+
158
+ @workers.each do |worker_class|
159
+ add_worker(worker_class)
160
+ end
161
+ end
162
+
163
+ #
164
+ # The discovered recon values.
165
+ #
166
+ # @return [Set<Value>]
167
+ # The set of discovered recon values.
168
+ #
169
+ # @api public
170
+ #
171
+ def values
172
+ @graph.nodes
173
+ end
174
+
175
+ #
176
+ # Runs the recon engine with the given initial values.
177
+ #
178
+ # @param [Array<Value>] values
179
+ # The initial values to start the recon engine with.
180
+ #
181
+ # @param [Hash{Symbol => Object}] kwargs
182
+ # Additional keyword arguments for {#initialize}.
183
+ #
184
+ # @yield [value, (value, parent)]
185
+ # The given block will be passed each discovered value during recon.
186
+ # If the block accepts two arguments the value and it's parent value
187
+ # will be passed to the block.
188
+ #
189
+ # @yieldparam [Values::Value] value
190
+ # A value discovered by one of the recon workers.
191
+ #
192
+ # @yieldparam [Values::Value] parent
193
+ # The parent value which is associated to the discovered value.
194
+ #
195
+ # @return [Engine]
196
+ # The engine instance.
197
+ #
198
+ # @api public
199
+ #
200
+ def self.run(values,**kwargs,&block)
201
+ engine = new(values,**kwargs,&block)
202
+
203
+ # start the engine in it's own Async task
204
+ Async do |task|
205
+ # start the engine
206
+ engine.run(task)
207
+ end
208
+
209
+ return engine
210
+ end
211
+
212
+ #
213
+ # The main recon engine event loop.
214
+ #
215
+ # @param [Async::Task] task
216
+ # The parent async task.
217
+ #
218
+ # @api private
219
+ #
220
+ def run(task=Async::Task.current)
221
+ # enqueue the scope values for processing
222
+ # rubocop:disable Style/HashEachMethods
223
+ @scope.values.each do |value|
224
+ enqueue_value(value)
225
+ end
226
+ # rubocop:enable Style/HashEachMethods
227
+
228
+ # output consumer task
229
+ task.async do
230
+ until (@value_status.empty? && @output_queue.empty?)
231
+ process(@output_queue.dequeue)
232
+ end
233
+
234
+ shutdown!
235
+ end
236
+
237
+ # start all work groups
238
+ @worker_pools.each_value do |worker_pools|
239
+ worker_pools.each do |worker_pool|
240
+ worker_pool.start(task)
241
+ end
242
+ end
243
+ end
244
+
245
+ #
246
+ # Adds a worker class to the engine.
247
+ #
248
+ # @param [Class<Worker>] worker_class
249
+ # The worker class.
250
+ #
251
+ # @param [Hash{Symbol => Object}, nil] params
252
+ # Additional params for {Worker#initialize}.
253
+ #
254
+ # @api private
255
+ #
256
+ def add_worker(worker_class, params: nil, concurrency: nil)
257
+ params ||= @config.params[worker_class.id]
258
+ concurrency ||= @config.concurrency[worker_class.id]
259
+
260
+ worker = worker_class.new(params: params)
261
+ worker_pool = WorkerPool.new(worker, concurrency: concurrency,
262
+ output_queue: @output_queue,
263
+ logger: @logger)
264
+
265
+ worker_class.accepts.each do |value_class|
266
+ (@worker_classes[value_class] ||= []) << worker_class
267
+ (@worker_pools[value_class] ||= []) << worker_pool
268
+ end
269
+ end
270
+
271
+ #
272
+ # Registers a callback for the given event.
273
+ #
274
+ # @param [:value, :connection, :job_started, :job_completed, :job_failed] event
275
+ # The event type to register the callback for.
276
+ #
277
+ # @yield [value, (value, parent), (worker_class, value, parent)]
278
+ # If `:value` is given, then the given block will be passed each new value.
279
+ #
280
+ # @yield [(value, parent), (worker_class, value, parent)]
281
+ # If `:connection` is given, then the given block will be passed the
282
+ # discovered value and it's parent value.
283
+ #
284
+ # @yield [worker_class, value]
285
+ # If `:job_started` is given, then the given block will be passed the
286
+ # worker class and the input value.
287
+ #
288
+ # @yield [worker_class, value]
289
+ # If `:job_completed` is given, then the given block will be passed the
290
+ # worker class and the input value.
291
+ #
292
+ # @yield [worker_class, value, exception]
293
+ # If `:job_failed` is given, then any exception raised by a worker will
294
+ # be passed to the given block.
295
+ #
296
+ # @yieldparam [Values::Value] value
297
+ # A discovered value value.
298
+ #
299
+ # @yieldparam [Values::Value] parent
300
+ # The parent value of the value.
301
+ #
302
+ # @yieldparam [Class<Worker>] worker_class
303
+ # The worker class.
304
+ #
305
+ # @yieldparam [RuntimeError] exception
306
+ # An exception that was raised by a worker.
307
+ #
308
+ # @api public
309
+ #
310
+ def on(event,&block)
311
+ case event
312
+ when :value then @on_value_callbacks << block
313
+ when :connection then @on_connection_callbacks << block
314
+ when :job_started then @on_job_started_callbacks << block
315
+ when :job_completed then @on_job_completed_callbacks << block
316
+ when :job_failed then @on_job_failed_callbacks << block
317
+ else
318
+ raise(ArgumentError,"unsupported event type: #{event.inspect}")
319
+ end
320
+ end
321
+
322
+ private
323
+
324
+ #
325
+ # Calls the `on(:job_started) { ... }` callbacks.
326
+ #
327
+ # @param [Worker] worker
328
+ # The worker that is processing the value.
329
+ #
330
+ # @param [Values::Value] value
331
+ # The value that is being processed.
332
+ #
333
+ # @api private
334
+ #
335
+ def on_job_started(worker,value)
336
+ @on_job_started_callbacks.each do |callback|
337
+ callback.call(worker.class,value)
338
+ end
339
+ end
340
+
341
+ #
342
+ # Calls the `on(:job_completed) { ... }` callbacks.
343
+ #
344
+ # @param [Worker] worker
345
+ # The worker that processed the value.
346
+ #
347
+ # @param [Values::Value] value
348
+ # The value that was processed.
349
+ #
350
+ # @api private
351
+ #
352
+ def on_job_completed(worker,value)
353
+ @on_job_completed_callbacks.each do |callback|
354
+ callback.call(worker.class,value)
355
+ end
356
+ end
357
+
358
+ #
359
+ # Calls the `on(:job_failed) { ... }` callbacks.
360
+ #
361
+ # @param [Worker] worker
362
+ # The worker that raised the exception.
363
+ #
364
+ # @param [Values::Value] value
365
+ # The value that was being processed.
366
+ #
367
+ # @param [RuntimeError] exception
368
+ # The exception raised by the worker.
369
+ #
370
+ # @api private
371
+ #
372
+ def on_job_failed(worker,value,exception)
373
+ @on_job_failed_callbacks.each do |callback|
374
+ callback.call(worker.class,value,exception)
375
+ end
376
+ end
377
+
378
+ #
379
+ # Calls the `on(:value) { ... }` callbacks.
380
+ #
381
+ # @param [Worker] worker
382
+ # The worker that discovered the value.
383
+ #
384
+ # @param [Values::Value] value
385
+ # The newly discovered value.
386
+ #
387
+ # @param [Values::Value] parent
388
+ # The parent value associated with the new value.
389
+ #
390
+ # @api private
391
+ #
392
+ def on_value(worker,value,parent)
393
+ @on_value_callbacks.each do |callback|
394
+ case callback.arity
395
+ when 1 then callback.call(value)
396
+ when 2 then callback.call(value,parent)
397
+ else callback.call(worker.class,value,parent)
398
+ end
399
+ end
400
+ end
401
+
402
+ #
403
+ # Calls the `on(:connection) { ... }` callbacks.
404
+ #
405
+ # @param [Worker] worker
406
+ # The worker that discovered the value.
407
+ #
408
+ # @param [Values::Value] value
409
+ # The discovered value.
410
+ #
411
+ # @param [Values::Value] parent
412
+ # The parent value associated with the value.
413
+ #
414
+ # @api private
415
+ #
416
+ def on_connection(worker,value,parent)
417
+ @on_connection_callbacks.each do |callback|
418
+ case callback.arity
419
+ when 2 then callback.call(value,parent)
420
+ else callback.call(worker.class,value,parent)
421
+ end
422
+ end
423
+ end
424
+
425
+ #
426
+ # Processes a message.
427
+ #
428
+ # @param [Message::WorkerStarted, Message::WorkerStopped, Message::JobStarted, Message::JobCompleted, Message::JobFailed, Message::Value] mesg
429
+ # A queue message to process.
430
+ #
431
+ # @raise [NotImplementedError]
432
+ # An unknown message type was given.
433
+ #
434
+ # @api private
435
+ #
436
+ def process(mesg)
437
+ case mesg
438
+ when Message::WorkerStarted then process_worker_started(mesg)
439
+ when Message::WorkerStopped then process_worker_stopped(mesg)
440
+ when Message::JobStarted then process_job_started(mesg)
441
+ when Message::JobCompleted then process_job_completed(mesg)
442
+ when Message::JobFailed then process_job_failed(mesg)
443
+ when Message::Value then process_value(mesg)
444
+ else
445
+ raise(NotImplementedError,"unable to process message: #{mesg.inspect}")
446
+ end
447
+ end
448
+
449
+ #
450
+ # Handles when a worker has started.
451
+ #
452
+ # @param [Message::WorkerStarted] mesg
453
+ # The worker started message.
454
+ #
455
+ # @api private
456
+ #
457
+ def process_worker_started(mesg)
458
+ @logger.debug("Worker started: #{mesg.worker}")
459
+ @worker_pool_count += 1
460
+ end
461
+
462
+ #
463
+ # Handles when a worker has stopped.
464
+ #
465
+ # @param [Message::WorkerStopped] mesg
466
+ # The worker stopped message.
467
+ #
468
+ # @api private
469
+ #
470
+ def process_worker_stopped(mesg)
471
+ @logger.debug("Worker shutdown: #{mesg.worker}")
472
+ @worker_pool_count -= 1
473
+ end
474
+
475
+ #
476
+ # Handles when a worker job is started.
477
+ #
478
+ # @param [Message::JobStarted] mesg
479
+ # The job started message.
480
+ #
481
+ # @api private
482
+ #
483
+ def process_job_started(mesg)
484
+ worker = mesg.worker
485
+ value = mesg.value
486
+
487
+ @logger.debug("Job started: #{worker.class} #{value.inspect}")
488
+ on_job_started(worker,value)
489
+
490
+ @value_status.job_started(worker.class,value)
491
+ end
492
+
493
+ #
494
+ # Handles when a worker job is completed.
495
+ #
496
+ # @param [Message::JobStarted] mesg
497
+ # The job completed message.
498
+ #
499
+ # @api private
500
+ #
501
+ def process_job_completed(mesg)
502
+ worker = mesg.worker
503
+ value = mesg.value
504
+
505
+ @logger.debug("Job completed: #{worker.class} #{value.inspect}")
506
+ on_job_completed(worker,value)
507
+
508
+ @value_status.job_completed(worker.class,value)
509
+ end
510
+
511
+ #
512
+ # Handles when a worker job fails.
513
+ #
514
+ # @param [Message::JobFailed] mesg
515
+ # The job failed message.
516
+ #
517
+ # @api private
518
+ #
519
+ def process_job_failed(mesg)
520
+ worker = mesg.worker
521
+ value = mesg.value
522
+ exception = mesg.exception
523
+
524
+ @logger.debug("Job failed: #{worker.class} #{value.inspect} #{exception.inspect}")
525
+ on_job_failed(worker,value,exception)
526
+
527
+ @value_status.job_failed(worker.class,value)
528
+ end
529
+
530
+ #
531
+ # Handles when a value is received.
532
+ #
533
+ # @param [Message::Value] mesg
534
+ # The value message.
535
+ #
536
+ # @api private
537
+ #
538
+ def process_value(mesg)
539
+ worker = mesg.worker
540
+ value = mesg.value
541
+ parent = mesg.parent
542
+
543
+ @logger.debug("Output value dequeued: #{worker.class} #{value.inspect}")
544
+
545
+ # check if the new value is "in scope"
546
+ if @scope.include?(value)
547
+ # check if the value hasn't been seen yet?
548
+ if @graph.add_node(value)
549
+ @logger.debug("Added value #{value.inspect} to graph")
550
+ on_value(worker,value,parent)
551
+
552
+ # check if the message has exceeded the max depth
553
+ if @max_depth.nil? || mesg.depth < @max_depth
554
+ @logger.debug("Re-enqueueing value: #{value.inspect} ...")
555
+
556
+ # feed the message back into the engine
557
+ enqueue_mesg(mesg)
558
+ end
559
+ end
560
+
561
+ if @graph.add_edge(value,parent)
562
+ @logger.debug("Added a new connection between #{value.inspect} and #{parent.inspect} to the graph")
563
+ on_connection(worker,value,parent)
564
+ end
565
+ end
566
+ end
567
+
568
+ #
569
+ # Enqueues a message for processing.
570
+ #
571
+ # @param [Message::Value, Message::SHUTDOWN] mesg
572
+ # The message object.
573
+ #
574
+ # @raise [NotImplementedError]
575
+ # An unsupported message type was given.
576
+ #
577
+ # @api private
578
+ #
579
+ def enqueue_mesg(mesg)
580
+ case mesg
581
+ when Message::Value
582
+ value = mesg.value
583
+
584
+ if (worker_classes = @worker_classes[value.class])
585
+ worker_classes.each do |worker_class|
586
+ @logger.debug("Value enqueued: #{worker_class} #{value.inspect}")
587
+
588
+ @value_status.value_enqueued(worker_class,value)
589
+ end
590
+
591
+ @worker_pools[value.class].each do |worker_pool|
592
+ worker_pool.enqueue_mesg(mesg)
593
+ end
594
+ end
595
+ when Message::SHUTDOWN
596
+ @logger.debug("Shutting down ...")
597
+
598
+ @worker_pools.each_value do |worker_pools|
599
+ worker_pools.each do |worker_pool|
600
+ @logger.debug("Shutting down worker: #{worker_pool.worker} ...")
601
+
602
+ worker_pool.enqueue_mesg(mesg)
603
+ end
604
+ end
605
+ else
606
+ raise(NotImplementedError,"unable to handle message: #{mesg.inspect}")
607
+ end
608
+ end
609
+
610
+ #
611
+ # Sends a new value into the recon engine for processing.
612
+ #
613
+ # @param [Values::Value] value
614
+ # The value object to enqueue.
615
+ #
616
+ # @api private
617
+ #
618
+ def enqueue_value(value)
619
+ @graph.add_node(value)
620
+ enqueue_mesg(Message::Value.new(value))
621
+ end
622
+
623
+ #
624
+ # Sends the shutdown message and waits for all worker pools to shutdown.
625
+ #
626
+ # @api private
627
+ #
628
+ def shutdown!
629
+ enqueue_mesg(Message::SHUTDOWN)
630
+
631
+ # wait until all workers report that they have exited
632
+ until @worker_pool_count == 0
633
+ process(@output_queue.dequeue)
634
+ end
635
+ end
636
+
637
+ end
638
+ end
639
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
4
+ #
5
+ # ronin-support is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # ronin-support is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public License
16
+ # along with ronin-support. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ module Ronin
20
+ module Recon
21
+ #
22
+ # Base class for all {Ronin::Recon} exceptions.
23
+ #
24
+ class Exception < RuntimeError
25
+ end
26
+
27
+ #
28
+ # Indicates that a value string could not be parsed.
29
+ #
30
+ class UnknownValue < Exception
31
+ end
32
+
33
+ #
34
+ # Indicates invalid YAML configuration.
35
+ #
36
+ class InvalidConfig < Exception
37
+ end
38
+
39
+ #
40
+ # Indicates an invalid YAML configuration file.
41
+ #
42
+ class InvalidConfigFile < InvalidConfig
43
+ end
44
+ end
45
+ end