# frozen_string_literal: true
#
# ronin-db-activerecord - ActiveRecord backend for the Ronin Database.
#
# Copyright (c) 2022-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# ronin-db-activerecord is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ronin-db-activerecord is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ronin-db-activerecord. If not, see .
#
require 'ronin/db/address'
require 'active_record'
require 'ipaddr'
require 'resolv'
module Ronin
module DB
#
# Represents IP addresses and their associated {HostName host names}
# and {MACAddress MAC addresses}.
#
class IPAddress < Address
# @!attribute [rw] address
# The IP Address.
#
# @return [String]
attribute :address, :string
validates :address, presence: true,
uniqueness: true,
length: {maximum: 39},
format: {
with: /#{Resolv::IPv4::Regex}|#{Resolv::IPv6::Regex}/,
message: 'Must be a valid IP address'
}
# @!attribute [rw] hton
# The IP address, but in network byte-order.
#
# @return [String]
attribute :hton, :binary
before_save :set_hton
# @!attribute [rw] version
# Type of the address.
#
# @return [Integer]
attribute :version, :integer
validates :version, inclusion: {in: [4, 6]}
# @!attribute [rw] ip_address_mac_addresses
# The MAC Addresses associations.
#
# @return [Array]
has_many :ip_address_mac_addresses, dependent: :destroy,
class_name: 'IPAddressMACAddress'
# @!attribute [rw] mac_addresses
# The MAC Addresses associated with the IP Address.
#
# @return [Array]
has_many :mac_addresses, through: :ip_address_mac_addresses,
class_name: 'MACAddress'
# @!attribute [rw] host_name_ip_addresses
# The host-names that the IP Address serves.
#
# @return [Array]
has_many :host_name_ip_addresses, dependent: :destroy,
class_name: 'HostNameIPAddress'
# @!attribute [rw] host_names
# The host-names associated with the IP Address.
#
# @return [Array]
has_many :host_names, through: :host_name_ip_addresses
# @!attribute [rw] open_ports
# The open ports of the host.
#
# @return [Array]
has_many :open_ports, dependent: :destroy
# @!attribute [rw] ports
# The ports of the host.
#
# @return [Array]
has_many :ports, through: :open_ports
# @!attribute [rw] os_guesses
# Any OS guesses against the IP Address.
#
# @return [Array]
has_many :os_guesses, dependent: :destroy,
class_name: 'OSGuess'
# @!attribute [rw] oses
# Any OSes that the IP Address might be running
#
# @return [Array]
has_many :oses, through: :os_guesses,
class_name: 'OS'
#
# Searches for all IPv4 addresses.
#
# @return [Array]
# The IPv4 addresses.
#
# @api public
#
def self.v4
where(version: 4)
end
#
# Searches for all IPv6 addresses.
#
# @return [Array]
# The IPv6 addresses.
#
# @api public
#
def self.v6
where(version: 6)
end
#
# Queries all IP address that are between the first IP address and last IP
# address.
#
# @param [String] first_ip
# The first IP of the IP range.
#
# @param [String] last_ip
# The last IP of the IP range.
#
# @return [Array]
#
def self.between(first_ip,last_ip)
first_ip_hton = IPAddr.new(first_ip).hton
last_ip_hton = IPAddr.new(last_ip).hton
hton = arel_table[:hton]
where(hton.gteq(first_ip_hton).and(hton.lteq(last_ip_hton)))
end
#
# Queries all IP addresses that exist in the range of IP addresses.
#
# @param [Range, #begin, #end] range
# The IP range to query.
#
# @return [Array]
#
def self.in_range(range)
between(range.begin,range.end)
end
#
# Searches for all IP addresses associated with specific MAC address(es).
#
# @param [Array, String] mac
# The MAC address(es) to search for.
#
# @return [Array]
# The matching IP addresses.
#
# @api public
#
def self.with_mac_address(mac)
joins(:mac_addresses).where(mac_addresses: {address: mac})
end
#
# Searches for IP addresses associated with the given host name(s).
#
# @param [Array, String] name
# The host name(s) to search for.
#
# @return [Array]
# The matching IP addresses.
#
# @api public
#
def self.with_host_name(name)
joins(:host_names).where(host_names: {name: name})
end
#
# Searches for IP addresses with the given open port(s).
#
# @param [Array, Integer] number
# The port number(s) to search for.
#
# @return [Array]
# The matching IP addresses.
#
# @api public
#
def self.with_port_number(number)
joins(:ports).where(ports: {number: number})
end
#
# Initializes the IP address record.
#
# @param [Array] arguments
# Additional attribute arguments.
#
# @param [Hash{Symbol => Object}] kwargs
# Additional attribute values.
#
# @note Also assigns a default value to `version` based on the `address`.
#
def initialize(*arguments,**kwargs)
super(*arguments,**kwargs)
self.version ||= if ipaddr
if ipaddr.ipv6? then 6
else 4
end
end
end
#
# Sets the IP address'es address.
#
# @param [String, IPAddr, nil] new_address
# The new address to use.
#
# @return [String, IPAddr, nil]
# The IP address'es new address.
#
def address=(new_address)
@ipaddr = nil
super(new_address)
end
#
# Returns an `IPAddr` object for the IP address.
#
# @return [IPAddr]
# The IPAddr object representing either the IPv4 or IPv6 address.
#
# @api public
#
def ipaddr
@ipaddr ||= if self.address
begin
IPAddr.new(self.address)
rescue IPAddr::InvalidAddressError
end
end
end
#
# Queries the {ASN} record for the IP address.
#
# @return [ASN, nil]
#
def asn
ASN.containing_ip(ipaddr)
end
#
# The MAC Address that was most recently used by the IP Address.
#
# @return [MACAddress]
# The MAC Address that most recently used the IP Address.
#
# @api public
#
def recent_mac_address
self.ip_address_mac_addresses.order('created_at DESC').mac_addresses.first
end
#
# The host-name that was most recently used by the IP Address.
#
# @return [HostName]
# The host-name that most recently used by the IP Address.
#
# @api public
#
def recent_host_name
self.host_name_ip_addresses.order('created_at DESC').host_names.first
end
#
# The Operating System that was most recently guessed for the IP
# Address.
#
# @return [OS]
# The Operating System that most recently was guessed.
#
# @api public
#
def recent_os_guess
self.os_guesses.order('created_at DESC').oses.first
end
alias to_ip ipaddr
#
# Converts the address to an Integer.
#
# @return [Integer]
# The network representation of the IP address.
#
# @api public
#
def to_i
ipaddr.to_i
end
private
#
# Sets the `hton` attribute.
#
def set_hton
self.hton = self.ipaddr.hton
end
end
end
end
require 'ronin/db/ip_address_mac_address'
require 'ronin/db/mac_address'
require 'ronin/db/host_name_ip_address'
require 'ronin/db/host_name'
require 'ronin/db/open_port'
require 'ronin/db/port'
require 'ronin/db/host_name'
require 'ronin/db/os_guess'
require 'ronin/db/os'
require 'ronin/db/asn'