phisher_phinder 0.1.0 → 0.2.0

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +2 -1
  3. data/.gitignore +3 -0
  4. data/Gemfile +0 -11
  5. data/Gemfile.lock +45 -13
  6. data/README.md +108 -2
  7. data/exe/phisher_phinder +61 -0
  8. data/lib/phisher_phinder.rb +11 -2
  9. data/lib/phisher_phinder/command.rb +20 -0
  10. data/lib/phisher_phinder/display.rb +64 -0
  11. data/lib/phisher_phinder/extended_ip.rb +4 -0
  12. data/lib/phisher_phinder/extended_ip_factory.rb +4 -2
  13. data/lib/phisher_phinder/geoip_ip_data.rb +9 -2
  14. data/lib/phisher_phinder/mail.rb +10 -3
  15. data/lib/phisher_phinder/mail_parser.rb +43 -30
  16. data/lib/phisher_phinder/mail_parser/authentication_headers/auth_results_parser.rb +150 -0
  17. data/lib/phisher_phinder/mail_parser/authentication_headers/parser.rb +25 -0
  18. data/lib/phisher_phinder/mail_parser/authentication_headers/received_spf_parser.rb +222 -0
  19. data/lib/phisher_phinder/mail_parser/body/block_classifier.rb +106 -0
  20. data/lib/phisher_phinder/mail_parser/body/block_parser.rb +37 -0
  21. data/lib/phisher_phinder/mail_parser/body_parser.rb +26 -31
  22. data/lib/phisher_phinder/mail_parser/header_value_parser.rb +25 -10
  23. data/lib/phisher_phinder/mail_parser/received_headers/by_parser.rb +35 -5
  24. data/lib/phisher_phinder/mail_parser/received_headers/for_parser.rb +25 -5
  25. data/lib/phisher_phinder/mail_parser/received_headers/from_parser.rb +50 -6
  26. data/lib/phisher_phinder/mail_parser/received_headers/parser.rb +50 -29
  27. data/lib/phisher_phinder/mail_parser/received_headers/starttls_parser.rb +8 -1
  28. data/lib/phisher_phinder/null_lookup_client.rb +9 -0
  29. data/lib/phisher_phinder/null_response.rb +12 -0
  30. data/lib/phisher_phinder/sender_extractor.rb +74 -0
  31. data/lib/phisher_phinder/simple_ip.rb +4 -0
  32. data/lib/phisher_phinder/tracing_report.rb +47 -0
  33. data/lib/phisher_phinder/version.rb +1 -1
  34. data/phisher_phinder.gemspec +15 -1
  35. metadata +208 -13
@@ -4,12 +4,11 @@ module PhisherPhinder
4
4
  module MailParser
5
5
  module ReceivedHeaders
6
6
  class Parser
7
- def initialize(by_parser:, for_parser:, from_parser:, starttls_parser:, timestamp_parser:, classifier:)
7
+ def initialize(by_parser:, for_parser:, from_parser:, timestamp_parser:, classifier:)
8
8
  @parsers = {
9
9
  by: by_parser,
10
10
  for: for_parser,
11
11
  from: from_parser,
12
- starttls: starttls_parser,
13
12
  time: timestamp_parser,
14
13
  }
15
14
  @classifier = classifier
@@ -24,7 +23,13 @@ module PhisherPhinder
24
23
  components = extract_value_components(header_value).merge(time: timestamp)
25
24
 
26
25
  output = @parsers.inject({}) do |memo, (component_name, parser)|
27
- memo.merge(parser.parse(components[component_name]))
26
+ memo.merge(parser.parse(components[component_name])) do |key, old_value, new_value|
27
+ if key == :starttls && old_value
28
+ old_value
29
+ else
30
+ new_value
31
+ end
32
+ end
28
33
  end
29
34
 
30
35
  output.merge(@classifier.classify(output))
@@ -33,40 +38,56 @@ module PhisherPhinder
33
38
  private
34
39
 
35
40
  def extract_value_components(header_value)
36
- require 'strscan'
37
41
 
38
- scanner = StringScanner.new(header_value)
42
+ values = {
43
+ time: [],
44
+ for: [],
45
+ by: [],
46
+ from: []
47
+ }
48
+
49
+ cleaned_value = header_value.gsub(/\A\(([^\)]+)\)/, '\1')
39
50
 
40
- output = {}
51
+ tokenised_value = cleaned_value.split(" ")
52
+ current_component = nil
41
53
 
42
- if scanner.check(/\(from\s+[^)]+\)/)
43
- from_part = scanner.scan(/\(from\s+[^)]+\)/)
44
- elsif scanner.check(/from\s.+?\(HELO\s[^)]+\)\s\(\[[^\]]+\]\)/)
45
- from_part = scanner.scan(/from.+?(?=by)/)
46
- elsif scanner.check(/from\s[^)(]+\sby/)
47
- from_part = scanner.scan(/from.+?(?=by)/)
48
- else
49
- from_part = scanner.scan(/from\s.+?\([^)]+?\)/)
54
+ tokenised_value.each do |val|
55
+ current_component = component_start(val, values) || current_component
56
+
57
+ values[current_component] << val if current_component
50
58
  end
51
59
 
52
- if scanner.check(/.+\S+\s+by/)
53
- starttls_part = scanner.scan(/.+\s(?=by)/)
54
- by_part = scanner.scan(/\s?by.+?\sid\s[\S]+\s?/)
55
- for_part = scanner.scan(/for\s+\S+/)
56
- elsif scanner.check(/\s?by.+?\s(id|ID)\s[\S]+\s?/)
57
- by_part = scanner.scan(/\s?by.+?\s(id|ID)\s[\S]+\s?/) unless scanner.eos?
58
- for_part = scanner.scan(/for\s+\S+/) unless scanner.eos?
59
- starttls_part = scanner.rest unless scanner.eos?
60
- elsif scanner.check(/by.+(?!\sid)/)
61
- by_part = scanner.scan(/.+/)
60
+ values.inject({}) do |memo, (component, values)|
61
+ memo.merge(component => (values.any? ? values.join(' ') : nil))
62
62
  end
63
+ end
63
64
 
