recog 0.02 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/.rspec +2 -1
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/Gemfile +3 -1
- data/README.md +12 -12
- data/Rakefile +22 -0
- data/bin/recog_verify.rb +1 -1
- data/features/match.feature +2 -2
- data/features/verify.feature +10 -7
- data/features/xml/no_tests.xml +0 -50
- data/features/xml/successful_tests.xml +7 -22
- data/features/xml/tests_with_failures.xml +10 -0
- data/features/xml/tests_with_warnings.xml +7 -0
- data/lib/recog/db.rb +26 -10
- data/lib/recog/db_manager.rb +1 -1
- data/lib/recog/fingerprint.rb +118 -34
- data/lib/recog/fingerprint/regexp_factory.rb +39 -0
- data/lib/recog/fingerprint/test.rb +13 -0
- data/lib/recog/matcher.rb +3 -3
- data/lib/recog/nizer.rb +16 -23
- data/lib/recog/verifier.rb +10 -25
- data/lib/recog/verifier_factory.rb +1 -1
- data/lib/recog/verify_reporter.rb +1 -1
- data/lib/recog/version.rb +1 -1
- data/recog.gemspec +12 -3
- data/spec/data/test_fingerprints.xml +12 -0
- data/spec/lib/fingerprint_self_test_spec.rb +8 -4
- data/spec/lib/{db_spec.rb → recog/db_spec.rb} +19 -7
- data/spec/lib/recog/fingerprint/regexp_factory.rb +61 -0
- data/spec/lib/recog/fingerprint_spec.rb +5 -0
- data/spec/lib/{formatter_spec.rb → recog/formatter_spec.rb} +1 -1
- data/spec/lib/{match_reporter_spec.rb → recog/match_reporter_spec.rb} +10 -9
- data/spec/lib/{nizer_spec.rb → recog/nizer_spec.rb} +5 -5
- data/spec/lib/{verify_reporter_spec.rb → recog/verify_reporter_spec.rb} +8 -7
- data/spec/spec_helper.rb +82 -0
- data/xml/apache_os.xml +48 -2
- data/xml/http_servers.xml +38 -6
- data/xml/ntp_banners.xml +4 -3
- data/xml/smb_native_os.xml +32 -32
- data/xml/smtp_expn.xml +1 -0
- data/xml/smtp_help.xml +2 -1
- data/xml/snmp_sysdescr.xml +164 -24
- data/xml/ssh_banners.xml +7 -3
- metadata +56 -8
- data/Gemfile.lock +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4177857ea00bc92010990ba3dd3d16a9df376773
|
4
|
+
data.tar.gz: 0e75995b2330ce2f6425c4c3b465bdee53644188
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36c37a2bf118bd25a395d477d6d5b29a80e145fd2aaf09f33b522ef6678b79daaf86783293f5d06d06a63ba068aa05ca4425e77308cc6362b3ade01f41b38d03
|
7
|
+
data.tar.gz: 59fd715fe36dee81b72b98dfe2e2426681e67fd2d651df56feeb8918f3d215eb91a13fad2ee1ed8813fac6947061eb328e8b5314b0364c4cef187c72ab0937f1
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.travis.yml
CHANGED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,7 @@ Recog: A Recognition Framework
|
|
3
3
|
|
4
4
|
Recog is a framework for identifying products, services, operating systems, and hardware by matching fingerprints against data returned from various network probes. Recog makes it simply to extract useful information from web server banners, snmp system description fields, and a whole lot more. Recog is open source, please see the [LICENSE](https://github.com/recog/LICENSE) file for more information.
|
5
5
|
|
6
|
-
[![Build Status](https://travis-ci.org/rapid7/recog.png)](https://travis-ci.org/rapid7/recog)
|
6
|
+
[![Build Status](https://travis-ci.org/rapid7/recog.png)](https://travis-ci.org/rapid7/recog)
|
7
7
|
==
|
8
8
|
|
9
9
|
## Installation
|
@@ -16,18 +16,18 @@ Recog consists of both XML fingerprint files and an assortment of code, mostly i
|
|
16
16
|
|
17
17
|
## Maturity
|
18
18
|
|
19
|
-
Please note that while the XML fingerprints themselves are quite stable and well-tested, the Ruby codebase in Recog is still fairly new and subject to change quickly. Please contact us (research[at]rapid7.com) before leveraging the Recog code within any production projects.
|
19
|
+
Please note that while the XML fingerprints themselves are quite stable and well-tested, the Ruby codebase in Recog is still fairly new and subject to change quickly. Please contact us (research[at]rapid7.com) before leveraging the Recog code within any production projects.
|
20
20
|
|
21
21
|
## Fingerprints
|
22
22
|
|
23
|
-
The fingerprints within Recog are stored in XML files, each of which is designed to match a specific protocol response string or field. For example, the file [ssh_banners.xml](https://github.com/recog/xml/ssh_banners.xml) can determine the os, vendor, and sometimes hardware product by matching the initial SSH daemon banner string.
|
23
|
+
The fingerprints within Recog are stored in XML files, each of which is designed to match a specific protocol response string or field. For example, the file [ssh_banners.xml](https://github.com/rapid7/recog/blob/master/xml/ssh_banners.xml) can determine the os, vendor, and sometimes hardware product by matching the initial SSH daemon banner string.
|
24
24
|
|
25
25
|
A fingerprint file consists of an XML document like the following:
|
26
26
|
|
27
27
|
01: <?xml version="1.0"?>
|
28
|
-
02:
|
28
|
+
02:
|
29
29
|
03: <fingerprints matches="ssh.banner">
|
30
|
-
04:
|
30
|
+
04:
|
31
31
|
05: <fingerprint pattern="^RomSShell_([\d\.]+)$">
|
32
32
|
06: <description>Allegro RomSShell SSH</description>
|
33
33
|
07: <example>RomSShell_4.62</example>
|
@@ -35,25 +35,25 @@ A fingerprint file consists of an XML document like the following:
|
|
35
35
|
09: <param pos="0" name="service.product" value="RomSShell"/>
|
36
36
|
10: <param pos="1" name="service.version"/>
|
37
37
|
11: </fingerprint>
|
38
|
-
12:
|
38
|
+
12:
|
39
39
|
13: </fingerprints>
|
40
40
|
|
41
|
-
The first line should always consist of the XML version declaration. The first element should always be a <fingerpints/> block with a `matches` attribute indicating what this fingerprint file is supposed to match. The `matches` attribute is normally in the form of protocol.field.
|
41
|
+
The first line should always consist of the XML version declaration. The first element should always be a <fingerpints/> block with a `matches` attribute indicating what this fingerprint file is supposed to match. The `matches` attribute is normally in the form of protocol.field.
|
42
42
|
|
43
|
-
Inside of the <fingerprints/> element there should be one or more <fingerprint/> elements. Every fingerprint should contain a `pattern` attribute, which contains the regular expression to be used against the match key.
|
43
|
+
Inside of the <fingerprints/> element there should be one or more <fingerprint/> elements. Every fingerprint should contain a `pattern` attribute, which contains the regular expression to be used against the match key.
|
44
44
|
|
45
|
-
Inside of the fingerprint, a <description/> element should contain a human-readable string describing this fingerprint.
|
45
|
+
Inside of the fingerprint, a <description/> element should contain a human-readable string describing this fingerprint.
|
46
46
|
|
47
47
|
The <example/> element should contain a successful match for the fingerprint's `pattern`. Multiple <example/> elements are preferred, as these elements are used for the built-in regression testing suite.
|
48
48
|
|
49
49
|
the <param/> elements contain a `pos` attribute, which indicates what capture field from the `pattern` should be extracted, or `0` for a static string. The `name` attribute is the key that will be reported in the case of a successful match and the `value` will either be a static string for `pos` values of `0` or missing and taken from the captured field.
|
50
50
|
|
51
51
|
Once a fingerprint has been added, the <examples/> entries can be tested by executing `bin/recog_verify.rb` against the fingerprint file:
|
52
|
-
|
52
|
+
|
53
53
|
$ bin/recog_verify.rb xml/ssh_banners.xml
|
54
|
-
|
54
|
+
|
55
55
|
Matches can be tested on the command-line in a similar fashion:
|
56
|
-
|
56
|
+
|
57
57
|
$ echo 'OpenSSH_6.6p1 Ubuntu-2ubuntu1' | bin/recog_match.rb xml/ssh_banners.xml -
|
58
58
|
MATCH: {"service.version"=>"6.6p1", "openssh.comment"=>"Ubuntu-2ubuntu1", "service.vendor"=>"OpenBSD", "service.family"=>"OpenSSH", "service.product"=>"OpenSSH", "data"=>"OpenSSH_6.6p1 Ubuntu-2ubuntu1"}
|
59
59
|
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
RSpec::Core::RakeTask.new do |t|
|
5
|
+
t.pattern = "spec/**/*_spec.rb"
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'yard'
|
9
|
+
require 'yard/rake/yardoc_task'
|
10
|
+
YARD::Rake::YardocTask.new do |t|
|
11
|
+
t.files = ['lib/**/*.rb', '-', 'README.md']
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'cucumber'
|
15
|
+
require 'cucumber/rake/task'
|
16
|
+
|
17
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
18
|
+
t.cucumber_opts = "features --format pretty"
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => [ :spec, :features, :yard ]
|
22
|
+
|
data/bin/recog_verify.rb
CHANGED
@@ -14,7 +14,7 @@ option_parser = OptionParser.new do |opts|
|
|
14
14
|
opts.separator ""
|
15
15
|
opts.separator "Options"
|
16
16
|
|
17
|
-
opts.on("-f", "--format FORMATTER",
|
17
|
+
opts.on("-f", "--format FORMATTER",
|
18
18
|
"Choose a formatter.",
|
19
19
|
" [s]ummary (default - failure/warning msgs and summary)",
|
20
20
|
" [d]etail (fingerprint name with tests and expanded summary)") do |format|
|
data/features/match.feature
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Feature: Match
|
2
2
|
Scenario: Finds matches
|
3
|
-
When I run `
|
3
|
+
When I run `recog_match.rb matching_banners_fingerprints.xml banners.xml`
|
4
4
|
Then it should pass with:
|
5
5
|
"""
|
6
6
|
MATCH: {"pureftpd.config"=>"[privsep] [TLS] ", "service.family"=>"Pure-FTPd", "service.product"=>"Pure-FTPd", "data"=>"---------- Welcome to Pure-FTPd [privsep] [TLS] ----------"}
|
@@ -8,7 +8,7 @@ Feature: Match
|
|
8
8
|
"""
|
9
9
|
|
10
10
|
Scenario: Fails at finding matches
|
11
|
-
When I run `
|
11
|
+
When I run `recog_match.rb failing_banners_fingerprints.xml banners.xml`
|
12
12
|
Then it should pass with:
|
13
13
|
"""
|
14
14
|
FAIL: ---------- Welcome to Pure-FTPd [privsep] [TLS] ----------
|
data/features/verify.feature
CHANGED
@@ -1,31 +1,34 @@
|
|
1
1
|
Feature: Verify
|
2
2
|
Scenario: No tests
|
3
|
-
When I run `
|
3
|
+
When I run `recog_verify.rb no_tests.xml`
|
4
4
|
Then it should pass with:
|
5
5
|
"""
|
6
6
|
SUMMARY: Test completed with 0 successful, 0 warnings, and 0 failures
|
7
7
|
"""
|
8
8
|
|
9
9
|
Scenario: Successful tests
|
10
|
-
When I run `
|
10
|
+
When I run `recog_verify.rb successful_tests.xml`
|
11
11
|
Then it should pass with:
|
12
12
|
"""
|
13
|
-
SUMMARY: Test completed with
|
13
|
+
SUMMARY: Test completed with 4 successful, 0 warnings, and 0 failures
|
14
14
|
"""
|
15
15
|
|
16
16
|
Scenario: Tests with warnings
|
17
|
-
When I run `
|
17
|
+
When I run `recog_verify.rb tests_with_warnings.xml`
|
18
18
|
Then it should pass with:
|
19
19
|
"""
|
20
|
-
WARN: 'Pure-FTPd'
|
20
|
+
WARN: 'Pure-FTPd' has no test cases
|
21
21
|
SUMMARY: Test completed with 1 successful, 1 warnings, and 0 failures
|
22
22
|
"""
|
23
23
|
|
24
24
|
Scenario: Tests with failures
|
25
|
-
When I run `
|
25
|
+
When I run `recog_verify.rb tests_with_failures.xml`
|
26
26
|
Then it should pass with:
|
27
27
|
"""
|
28
28
|
FAIL: 'foo test' failed to match "bar" with (?-mix:^foo$)'
|
29
29
|
FAIL: '' failed to match "This almost matches" with (?-mix:^This matches$)'
|
30
|
-
|
30
|
+
FAIL: 'bar test' failed to find expected capture group os.version '5.0'
|
31
|
+
SUMMARY: Test completed with 0 successful, 0 warnings, and 3 failures
|
31
32
|
"""
|
33
|
+
|
34
|
+
|
data/features/xml/no_tests.xml
CHANGED
@@ -1,53 +1,3 @@
|
|
1
1
|
<?xml version="1.0"?>
|
2
|
-
<!--
|
3
|
-
SMTP response lines to the EHLO command are matched against these patterns
|
4
|
-
(1 line at a time) to fingerprint SMTP servers.
|
5
|
-
|
6
|
-
See comment at the top of smtp_banners.xml for additional info.
|
7
|
-
-->
|
8
|
-
|
9
2
|
<fingerprints>
|
10
|
-
<fingerprint pattern="^500[ -]Syntax error, command "XXXX" unrecognized$">
|
11
|
-
<description>
|
12
|
-
Cisco PIX changes the command letters to 'X' before passing
|
13
|
-
them to the real SMTP server.
|
14
|
-
</description>
|
15
|
-
<param pos="0" name="service.vendor" value="Cisco"/>
|
16
|
-
<param pos="0" name="service.family" value="PIX"/>
|
17
|
-
<param pos="0" name="service.product" value="PIX"/>
|
18
|
-
</fingerprint>
|
19
|
-
|
20
|
-
<!--
|
21
|
-
Don't try to infer a fingerprint from XEXCH50, because if we do, it might overwrite
|
22
|
-
a very precise MS IIS SMTP service or MS Exchange Server fingerprint found with the
|
23
|
-
help of smtp_banners.xml. Instead, this case is handled specially by the Jess rule
|
24
|
-
smtp-iis-xexch50-svc-fingerprint. -mrb
|
25
|
-
|
26
|
-
<fingerprint pattern="^250[ -] *XEXCH50.*$">
|
27
|
-
<description>
|
28
|
-
Microsoft Exchange/IIS server
|
29
|
-
</description>
|
30
|
-
<param pos="0" name="service.vendor" value="Microsoft"/>
|
31
|
-
<param pos="0" name="service.family" value="IIS"/>
|
32
|
-
<param pos="0" name="service.product" value="IIS"/>
|
33
|
-
<param pos="0" name="os.vendor" value="Microsoft"/>
|
34
|
-
<param pos="0" name="os.family" value="Windows"/>
|
35
|
-
<param pos="0" name="os.device" value="General"/>
|
36
|
-
<param pos="0" name="os.product" value="Windows"/>
|
37
|
-
</fingerprint>
|
38
|
-
-->
|
39
|
-
|
40
|
-
<fingerprint pattern="^221[ -]See ya in cyberspace$">
|
41
|
-
<description>
|
42
|
-
221 See ya in cyberspace
|
43
|
-
</description>
|
44
|
-
<param pos="0" name="service.vendor" value="Alt-N"/>
|
45
|
-
<param pos="0" name="service.family" value="MDaemon"/>
|
46
|
-
<param pos="0" name="service.product" value="MDaemon"/>
|
47
|
-
<param pos="0" name="os.vendor" value="Microsoft"/>
|
48
|
-
<param pos="0" name="os.family" value="Windows"/>
|
49
|
-
<param pos="0" name="os.device" value="General"/>
|
50
|
-
<param pos="0" name="os.product" value="Windows"/>
|
51
|
-
<param pos="0" name="os.arch" value="x86"/>
|
52
|
-
</fingerprint>
|
53
3
|
</fingerprints>
|
@@ -7,27 +7,12 @@
|
|
7
7
|
<param pos="0" name="os.product" value="IOS"/>
|
8
8
|
<param pos="1" name="os.version"/>
|
9
9
|
</fingerprint>
|
10
|
-
<fingerprint pattern="^
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
<param pos="0" name="os.vendor" value="Microsoft"/>
|
18
|
-
<param pos="0" name="os.device" value="General"/>
|
19
|
-
<param pos="0" name="os.family" value="Windows"/>
|
20
|
-
<param pos="0" name="os.product" value="Windows"/>
|
21
|
-
</fingerprint>
|
22
|
-
<fingerprint pattern="^The Microsoft Exchange IMAP4 service is ready\.?$">
|
23
|
-
<example>The Microsoft Exchange IMAP4 service is ready.</example>
|
24
|
-
<description>Microsoft Exchange Server</description>
|
25
|
-
<param pos="0" name="service.vendor" value="Microsoft"/>
|
26
|
-
<param pos="0" name="service.family" value="Exchange Server"/>
|
27
|
-
<param pos="0" name="service.product" value="Exchange Server"/>
|
28
|
-
<param pos="0" name="os.vendor" value="Microsoft"/>
|
29
|
-
<param pos="0" name="os.device" value="General"/>
|
30
|
-
<param pos="0" name="os.family" value="Windows"/>
|
31
|
-
<param pos="0" name="os.product" value="Windows"/>
|
10
|
+
<fingerprint pattern="^bar ([\d.]+)$">
|
11
|
+
<description>bar test</description>
|
12
|
+
<example os.version="1.0" >bar 1.0</example>
|
13
|
+
<example os.version="2.0" >bar 2.0</example>
|
14
|
+
<example os.version="2.1" >bar 2.1</example>
|
15
|
+
<param pos="1" name="os.version" />
|
16
|
+
<param pos="0" name="os.name" value="Bar" />
|
32
17
|
</fingerprint>
|
33
18
|
</fingerprints>
|
@@ -2,9 +2,19 @@
|
|
2
2
|
<fingerprints>
|
3
3
|
<fingerprint pattern="^foo$">
|
4
4
|
<description>foo test</description>
|
5
|
+
<!-- Fail: doesn't match -->
|
5
6
|
<example>bar</example>
|
6
7
|
</fingerprint>
|
7
8
|
<fingerprint pattern="^This matches$">
|
9
|
+
<!-- Warn: no name -->
|
10
|
+
<!-- Fail: doesn't match -->
|
8
11
|
<example>This almost matches</example>
|
9
12
|
</fingerprint>
|
13
|
+
<fingerprint pattern="^bar ([\d.]+)$">
|
14
|
+
<description>bar test</description>
|
15
|
+
<!-- Fail: expected os.version doesn't match the capture group -->
|
16
|
+
<example os.version="5.0" >bar 1.0</example>
|
17
|
+
<param pos="1" name="os.version" />
|
18
|
+
<param pos="0" name="os.name" value="Bar" />
|
19
|
+
</fingerprint>
|
10
20
|
</fingerprints>
|
@@ -7,4 +7,11 @@
|
|
7
7
|
<param pos="0" name="service.family" value="Pure-FTPd"/>
|
8
8
|
<param pos="0" name="service.product" value="Pure-FTPd"/>
|
9
9
|
</fingerprint>
|
10
|
+
<fingerprint pattern="^-{10} Welcome to Pure-FTPd (.*)-{10}$">
|
11
|
+
<!-- should warn with no examples -->
|
12
|
+
<description>Pure-FTPd</description>
|
13
|
+
<param pos="1" name="pureftpd.config"/>
|
14
|
+
<param pos="0" name="service.family" value="Pure-FTPd"/>
|
15
|
+
<param pos="0" name="service.product" value="Pure-FTPd"/>
|
16
|
+
</fingerprint>
|
10
17
|
</fingerprints>
|
data/lib/recog/db.rb
CHANGED
@@ -1,38 +1,54 @@
|
|
1
1
|
module Recog
|
2
|
+
|
3
|
+
# A collection of {Fingerprint fingerprints} for matching against a particular
|
4
|
+
# kind of fingerprintable data, e.g. an HTTP `Server` header
|
2
5
|
class DB
|
3
6
|
require 'nokogiri'
|
4
7
|
require 'recog/fingerprint'
|
5
8
|
|
6
|
-
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :path
|
11
|
+
|
12
|
+
# @return [Array<Fingerprint>] {Fingerprint} objects that can be matched
|
13
|
+
# against strings that make sense for the {#match_key}
|
14
|
+
attr_reader :fingerprints
|
15
|
+
|
16
|
+
# @return [String] Taken from the `fingerprints/matches` element, or
|
17
|
+
# defaults to the basename of {#path} without the `.xml` extension.
|
18
|
+
attr_reader :match_key
|
7
19
|
|
20
|
+
# @param path [String]
|
8
21
|
def initialize(path)
|
9
|
-
|
22
|
+
@match_key = nil
|
23
|
+
@path = path
|
24
|
+
@fingerprints = []
|
25
|
+
|
10
26
|
parse_fingerprints
|
11
27
|
end
|
12
28
|
|
29
|
+
# @return [void]
|
13
30
|
def parse_fingerprints
|
14
|
-
self.fingerprints = []
|
15
31
|
xml = nil
|
16
|
-
|
32
|
+
|
17
33
|
File.open(self.path, "rb") do |fd|
|
18
|
-
xml = Nokogiri::XML(
|
34
|
+
xml = Nokogiri::XML(fd.read(fd.stat.size))
|
19
35
|
end
|
20
36
|
|
21
37
|
xml.xpath("/fingerprints").each do |fbase|
|
22
38
|
if fbase['matches']
|
23
|
-
|
39
|
+
@match_key = fbase['matches'].to_s
|
24
40
|
end
|
25
41
|
end
|
26
42
|
|
27
|
-
unless
|
28
|
-
|
43
|
+
unless @match_key
|
44
|
+
@match_key = File.basename(self.path).sub(/\.xml$/, '')
|
29
45
|
end
|
30
46
|
|
31
47
|
xml.xpath("/fingerprints/fingerprint").each do |fprint|
|
32
|
-
fingerprints << Fingerprint.new(fprint)
|
48
|
+
@fingerprints << Fingerprint.new(fprint)
|
33
49
|
end
|
34
50
|
|
35
51
|
xml = nil
|
36
52
|
end
|
37
53
|
end
|
38
|
-
end
|
54
|
+
end
|
data/lib/recog/db_manager.rb
CHANGED
data/lib/recog/fingerprint.rb
CHANGED
@@ -1,60 +1,144 @@
|
|
1
1
|
module Recog
|
2
|
+
|
3
|
+
# A fingerprint that can be {#match matched} against a particular kind of
|
4
|
+
# fingerprintable data, e.g. an HTTP `Server` header
|
2
5
|
class Fingerprint
|
3
|
-
|
6
|
+
require 'recog/fingerprint/regexp_factory'
|
7
|
+
require 'recog/fingerprint/test'
|
8
|
+
|
9
|
+
# A human readable name describing this fingerprint
|
10
|
+
# @return (see #parse_description)
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# Regular expression pulled from the {DB} xml file.
|
14
|
+
#
|
15
|
+
# @see #create_regexp
|
16
|
+
# @return [Regexp] the Regexp to try when calling {#match}
|
17
|
+
attr_reader :regex
|
18
|
+
|
19
|
+
# Collection of indexes for capture groups created by {#match}
|
20
|
+
#
|
21
|
+
# @return (see #parse_params)
|
22
|
+
attr_reader :params
|
23
|
+
|
24
|
+
# Collection of example strings that should {#match} our {#regex}
|
25
|
+
#
|
26
|
+
# @return (see #parse_examples)
|
27
|
+
attr_reader :tests
|
4
28
|
|
29
|
+
# @param xml [Nokogiri::XML::Element]
|
5
30
|
def initialize(xml)
|
6
|
-
@name =
|
31
|
+
@name = parse_description(xml)
|
7
32
|
@regex = create_regexp(xml)
|
8
|
-
@params =
|
9
|
-
@tests
|
33
|
+
@params = {}
|
34
|
+
@tests = []
|
35
|
+
|
36
|
+
parse_examples(xml)
|
37
|
+
parse_params(xml)
|
10
38
|
end
|
11
39
|
|
12
|
-
|
40
|
+
# Attempt to match the given string.
|
41
|
+
#
|
42
|
+
# @param match_string [String]
|
43
|
+
# @return [Hash,nil] Keys will be host, service, and os attributes
|
44
|
+
def match(match_string)
|
45
|
+
match_data = @regex.match(match_string)
|
46
|
+
return if match_data.nil?
|
13
47
|
|
14
|
-
|
15
|
-
|
16
|
-
|
48
|
+
result = { 'matched' => @name }
|
49
|
+
@params.each_pair do |k,v|
|
50
|
+
if v[0] == 0
|
51
|
+
# A match offset of 0 means this param has a hardcoded value
|
52
|
+
result[k] = v[1]
|
53
|
+
else
|
54
|
+
result[k] = match_data[ v[0] ]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
return result
|
58
|
+
end
|
59
|
+
|
60
|
+
# Ensure all the {#tests} actually match the fingerprint and return the
|
61
|
+
# expected capture groups.
|
62
|
+
#
|
63
|
+
# @yieldparam status [Symbol] One of `:warn`, `:fail`, or `:success` to
|
64
|
+
# indicate whether a test worked
|
65
|
+
# @yieldparam message [String] A human-readable string explaining the
|
66
|
+
# `status`
|
67
|
+
def verify_tests(&block)
|
68
|
+
if tests.size == 0
|
69
|
+
yield :warn, "'#{@name}' has no test cases"
|
70
|
+
end
|
71
|
+
|
72
|
+
tests.each do |test|
|
73
|
+
result = match(test.content)
|
74
|
+
if result.nil?
|
75
|
+
yield :fail, "'#{@name}' failed to match #{test.content.inspect} with #{@regex}'"
|
76
|
+
next
|
77
|
+
end
|
78
|
+
|
79
|
+
message = test
|
80
|
+
status = :success
|
81
|
+
# Ensure that all the attributes as provided by the example were parsed
|
82
|
+
# out correctly and match the capture group values we expect.
|
83
|
+
test.attributes.each do |k, v|
|
84
|
+
if !result.has_key?(k) || result[k] != v
|
85
|
+
message = "'#{@name}' failed to find expected capture group #{k} '#{v}'"
|
86
|
+
status = :fail
|
87
|
+
break
|
88
|
+
end
|
89
|
+
end
|
90
|
+
yield status, message
|
91
|
+
end
|
17
92
|
end
|
18
93
|
|
94
|
+
private
|
95
|
+
|
96
|
+
# @param xml [Nokogiri::XML::Element]
|
97
|
+
# @return [Regexp]
|
19
98
|
def create_regexp(xml)
|
20
99
|
pattern = xml['pattern']
|
21
100
|
flags = xml['flags'].to_s.split(',')
|
22
101
|
RegexpFactory.build(pattern, flags)
|
23
102
|
end
|
24
103
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
value = e['value'].to_s
|
31
|
-
h[name] = [pos, value]
|
32
|
-
end
|
33
|
-
end
|
104
|
+
# @param xml [Nokogiri::XML::Element]
|
105
|
+
# @return [String] Contents of the source XML's `description` tag
|
106
|
+
def parse_description(xml)
|
107
|
+
element = xml.xpath('description')
|
108
|
+
element.empty? ? '' : element.first.content
|
34
109
|
end
|
35
110
|
|
36
|
-
|
37
|
-
|
38
|
-
|
111
|
+
# @param xml [Nokogiri::XML::Element]
|
112
|
+
# @return [void]
|
113
|
+
def parse_examples(xml)
|
114
|
+
elements = xml.xpath('example')
|
39
115
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
116
|
+
elements.each do |elem|
|
117
|
+
# convert nokogiri Attributes into a hash of name => value
|
118
|
+
attrs = elem.attributes.values.reduce({}) { |a,e| a.merge(e.name => e.value) }
|
119
|
+
@tests << Test.new(elem.content, attrs)
|
44
120
|
end
|
45
121
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
|
125
|
+
# @param xml [Nokogiri::XML::Element]
|
126
|
+
# @return [Hash<String,Array>] Keys are things like `"os.name"`, values are a two
|
127
|
+
# element Array. The first element is an index for the capture group that returns
|
128
|
+
# that thing. If the index is 0, the second element is a static value for
|
129
|
+
# that thing; otherwise it is undefined.
|
130
|
+
def parse_params(xml)
|
131
|
+
@params = {}.tap do |h|
|
132
|
+
xml.xpath('param').each do |param|
|
133
|
+
name = param['name']
|
134
|
+
pos = param['pos'].to_i
|
135
|
+
value = param['value'].to_s
|
136
|
+
h[name] = [pos, value]
|
55
137
|
end
|
56
|
-
rflags
|
57
138
|
end
|
139
|
+
|
140
|
+
nil
|
58
141
|
end
|
142
|
+
|
59
143
|
end
|
60
144
|
end
|