phisher_phinder 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []