lib/nmap/xml/host.rb
# frozen_string_literal: true
require_relative 'status'
require_relative 'address'
require_relative 'hostname'
require_relative 'os'
require_relative 'port'
require_relative 'ip_id_sequence'
require_relative 'tcp_sequence'
require_relative 'tcp_ts_sequence'
require_relative 'uptime'
require_relative 'traceroute'
require_relative 'host_script'
require 'nokogiri'
require 'time'
module Nmap
class XML
#
# Wraps a `host` XML element.
#
# @since 1.0.0
#
class Host
include Enumerable
#
# Creates a new Host object.
#
# @param [Nokogiri::XML::Node] node
# The XML node that contains the host information.
#
def initialize(node)
@node = node
end
#
# The time the host was first scanned.
#
# @return [Time]
# The time the host was first scanned.
#
# @since 0.1.2
#
def start_time
@start_time ||= Time.at(@node['starttime'].to_i)
end
#
# The time the host was last scanned.
#
# @return [Time]
# The time the host was last scanned.
#
# @since 0.1.2
#
def end_time
@end_time ||= Time.at(@node['endtime'].to_i)
end
#
# Parses the status of the host.
#
# @return [Status]
# The status of the host.
#
def status
unless @status
status = @node.at_xpath('status')
@status = Status.new(
status['state'].to_sym,
status['reason'],
status['reason_ttl'].to_i
)
end
return @status
end
#
# Parses each address of the host.
#
# @yield [addr]
# Each parsed address will be pass to a given block.
#
# @yieldparam [Address] addr
# A address of the host.
#
# @return [Host, Enumerator]
# The host.
# If no block was given, an enumerator will be returned.
#
def each_address
return enum_for(__method__) unless block_given?
@node.xpath("address[@addr]").each do |addr|
address = Address.new(
addr['addrtype'].to_sym,
addr['addr'],
addr['vendor']
)
yield address
end
return self
end
#
# Parses the addresses of the host.
#
# @return [Array<Host>]
# The addresses of the host.
#
def addresses
each_address.to_a
end
#
# Parses the MAC address of the host.
#
# @return [String]
# The MAC address of the host.
#
def mac
@mac ||= if (addr = @node.at_xpath("address[@addrtype='mac']"))
addr['addr']
end
end
#
# Parses the MAC vendor of the host.
#
# @return [String]
# The Mac Vendor of the host.
#
# @since 0.8.0
#
def vendor
@vendor ||= if (vendor = @node.at_xpath("address/@vendor"))
vendor.inner_text
end
end
#
# Parses the IPv4 address of the host.
#
# @return [String]
# The IPv4 address of the host.
#
def ipv4
@ipv4 ||= if (addr = @node.at_xpath("address[@addrtype='ipv4']"))
addr['addr']
end
end
#
# Parses the IPv6 address of the host.
#
# @return [String]
# The IPv6 address of the host.
#
def ipv6
@ipv6 ||= if (addr = @node.at_xpath("address[@addrtype='ipv6']"))
addr['addr']
end
end
#
# The IP address of the host.
#
# @return [String]
# The IPv4 or IPv6 address of the host.
#
def ip
ipv6 || ipv4
end
#
# The address of the host.
#
# @return [String]
# The IP or MAC address of the host.
#
def address
ip || mac
end
#
# Parses the hostnames of the host.
#
# @yield [host]
# Each parsed hostname will be passed to the given block.
#
# @yieldparam [Hostname] host
# A hostname of the host.
#
# @return [Host, Enumerator]
# The host.
# If no block was given, an enumerator will be returned.
#
def each_hostname
return enum_for(__method__) unless block_given?
@node.xpath("hostnames/hostname[@name]").each do |host|
yield Hostname.new(host['type'],host['name'])
end
return self
end
#
# Parses the hostnames of the host.
#
# @return [Array<Hostname>]
# The hostnames of the host.
#
def hostnames
each_hostname.to_a
end
#
# The primary hostname of the host.
#
# @return [Hostname, nil]
#
# @since 0.8.0
#
def hostname
each_hostname.first
end
#
# Parses the OS guessing information of the host.
#
# @yield [os]
# If a block is given, it will be passed the OS guessing information.
#
# @yieldparam [OS] os
# The OS guessing information.
#
# @return [OS]
# The OS guessing information.
#
def os
@os ||= if (os = @node.at_xpath('os'))
OS.new(os)
end
yield @os if (@os && block_given?)
return @os
end
#
# Parses the Uptime analysis of the host.
#
# @yield [uptime]
# If a block is given, it will be passed the resulting object
#
# @yieldparam [Uptime]
# Uptime value.
#
# @return [Uptime]
# The parsed object.
#
# @since 0.7.0
#
def uptime
@uptime ||= if (uptime = @node.at_xpath('uptime'))
Uptime.new(
uptime['seconds'].to_i,
Time.parse(uptime['lastboot'])
)
end
yield @uptime if (@uptime && block_given?)
return @uptime
end
#
# Parses the TCP Sequence number analysis of the host.
#
# @yield [sequence]
# If a block is given, it will be passed the resulting object
#
# @yieldparam [TcpSequence] sequence
# TCP Sequence number analysis.
#
# @return [TcpSequence]
# The parsed object.
#
def tcp_sequence
@tcp_sequence ||= if (seq = @node.at_xpath('tcpsequence'))
TcpSequence.new(seq)
end
yield @tcp_sequence if (@tcp_sequence && block_given?)
return @tcp_sequence
end
#
# Parses the IPID sequence number analysis of the host.
#
# @yield [ipidsequence]
# If a block is given, it will be passed the resulting object
#
# @yieldparam [IpIdSequence] ipidsequence
# IPID Sequence number analysis.
#
# @return [IpIdSequence]
# The parsed object.
#
def ip_id_sequence
@ip_id_sequence ||= if (seq = @node.at_xpath('ipidsequence'))
IpIdSequence.new(seq)
end
yield @ip_id_sequence if (@ip_id_sequence && block_given?)
return @ip_id_sequence
end
#
# Parses the TCP Timestamp sequence number analysis of the host.
#
# @yield [tcptssequence]
# If a block is given, it will be passed the resulting object
#
# @yieldparam [TcpTsSequence] tcptssequence
# TCP Timestamp Sequence number analysis.
#
# @return [TcpTsSequence]
# The parsed object.
#
def tcp_ts_sequence
@tcp_ts_sequence ||= if (seq = @node.at_xpath('tcptssequence'))
TcpTsSequence.new(seq)
end
yield @tcp_ts_sequence if (@tcp_ts_sequence && block_given?)
return @tcp_ts_sequence
end
#
# Parses the scanned ports of the host.
#
# @yield [port]
# Each scanned port of the host.
#
# @yieldparam [Port] port
# A scanned port of the host.
#
# @return [Host, Enumerator]
# The host.
# If no block was given, an enumerator will be returned.
#
def each_port
return enum_for(__method__) unless block_given?
@node.xpath("ports/port").each do |port|
yield Port.new(port)
end
return self
end
#
# Parses the scanned ports of the host.
#
# @return [Array<Port>]
# The scanned ports of the host.
#
def ports
each_port.to_a
end
#
# Parses the open ports of the host.
#
# @yield [port]
# Each open port of the host.
#
# @yieldparam [Port] port
# An open scanned port of the host.
#
# @return [Host, Enumerator]
# The host.
# If no block was given, an enumerator will be returned.
#
def each_open_port
return enum_for(__method__) unless block_given?
@node.xpath("ports/port[state/@state='open']").each do |port|
yield Port.new(port)
end
return self
end
#
# Parses the open ports of the host.
#
# @return [Array<Port>]
# The open ports of the host.
#
def open_ports
each_open_port.to_a
end
#
# Parses the TCP ports of the host.
#
# @yield [port]
# Each TCP port of the host.
#
# @yieldparam [Port] port
# An TCP scanned port of the host.
#
# @return [Host, Enumerator]
# The host.
# If no block was given, an enumerator will be returned.
#
def each_tcp_port
return enum_for(__method__) unless block_given?
@node.xpath("ports/port[@protocol='tcp']").each do |port|
yield Port.new(port)
end
return self
end
#
# Parses the TCP ports of the host.
#
# @return [Array<Port>]
# The TCP ports of the host.
#
def tcp_ports
each_tcp_port.to_a
end
#
# Parses the UDP ports of the host.
#
# @yield [port]
# Each UDP port of the host.
#
# @yieldparam [Port] port
# An UDP scanned port of the host.
#
# @return [Host, Enumerator]
# The host.
# If no block was given, an enumerator will be returned.
#
def each_udp_port
return enum_for(__method__) unless block_given?
@node.xpath("ports/port[@protocol='udp']").each do |port|
yield Port.new(port)
end
return self
end
#
# Parses the UDP ports of the host.
#
# @return [Array<Port>]
# The UDP ports of the host.
#
def udp_ports
each_udp_port.to_a
end
#
# Parses the open ports of the host.
#
# @see each_open_port
#
def each(&block)
each_open_port(&block)
end
#
# The NSE scripts ran against the host.
#
# @return [HostScript, nil]
# Contains the host script output and data.
#
# @since 0.9.0
#
def host_script
@host_script ||= if (hostscript = @node.at_xpath('hostscript'))
HostScript.new(hostscript)
end
end
#
# Parses the traceroute information, if present.
#
# @yield [traceroute]
# If a block is given, it will be passed the traceroute information.
#
# @yieldparam [Traceroute] traceroute
# The traceroute information.
#
# @return [Traceroute]
# The traceroute information.
#
# @since 0.7.0
#
def traceroute
@traceroute ||= if (trace = @node.at_xpath('trace'))
Traceroute.new(trace)
end
yield @traceroute if (@traceroute && block_given?)
return @traceroute
end
#
# Converts the host to a String.
#
# @return [String]
# The hostname or address of the host.
#
# @see address
#
def to_s
(hostname || address).to_s
end
#
# Inspects the host.
#
# @return [String]
# The inspected host.
#
def inspect
"#<#{self.class}: #{self}>"
end
end
end
end