64
- {
65
- by: by_part,
66
- for: for_part,
67
- from: from_part,
68
- starttls: starttls_part
65
+ def for_scan(scanner)
66
+ return nil if scanner.eos?
67
+
68
+ scanner.check(/for\s<[^>]+>/) ? scanner.scan(/for\s<[^>]+>/) : scanner.scan(/for\s+\S+/)
69
+ end
70
+
71
+ def component_start(token, values)
72
+ markers = {
73
+ 'from' => :from,
74
+ 'by' => :by,
75
+ 'for' => :for
76
+ }
77
+
78
+ return nil unless component = markers[token]
79
+
80
+ blocking_components = {
81
+ from: [:by, :for],
82
+ by: [:for],
83
+ for: []
69
84
  }
85
+
86
+ if blocking_components[component].any? { |blocking_comp| values[blocking_comp].any? }
87
+ nil
88
+ else
89
+ component
90
+ end
70
91
  end
71
92
  end
72
93
  end
@@ -9,6 +9,7 @@ module PhisherPhinder
9
9
 
10
10
  patterns = [
11
11
  /\(version=(?<version>\S+)\scipher=(?<cipher>\S+)\sbits=(?<bits>\S+)\)/,
12
+ /\(version=(?<version>\S+),\scipher=(?<cipher>\S+)\)/,
12
13
  /using\s(?<version>\S+)\swith cipher\s(?<cipher>\S+)\s\((?<bits>.+?) bits\)/
13
14
  ]
14
15
 
@@ -16,7 +17,13 @@ module PhisherPhinder
16
17
  memo || component.match(pattern)
17
18
  end
18
19
 
19
- {starttls: {version: matches[:version], cipher: matches[:cipher], bits: matches[:bits]}}
20
+ {
21
+ starttls: {
22
+ version: matches[:version],
23
+ cipher: matches[:cipher],
24
+ bits: matches.names.include?('bits') ? matches[:bits] : nil,
25
+ }
26
+ }
20
27
  end
21
28
  end
22
29
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ class NullLookupClient
5
+ def lookup(_ip_address)
6
+ NullResponse.new
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ class NullResponse
5
+ def method_missing(*_args)
6
+ end
7
+
8
+ def ==(other)
9
+ other.instance_of? self.class
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ class SenderExtractor
5
+ def extract(mail)
6
+ auth_senders = {
7
+ hosts: [],
8
+ email_addresses: []
9
+ }
10
+
11
+ processed_authservs = []
12
+
13
+ authentication_results = mail.authentication_headers[:authentication_results]
14
+
15
+ if authentication_results.any?
16
+ trusted_auth_header = authentication_results.first
17
+ untrusted_auth_headers = authentication_results[1..-1]
18
+
19
+ auth_senders[:hosts] << {
20
+ entry_type: :ip,
21
+ host: trusted_auth_header[:spf].first[:ip],
22
+ spf: {present: true, trusted: true}
23
+ }
24
+ auth_senders[:email_addresses] << {
25
+ email_address: trusted_auth_header[:spf].first[:from],
26
+ spf: {present: true, trusted: true, result: trusted_auth_header[:spf].first[:result]},
27
+ }
28
+
29
+ processed_authservs << trusted_auth_header[:authserv_id]
30
+
31
+ untrusted_auth_headers.each do |header|
32
+ next if processed_authservs.include? header[:authserv_id]
33
+
34
+ auth_senders[:hosts] << {entry_type: :ip, host: header[:spf].first[:ip], spf: {present: true, trusted: false}}
35
+ unless auth_senders[:email_addresses].find { |entry| entry[:email_address] == header[:spf].first[:from] }
36
+ auth_senders[:email_addresses] << {
37
+ email_address: header[:spf].first[:from],
38
+ spf: {present: true, trusted: false, result: header[:spf].first[:result]},
39
+ }
40
+ end
41
+ end
42
+ end
43
+
44
+ tracing_senders = []
45
+
46
+ mail.tracing_headers[:received].each do |header|
47
+ if tracing_senders.empty?
48
+ if header[:sender] && header[:sender][:ip] == trusted_auth_sender_ip(auth_senders)
49
+ tracing_senders << header[:sender]
50
+ end
51
+
52
+ next
53
+ end
54
+
55
+ if header[:sender] && header[:recipient] == tracing_senders.last[:host]
56
+ tracing_senders << header[:sender]
57
+ else
58
+ break
59
+ end
60
+ end
61
+
62
+ {
63
+ authentication_senders: auth_senders,
64
+ tracing_senders: tracing_senders
65
+ }
66
+ end
67
+
68
+ private
69
+
70
+ def trusted_auth_sender_ip(authentication_senders)
71
+ (authentication_senders[:hosts].find { |e| e[:spf][:trusted] })[:host]
72
+ end
73
+ end
74
+ end
@@ -11,5 +11,9 @@ module PhisherPhinder
11
11
  def ==(other)
12
12
  other.is_a?(SimpleIp) && ip_address == other.ip_address
13
13
  end
14
+
15
+ def to_s
16
+ @ip_address.to_s
17
+ end
14
18
  end
15
19
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ class TracingReport
5
+ def initialize(mail)
6
+ @mail = mail
7
+ end
8
+
9
+ def report
10
+ {
11
+ authentication: {
12
+ mechanisms: [:spf],
13
+ spf: {
14
+ success: latest_spf_entry[:result] == :pass,
15
+ ip: latest_spf_entry[:ip],
16
+ from_address: latest_spf_entry[:mailfrom],
17
+ client_ip: latest_spf_entry[:client_ip],
18
+ }
19
+ },
20
+ origin: extract_origin_headers(@mail.headers),
21
+ tracing: extract_tracing_headers(@mail.tracing_headers, latest_spf_entry)
22
+ }
23
+ end
24
+
25
+ private
26
+
27
+ def latest_spf_entry
28
+ @mail.authentication_headers[:received_spf].first
29
+ end
30
+
31
+ def ip_address(spf_entry)
32
+ spf_entry[:ip] || spf_entry[:client_ip]
33
+ end
34
+
35
+ def extract_tracing_headers(received_headers, latest_spf_entry)
36
+ start = received_headers[:received].find_index { |h| h[:sender][:ip] == ip_address(latest_spf_entry) }
37
+ received_headers[:received][start..-1]
38
+ end
39
+
40
+ def extract_origin_headers(headers)
41
+ [:from, :return_path, :message_id].inject({}) do |output, header_type|
42
+ entries = headers[header_type] || []
43
+ output.merge(header_type => entries.map { |h| h[:data] })
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,3 +1,3 @@
1
1
  module PhisherPhinder
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -27,6 +27,20 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
+ spec.add_dependency "dotenv", "~> 2.7.5"
31
+ spec.add_dependency "maxmind-geoip2", "~> 0.4.0"
32
+ spec.add_dependency "nokogiri", "~> 1.11.0"
33
+ spec.add_dependency "sequel", "~> 5.33"
34
+ spec.add_dependency "sqlite3", "~> 1.4.2"
35
+ spec.add_dependency "terminal-table", "~> 2.0.0"
36
+ spec.add_dependency "whois", "~> 5.0.1"
37
+ spec.add_dependency "whois-parser", "~> 1.2.0"
38
+
39
+ spec.add_development_dependency "bundler-audit", "~> 0.7.0.1"
40
+ spec.add_development_dependency "rake", "~> 12.0"
41
+ spec.add_development_dependency "rspec", "~> 3.0"
42
+ spec.add_development_dependency "timecop", "~> 0.9.2"
43
+ spec.add_development_dependency 'database_cleaner-sequel', '1.8.0'
44
+ spec.add_development_dependency 'webmock', '~> 3.8.3'
30
45
  spec.add_development_dependency "pry"
31
- spec.add_development_dependency "rspec", "3.9.0"
32
46
  end
metadata CHANGED
@@ -1,47 +1,230 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phisher_phinder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rory McKinley
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-23 00:00:00.000000000 Z
11
+ date: 2021-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: pry
14
+ name: dotenv
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 2.7.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.7.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: maxmind-geoip2
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.4.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.4.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.11.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.11.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: sequel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.33'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.33'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.4.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.4.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: terminal-table
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 2.0.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 2.0.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: whois
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 5.0.1
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 5.0.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: whois-parser
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 1.2.0
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 1.2.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: bundler-audit
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.7.0.1
20
132
  type: :development
21
133
  prerelease: false
22
134
  version_requirements: !ruby/object:Gem::Requirement
23
135
  requirements:
24
- - - ">="
136
+ - - "~>"
25
137
  - !ruby/object:Gem::Version
26
- version: '0'
138
+ version: 0.7.0.1
139
+ - !ruby/object:Gem::Dependency
140
+ name: rake
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '12.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '12.0'
27
153
  - !ruby/object:Gem::Dependency
28
154
  name: rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '3.0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '3.0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: timecop
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 0.9.2
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 0.9.2
181
+ - !ruby/object:Gem::Dependency
182
+ name: database_cleaner-sequel
29
183
  requirement: !ruby/object:Gem::Requirement
30
184
  requirements:
31
185
  - - '='
32
186
  - !ruby/object:Gem::Version
33
- version: 3.9.0
187
+ version: 1.8.0
34
188
  type: :development
35
189
  prerelease: false
36
190
  version_requirements: !ruby/object:Gem::Requirement
37
191
  requirements:
38
192
  - - '='
39
193
  - !ruby/object:Gem::Version
40
- version: 3.9.0
194
+ version: 1.8.0
195
+ - !ruby/object:Gem::Dependency
196
+ name: webmock
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 3.8.3
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 3.8.3
209
+ - !ruby/object:Gem::Dependency
210
+ name: pry
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
41
223
  description: A collection of tools to dissect and report on phishing emails
42
224
  email:
43
225
  - rorymckinley@gmail.com
44
- executables: []
226
+ executables:
227
+ - phisher_phinder
45
228
  extensions: []
46
229
  extra_rdoc_files: []
47
230
  files:
@@ -61,15 +244,23 @@ files:
61
244
  - bin/console
62
245
  - bin/setup
63
246
  - db/migrations/0001_create_geo_ip_cache.rb
247
+ - exe/phisher_phinder
64
248
  - lib/phisher_phinder.rb
65
249
  - lib/phisher_phinder/body_hyperlink.rb
66
250
  - lib/phisher_phinder/cached_geoip_client.rb
251
+ - lib/phisher_phinder/command.rb
252
+ - lib/phisher_phinder/display.rb
67
253
  - lib/phisher_phinder/expanded_data_processor.rb
68
254
  - lib/phisher_phinder/extended_ip.rb
69
255
  - lib/phisher_phinder/extended_ip_factory.rb
70
256
  - lib/phisher_phinder/geoip_ip_data.rb
71
257
  - lib/phisher_phinder/mail.rb
72
258
  - lib/phisher_phinder/mail_parser.rb
259
+ - lib/phisher_phinder/mail_parser/authentication_headers/auth_results_parser.rb
260
+ - lib/phisher_phinder/mail_parser/authentication_headers/parser.rb
261
+ - lib/phisher_phinder/mail_parser/authentication_headers/received_spf_parser.rb
262
+ - lib/phisher_phinder/mail_parser/body/block_classifier.rb
263
+ - lib/phisher_phinder/mail_parser/body/block_parser.rb
73
264
  - lib/phisher_phinder/mail_parser/body_parser.rb
74
265
  - lib/phisher_phinder/mail_parser/header_value_parser.rb
75
266
  - lib/phisher_phinder/mail_parser/received_headers/by_parser.rb
@@ -79,7 +270,11 @@ files:
79
270
  - lib/phisher_phinder/mail_parser/received_headers/parser.rb
80
271
  - lib/phisher_phinder/mail_parser/received_headers/starttls_parser.rb
81
272
  - lib/phisher_phinder/mail_parser/received_headers/timestamp_parser.rb
273
+ - lib/phisher_phinder/null_lookup_client.rb
274
+ - lib/phisher_phinder/null_response.rb
275
+ - lib/phisher_phinder/sender_extractor.rb
82
276
  - lib/phisher_phinder/simple_ip.rb
277
+ - lib/phisher_phinder/tracing_report.rb
83
278
  - lib/phisher_phinder/version.rb
84
279
  - phisher_phinder.gemspec
85
280
  homepage: https://capefox.co
@@ -90,7 +285,7 @@ metadata:
90
285
  homepage_uri: https://capefox.co
91
286
  source_code_uri: https://github.com/rorymckinley/phisher_phinder
92
287
  changelog_uri: https://github.com/rorymckinley/phisher_phinder/blob/master/CHANGELOG.md
93
- post_install_message:
288
+ post_install_message:
94
289
  rdoc_options: []
95
290
  require_paths:
96
291
  - lib
@@ -106,7 +301,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
301
  version: '0'
107
302
  requirements: []
108
303
  rubygems_version: 3.1.2
109
- signing_key:
304
+ signing_key:
110
305
  specification_version: 4
111
306
  summary: A gem for dissecting phishing emails
112
307
  test_files: []