ronin-db-activerecord 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.github/workflows/ruby.yml +31 -0
  4. data/.gitignore +13 -0
  5. data/.rspec +1 -0
  6. data/.ruby-version +1 -0
  7. data/.yardopts +1 -0
  8. data/COPYING.txt +165 -0
  9. data/ChangeLog.md +39 -0
  10. data/Gemfile +27 -0
  11. data/README.md +143 -0
  12. data/Rakefile +72 -0
  13. data/db/migrate/0001_create_ronin_ip_address_mac_addresses_table.rb +43 -0
  14. data/db/migrate/0002_create_ronin_vulnerabilities_table.rb +61 -0
  15. data/db/migrate/0003_create_ronin_url_schemes_table.rb +32 -0
  16. data/db/migrate/0004_create_ronin_url_query_param_names_table.rb +32 -0
  17. data/db/migrate/0005_create_ronin_user_names_table.rb +33 -0
  18. data/db/migrate/0006_create_ronin_software_vendors_table.rb +32 -0
  19. data/db/migrate/0007_create_ronin_advisories_table.rb +42 -0
  20. data/db/migrate/0008_create_ronin_host_name_ip_addresses_table.rb +43 -0
  21. data/db/migrate/0009_create_ronin_host_names_table.rb +34 -0
  22. data/db/migrate/0010_create_ronin_arches_table.rb +37 -0
  23. data/db/migrate/0011_create_ronin_email_addresses_table.rb +44 -0
  24. data/db/migrate/0012_create_ronin_oses_table.rb +36 -0
  25. data/db/migrate/0013_create_ronin_organizations_table.rb +31 -0
  26. data/db/migrate/0014_create_ronin_ip_addresses_table.rb +35 -0
  27. data/db/migrate/0015_create_ronin_os_guesses_table.rb +40 -0
  28. data/db/migrate/0016_create_ronin_url_query_params_table.rb +42 -0
  29. data/db/migrate/0017_create_ronin_passwords_table.rb +32 -0
  30. data/db/migrate/0018_create_ronin_open_ports_table.rb +46 -0
  31. data/db/migrate/0019_create_ronin_urls_table.rb +50 -0
  32. data/db/migrate/0020_create_ronin_softwares_table.rb +39 -0
  33. data/db/migrate/0021_create_ronin_mac_addresses_table.rb +33 -0
  34. data/db/migrate/0022_create_ronin_countries_table.rb +34 -0
  35. data/db/migrate/0023_create_ronin_services_table.rb +32 -0
  36. data/db/migrate/0024_create_ronin_credentials_table.rb +44 -0
  37. data/db/migrate/0025_create_ronin_ports_table.rb +33 -0
  38. data/db/migrate/0026_create_ronin_asns_table.rb +44 -0
  39. data/db/migrate/0027_create_ronin_http_query_param_names_table.rb +32 -0
  40. data/db/migrate/0028_create_ronin_http_query_params_table.rb +42 -0
  41. data/db/migrate/0029_create_ronin_http_header_names_table.rb +31 -0
  42. data/db/migrate/0030_create_ronin_http_request_headers_table.rb +41 -0
  43. data/db/migrate/0031_create_ronin_http_response_headers_table.rb +41 -0
  44. data/db/migrate/0032_create_ronin_http_requests_table.rb +41 -0
  45. data/db/migrate/0033_create_ronin_http_responses_table.rb +36 -0
  46. data/db/migrate/0034_create_ronin_service_credentials_table.rb +41 -0
  47. data/db/migrate/0035_create_ronin_web_credentials_table.rb +41 -0
  48. data/gemspec.yml +28 -0
  49. data/lib/ronin/db/address.rb +105 -0
  50. data/lib/ronin/db/advisory.rb +169 -0
  51. data/lib/ronin/db/arch.rb +160 -0
  52. data/lib/ronin/db/asn.rb +212 -0
  53. data/lib/ronin/db/credential.rb +248 -0
  54. data/lib/ronin/db/email_address.rb +225 -0
  55. data/lib/ronin/db/host_name.rb +224 -0
  56. data/lib/ronin/db/host_name_ip_address.rb +65 -0
  57. data/lib/ronin/db/http_header_name.rb +75 -0
  58. data/lib/ronin/db/http_query_param.rb +79 -0
  59. data/lib/ronin/db/http_query_param_name.rb +76 -0
  60. data/lib/ronin/db/http_request.rb +120 -0
  61. data/lib/ronin/db/http_request_header.rb +78 -0
  62. data/lib/ronin/db/http_response.rb +91 -0
  63. data/lib/ronin/db/http_response_header.rb +78 -0
  64. data/lib/ronin/db/ip_address.rb +351 -0
  65. data/lib/ronin/db/ip_address_mac_address.rb +62 -0
  66. data/lib/ronin/db/mac_address.rb +91 -0
  67. data/lib/ronin/db/migrations.rb +137 -0
  68. data/lib/ronin/db/model/has_name.rb +102 -0
  69. data/lib/ronin/db/model/has_unique_name.rb +82 -0
  70. data/lib/ronin/db/model/importable.rb +85 -0
  71. data/lib/ronin/db/model/last_scanned_at.rb +48 -0
  72. data/lib/ronin/db/model.rb +37 -0
  73. data/lib/ronin/db/models.rb +108 -0
  74. data/lib/ronin/db/open_port.rb +148 -0
  75. data/lib/ronin/db/organization.rb +50 -0
  76. data/lib/ronin/db/os.rb +183 -0
  77. data/lib/ronin/db/os_guess.rb +67 -0
  78. data/lib/ronin/db/password.rb +167 -0
  79. data/lib/ronin/db/port.rb +123 -0
  80. data/lib/ronin/db/root.rb +28 -0
  81. data/lib/ronin/db/schema_migration.rb +34 -0
  82. data/lib/ronin/db/service.rb +48 -0
  83. data/lib/ronin/db/service_credential.rb +66 -0
  84. data/lib/ronin/db/software.rb +85 -0
  85. data/lib/ronin/db/software_vendor.rb +42 -0
  86. data/lib/ronin/db/url.rb +497 -0
  87. data/lib/ronin/db/url_query_param.rb +79 -0
  88. data/lib/ronin/db/url_query_param_name.rb +76 -0
  89. data/lib/ronin/db/url_scheme.rb +80 -0
  90. data/lib/ronin/db/user_name.rb +96 -0
  91. data/lib/ronin/db/vulnerability.rb +81 -0
  92. data/lib/ronin/db/web_credential.rb +69 -0
  93. data/ronin-db-activerecord.gemspec +61 -0
  94. data/spec/advisory_spec.rb +277 -0
  95. data/spec/arch_spec.rb +228 -0
  96. data/spec/asn_spec.rb +504 -0
  97. data/spec/credential_spec.rb +362 -0
  98. data/spec/email_address_spec.rb +372 -0
  99. data/spec/host_name_ip_address_spec.rb +8 -0
  100. data/spec/host_name_spec.rb +207 -0
  101. data/spec/http_header_name_spec.rb +25 -0
  102. data/spec/http_query_param_name_spec.rb +25 -0
  103. data/spec/http_query_param_spec.rb +104 -0
  104. data/spec/http_request_header_spec.rb +72 -0
  105. data/spec/http_request_spec.rb +168 -0
  106. data/spec/http_response_header_spec.rb +74 -0
  107. data/spec/http_response_spec.rb +103 -0
  108. data/spec/ip_address_mac_addresses_spec.rb +8 -0
  109. data/spec/ip_address_spec.rb +386 -0
  110. data/spec/mac_address_spec.rb +67 -0
  111. data/spec/migrations_spec.rb +122 -0
  112. data/spec/model/has_name_spec.rb +65 -0
  113. data/spec/model/has_unique_name_spec.rb +61 -0
  114. data/spec/model/importable_spec.rb +105 -0
  115. data/spec/models_spec.rb +60 -0
  116. data/spec/open_port_spec.rb +87 -0
  117. data/spec/organization_spec.rb +10 -0
  118. data/spec/os_guess_spec.rb +43 -0
  119. data/spec/os_spec.rb +114 -0
  120. data/spec/password_spec.rb +81 -0
  121. data/spec/port_spec.rb +102 -0
  122. data/spec/schema_migration_spec.rb +8 -0
  123. data/spec/service_credential_spec.rb +43 -0
  124. data/spec/service_spec.rb +39 -0
  125. data/spec/software_spec.rb +76 -0
  126. data/spec/software_vendor_spec.rb +33 -0
  127. data/spec/spec_helper.rb +13 -0
  128. data/spec/url_query_param_name_spec.rb +25 -0
  129. data/spec/url_query_param_spec.rb +110 -0
  130. data/spec/url_scheme_spec.rb +39 -0
  131. data/spec/url_spec.rb +951 -0
  132. data/spec/user_name_spec.rb +54 -0
  133. data/spec/vulnerability_spec.rb +8 -0
  134. data/spec/web_credential_spec.rb +72 -0
  135. metadata +266 -0
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-db-activerecord - ActiveRecord backend for the Ronin Database.
4
+ #
5
+ # Copyright (c) 2022 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # ronin-db-activerecord 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-db-activerecord 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-db-activerecord. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/db/model'
22
+ require 'ronin/db/model/importable'
23
+
24
+ require 'active_record'
25
+
26
+ module Ronin
27
+ module DB
28
+ #
29
+ # Represents Credentials used to access services or websites.
30
+ #
31
+ class Credential < ActiveRecord::Base
32
+
33
+ include Model
34
+ include Model::Importable
35
+
36
+ # @!attribute [rw] id
37
+ # Primary key of the credential.
38
+ #
39
+ # @return [Integer]
40
+ attribute :id, :integer
41
+
42
+ # @!attribute [rw] user_name
43
+ # User name of the credential.
44
+ #
45
+ # @return [UserName, nil]
46
+ belongs_to :user_name, optional: true
47
+ validates :user_name, presence: true,
48
+ if: ->(cred) { cred.email_address.nil? }
49
+
50
+ # @!attribute [rw] email_address
51
+ # The optional email address associated with the Credential
52
+ #
53
+ # @return [EmailAddress, nil]
54
+ belongs_to :email_address, optional: true
55
+ validates :email_address, presence: true,
56
+ if: ->(cred) { cred.user_name.nil? }
57
+
58
+ # @!attribute [rw] password
59
+ # Password of the credential.
60
+ #
61
+ # @return [Password]
62
+ belongs_to :password, required: true
63
+
64
+ # @!attribute [rw] service_credentials
65
+ # The service credentials.
66
+ #
67
+ # @return [Array<ServiceCredential>]
68
+ has_many :service_credentials, dependent: :destroy
69
+
70
+ # @!attribute [rw] open_ports
71
+ # The open ports that accept this credential pair.
72
+ #
73
+ # @return [Array<OpenPort>]
74
+ has_many :open_ports, through: :service_credentials
75
+
76
+ # @!attribute [rw] web_credentials
77
+ # The Web credentials.
78
+ #
79
+ # @return [Array<WebCredential>]
80
+ has_many :web_credentials, dependent: :destroy
81
+
82
+ # @!attribute [rw] urls
83
+ # The URLs that accept this credential pair.
84
+ #
85
+ # @return [Array<URL>]
86
+ has_many :urls, through: :web_credentials
87
+
88
+ #
89
+ # Searches for all credentials for a specific user.
90
+ #
91
+ # @param [String] name
92
+ # The name of the user.
93
+ #
94
+ # @return [Array<Credential>]
95
+ # The credentials for the user.
96
+ #
97
+ # @api public
98
+ #
99
+ def self.for_user(name)
100
+ joins(:user_name).where(user_name: {name: name})
101
+ end
102
+
103
+ #
104
+ # Searches all web credentials that are associated with an
105
+ # email address.
106
+ #
107
+ # @param [String] email
108
+ # The email address to search for.
109
+ #
110
+ # @return [Array<WebCredential>]
111
+ # The web credentials associated with the email address.
112
+ #
113
+ # @raise [ArgumentError]
114
+ # The given email address was not a valid email address.
115
+ #
116
+ # @api public
117
+ #
118
+ def self.with_email_address(email)
119
+ unless email.include?('@')
120
+ raise(ArgumentError,"invalid email address #{email.inspect}")
121
+ end
122
+
123
+ user, domain = email.split('@',2)
124
+
125
+ return joins(email_address: [:user_name, :host_name]).where(
126
+ email_address: {
127
+ ronin_user_names: {name: user},
128
+ ronin_host_names: {name: domain}
129
+ }
130
+ )
131
+ end
132
+
133
+ #
134
+ # Searches for all credentials with a common password.
135
+ #
136
+ # @param [String] password
137
+ # The password to search for.
138
+ #
139
+ # @return [Array<Credential>]
140
+ # The credentials with the common password.
141
+ #
142
+ # @api public
143
+ #
144
+ def self.with_password(password)
145
+ joins(:password).where(password: {plain_text: password})
146
+ end
147
+
148
+ #
149
+ # Looks up the given credential.
150
+ #
151
+ # @param [String] cred
152
+ # The credential String
153
+ # (ex: `user:password` or `user@example.com:password`).
154
+ #
155
+ # @return [Credential, nil]
156
+ # The found credential.
157
+ #
158
+ def self.lookup(cred)
159
+ unless cred.include?(':')
160
+ raise(ArgumentError,"credential must be of the form user:password or email:password: #{cred.inspect}")
161
+ end
162
+
163
+ user_or_email, password = cred.split(':',2)
164
+
165
+ query = if user_or_email.include?('@')
166
+ with_email_address(user_or_email)
167
+ else
168
+ for_user(user_or_email)
169
+ end
170
+ query.with_password(password)
171
+ return query.first
172
+ end
173
+
174
+ #
175
+ # Imports the given credential.
176
+ #
177
+ # @param [String] cred
178
+ # The credential String
179
+ # (ex: `user:password` or `user@example.com:password`).
180
+ #
181
+ # @return [Credential]
182
+ # The imported credential.
183
+ #
184
+ def self.import(cred)
185
+ unless cred.include?(':')
186
+ raise(ArgumentError,"credential must be of the form user:password or email:password: #{cred.inspect}")
187
+ end
188
+
189
+ user_or_email, password = cred.split(':',2)
190
+
191
+ if user_or_email.include?('@')
192
+ create(
193
+ email_address: EmailAddress.find_or_import(user_or_email),
194
+ password: Password.find_or_import(password)
195
+ )
196
+ else
197
+ create(
198
+ user_name: UserName.find_or_import(user_or_email),
199
+ password: Password.find_or_import(password)
200
+ )
201
+ end
202
+ end
203
+
204
+ #
205
+ # The user the credential belongs to.
206
+ #
207
+ # @return [String]
208
+ # The user name.
209
+ #
210
+ # @api public
211
+ #
212
+ def user
213
+ self.user_name.name if self.user_name
214
+ end
215
+
216
+ #
217
+ # The clear-text password of the credential.
218
+ #
219
+ # @return [String]
220
+ # The clear-text password.
221
+ #
222
+ # @api public
223
+ #
224
+ def plain_text
225
+ self.password.plain_text if self.password
226
+ end
227
+
228
+ #
229
+ # Converts the credentials to a String.
230
+ #
231
+ # @return [String]
232
+ # The user name and the password.
233
+ #
234
+ # @api public
235
+ #
236
+ def to_s
237
+ "#{self.user_name}:#{self.password}"
238
+ end
239
+
240
+ end
241
+ end
242
+ end
243
+
244
+ require 'ronin/db/user_name'
245
+ require 'ronin/db/email_address'
246
+ require 'ronin/db/password'
247
+ require 'ronin/db/service_credential'
248
+ require 'ronin/db/web_credential'
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-db-activerecord - ActiveRecord backend for the Ronin Database.
4
+ #
5
+ # Copyright (c) 2022 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # ronin-db-activerecord 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-db-activerecord 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-db-activerecord. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/db/model'
22
+ require 'ronin/db/model/importable'
23
+
24
+ require 'active_record'
25
+ require 'uri/mailto'
26
+
27
+ module Ronin
28
+ module DB
29
+ #
30
+ # Represents email addresses and their associated {UserName user names} and
31
+ # {HostName host names}.
32
+ #
33
+ class EmailAddress < ActiveRecord::Base
34
+
35
+ include Model
36
+ include Model::Importable
37
+
38
+ # @!attribute [rw] id
39
+ # The primary key of the email address.
40
+ #
41
+ # @return [Integer]
42
+ attribute :id, :integer
43
+
44
+ # @!attribute [rw] address
45
+ # The raw string of the email address.
46
+ #
47
+ # @return [String]
48
+ attribute :address, :string
49
+ validates :address, presence: true,
50
+ uniqueness: true,
51
+ length: {maximum: 320},
52
+ format: {
53
+ with: URI::MailTo::EMAIL_REGEXP,
54
+ message: 'Must be a valid email address'
55
+ }
56
+
57
+ # @!attribute [rw] user_name
58
+ # The user-name component of the email address.
59
+ #
60
+ # @return [UserName]
61
+ belongs_to :user_name, required: true
62
+ validates :user_name, uniqueness: {scope: [:host_name_id]}
63
+
64
+ # @!attribute [rw] host_name
65
+ # The host-name component of the email address.
66
+ #
67
+ # @return [HostName]
68
+ belongs_to :host_name, required: true
69
+
70
+ # @!attribute [rw] ip_addresses
71
+ # Any IP addresses associated with the host name.
72
+ #
73
+ # @return [Array<IPAddress>]
74
+ has_many :ip_addresses, through: :host_name,
75
+ class_name: 'IPAddress'
76
+
77
+ # @!attribute [rw] credentials
78
+ # Any web credentials that are associated with the email address.
79
+ #
80
+ # @return [Array<Credential>]
81
+ has_many :credentials, dependent: :destroy
82
+
83
+ # @!attribute [rw] created_at
84
+ # Tracks when the email address was created at.
85
+ #
86
+ # @return [Time]
87
+ attribute :created_at, :time
88
+
89
+ #
90
+ # Searches for email addresses associated with the given host name(s).
91
+ #
92
+ # @param [Array<String>, String] name
93
+ # The host name(s) to search for.
94
+ #
95
+ # @return [Array<EmailAddress>]
96
+ # The matching email addresses.
97
+ #
98
+ # @api public
99
+ #
100
+ def self.with_host_name(name)
101
+ joins(:host_name).where(host_name: {name: name})
102
+ end
103
+
104
+ #
105
+ # Searches for email addresses associated with the given IP address(es).
106
+ #
107
+ # @param [Array<String>, String] ip
108
+ # The IP address(es) to search for.
109
+ #
110
+ # @return [Array<EmailAddress>]
111
+ # The matching email addresses.
112
+ #
113
+ # @api public
114
+ #
115
+ def self.with_ip_address(ip)
116
+ joins(:ip_addresses).where(ip_addresses: {address: ip})
117
+ end
118
+
119
+ #
120
+ # Searches for email addresses associated with the given user name(s).
121
+ #
122
+ # @param [Array<String>, String] name
123
+ # The user name(s) to search for.
124
+ #
125
+ # @return [Array<EmailAddress>]
126
+ # The matching email addresses.
127
+ #
128
+ # @api public
129
+ #
130
+ def self.with_user_name(name)
131
+ joins(:user_name).where(user_name: {name: name})
132
+ end
133
+
134
+ #
135
+ # Looks up the email address.
136
+ #
137
+ # @param [String] email
138
+ # The raw email address string.
139
+ #
140
+ # @return [EmailAddress, nil]
141
+ # The found email address.
142
+ #
143
+ def self.lookup(email)
144
+ find_by(address: email)
145
+ end
146
+
147
+ #
148
+ # Imports an email address.
149
+ #
150
+ # @param [String] email
151
+ # The email address to parse.
152
+ #
153
+ # @return [EmailAddress]
154
+ # A new or previously saved email address resource.
155
+ #
156
+ # @raise [ArgumentError]
157
+ # The email address did not have a user name or a host name.
158
+ #
159
+ # @api public
160
+ #
161
+ def self.import(email)
162
+ if email =~ /\s/
163
+ raise(ArgumentError,"email address #{email.inspect} must not contain spaces")
164
+ end
165
+
166
+ normalized_email = email.downcase
167
+ user, host = normalized_email.split('@',2)
168
+
169
+ if user.empty?
170
+ raise(ArgumentError,"email address #{email.inspect} must have a user name")
171
+ end
172
+
173
+ if host.empty?
174
+ raise(ArgumentError,"email address #{email.inspect} must have a host name")
175
+ end
176
+
177
+ return create(
178
+ address: normalized_email,
179
+ user_name: UserName.find_or_import(user),
180
+ host_name: HostName.find_or_import(host)
181
+ )
182
+ end
183
+
184
+ #
185
+ # The user of the email address.
186
+ #
187
+ # @return [String]
188
+ # The user name.
189
+ #
190
+ # @api public
191
+ #
192
+ def user
193
+ self.user_name.name if self.user_name
194
+ end
195
+
196
+ #
197
+ # The host of the email address.
198
+ #
199
+ # @return [String]
200
+ # The host name.
201
+ #
202
+ # @api public
203
+ #
204
+ def host
205
+ self.host_name.name if self.host_name
206
+ end
207
+
208
+ #
209
+ # Converts the email address into a String.
210
+ #
211
+ # @return [String]
212
+ # The raw email address.
213
+ #
214
+ # @api public
215
+ #
216
+ def to_s
217
+ "#{self.user_name}@#{self.host_name}"
218
+ end
219
+
220
+ end
221
+ end
222
+ end
223
+
224
+ require 'ronin/db/user_name'
225
+ require 'ronin/db/host_name'
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-db-activerecord - ActiveRecord backend for the Ronin Database.
4
+ #
5
+ # Copyright (c) 2022 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # ronin-db-activerecord 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-db-activerecord 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-db-activerecord. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/db/model'
22
+ require 'ronin/db/model/importable'
23
+ require 'ronin/db/model/last_scanned_at'
24
+
25
+ require 'active_record'
26
+ require 'uri/rfc2396_parser'
27
+ require 'strscan'
28
+
29
+ module Ronin
30
+ module DB
31
+ #
32
+ # Represents host names and their associated {IPAddress IP addresses}.
33
+ #
34
+ class HostName < ActiveRecord::Base
35
+
36
+ include Model
37
+ include Model::Importable
38
+ include Model::LastScannedAt
39
+
40
+ # @!attribute [rw] id
41
+ # The primary ID of the host nmae.
42
+ #
43
+ # @return [Integer]
44
+ attribute :id, :integer
45
+
46
+ # @!attribute [rw] name
47
+ # The address of the host name.
48
+ #
49
+ # @return [String]
50
+ attribute :name, :string
51
+ validates :name, presence: true,
52
+ uniqueness: true,
53
+ length: {maximum: 255},
54
+ format: {
55
+ with: /\A#{URI::RFC2396_REGEXP::PATTERN::HOSTNAME}\z/,
56
+ message: 'Must be a valid host-name'
57
+ }
58
+
59
+ # @!attribute [rw] created_at
60
+ # When the host name was first created.
61
+ #
62
+ # @return [Time]
63
+ attribute :created_at, :time
64
+
65
+ # @!attribute [rw] host_name_ip_addresses
66
+ # The IP Address associations.
67
+ #
68
+ # @return [Array<HostNameIPAddress>]
69
+ has_many :host_name_ip_addresses, dependent: :destroy,
70
+ class_name: 'HostNameIPAddress'
71
+
72
+ # @!attribute [rw] ip_addresses
73
+ # The IP Addresses that host the host name.
74
+ #
75
+ # @return [Array<IPAddress>]
76
+ has_many :ip_addresses, through: :host_name_ip_addresses,
77
+ class_name: 'IPAddress'
78
+
79
+ # @!attribute [rw] open_ports
80
+ # The open ports of the host.
81
+ #
82
+ # @return [Array<OpenPort>]
83
+ has_many :open_ports, through: :ip_addresses
84
+
85
+ # @!attribute [rw] ports
86
+ # The ports of the host.
87
+ #
88
+ # @return [Array<Port>]
89
+ has_many :ports, through: :ip_addresses
90
+
91
+ # @!attribute [rw] email_addresses
92
+ # The email addresses that are associated with the host-name.
93
+ #
94
+ # @return [Array<EmailAddress>]
95
+ has_many :email_addresses
96
+
97
+ # @!attribute [rw] urls
98
+ # The URLs that point to this host name.
99
+ #
100
+ # @return [Array<URL>]
101
+ has_many :urls, class_name: 'URL'
102
+
103
+ #
104
+ # Looks up the host name.
105
+ #
106
+ # @param [String] name
107
+ # The raw host name.
108
+ #
109
+ # @return [HostName, nil]
110
+ # The found host name.
111
+ #
112
+ def self.lookup(name)
113
+ find_by(name: name)
114
+ end
115
+
116
+ #
117
+ # Creates a new host name.
118
+ #
119
+ # @param [String] name
120
+ # The host name.
121
+ #
122
+ # @return [HostName]
123
+ # The created host name record.
124
+ #
125
+ def self.import(name)
126
+ create(name: name)
127
+ end
128
+
129
+ #
130
+ # Searches for host names associated with the given IP address(es).
131
+ #
132
+ # @param [Array<String>, String] ip
133
+ # The IP address(es) to search for.
134
+ #
135
+ # @return [Array<HostName>]
136
+ # The matching host names.
137
+ #
138
+ # @api public
139
+ #
140
+ def self.with_ip_address(ip)
141
+ joins(:ip_addresses).where(ip_addresses: {address: ip})
142
+ end
143
+
144
+ #
145
+ # Searches for host names with the given open port(s).
146
+ #
147
+ # @param [Array<Integer>, Integer] number
148
+ # The open port(s) to search for.
149
+ #
150
+ # @return [Array<HostName>]
151
+ # The matching host names.
152
+ #
153
+ # @api public
154
+ #
155
+ def self.with_port_number(number)
156
+ joins(:ports).where(ports: {number: number})
157
+ end
158
+
159
+ #
160
+ # Searches for all host names under the Top-Level Domain (TLD).
161
+ #
162
+ # @param [String] name
163
+ # The Top-Level Domain (TLD).
164
+ #
165
+ # @return [Array<HostName>]
166
+ # The matching host names.
167
+ #
168
+ # @api public
169
+ #
170
+ def self.with_tld(name)
171
+ name_column = self.arel_table[:name]
172
+
173
+ where(name_column.matches("%.#{sanitize_sql_like(name)}"))
174
+ end
175
+
176
+ #
177
+ # Searches for all host names sharing a canonical domain name.
178
+ #
179
+ # @param [String] name
180
+ # The canonical domain name to search for.
181
+ #
182
+ # @return [Array<HostName>]
183
+ # The matching host names.
184
+ #
185
+ # @api public
186
+ #
187
+ def self.with_domain(name)
188
+ name_column = self.arel_table[:name]
189
+
190
+ name = sanitize_sql_like(name)
191
+
192
+ where(name: name).or(where(name_column.matches("%.#{name}")))
193
+ end
194
+
195
+ #
196
+ # The IP Address that was most recently used by the host name.
197
+ #
198
+ # @return [IPAddress]
199
+ # The IP Address that most recently used by the host name.
200
+ #
201
+ # @api public
202
+ #
203
+ def recent_ip_address
204
+ self.host_name_ip_addresses.order('created_at DESC').ip_addresses.first
205
+ end
206
+
207
+ #
208
+ # Converts the host name to a String.
209
+ #
210
+ # @return [String]
211
+ # The host name.
212
+ #
213
+ # @api public
214
+ #
215
+ def to_s
216
+ self.name.to_s
217
+ end
218
+
219
+ end
220
+ end
221
+ end
222
+
223
+ require 'ronin/db/host_name_ip_address'
224
+ require 'ronin/db/ip_address'