CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/dns/static_hostnames.rb
Views: 11704
1
# -*- coding: binary -*-
2
3
require 'rex/socket'
4
require 'forwardable'
5
6
module Rex
7
module Proto
8
module DNS
9
##
10
# This class manages statically defined hostnames for DNS resolution where each name is a mapping to an IPv4 and or
11
# an IPv6 address. A single hostname can only map to one address of each family.
12
##
13
class StaticHostnames
14
extend Forwardable
15
16
def_delegators :@hostnames, :each, :each_with_index, :length, :empty?, :sort_by
17
18
# @param [Hash<String, IPAddr>] hostnames The hostnames to IP address mappings to initialize with.
19
def initialize(hostnames: nil)
20
@hostnames = {}
21
if hostnames
22
hostnames.each do |hostname, ip_address|
23
add(hostname, ip_address)
24
end
25
end
26
end
27
28
# Locate and parse a hosts file on the system. Only the first hostname to IP address definition is used which
29
# replicates the behavior of /etc/hosts on Linux. Loaded definitions are merged with existing definitions.
30
def parse_hosts_file
31
path = %w[
32
%WINDIR%\system32\drivers\etc\hosts
33
/etc/hosts
34
/data/data/com.termux/files/usr/etc/hosts
35
].find do |path|
36
path = File.expand_path(path)
37
File.file?(path) && File.readable?(path)
38
end
39
return unless path
40
41
path = File.expand_path(path)
42
::IO.foreach(path) do |line|
43
words = line.split
44
next unless words.length > 1 && Rex::Socket.is_ip_addr?(words.first)
45
46
ip_address = IPAddr.new(words.shift)
47
words.each do |hostname|
48
add(hostname, ip_address)
49
end
50
end
51
end
52
53
# Get an IP address of the specified type for the hostname. Only the first address is returned in cases where
54
# multiple addresses are defined.
55
#
56
# @param [String] hostname The hostname to retrieve an address for.
57
# @param [Integer] type The family of address to return represented as a DNS type (either A or AAAA).
58
# @return Returns the IP address if it was found, otherwise nil.
59
# @rtype [IPAddr, nil]
60
def get1(hostname, type = Dnsruby::Types::A)
61
get(hostname, type).first
62
end
63
64
# Get all IP addresses of the specified type for the hostname.
65
#
66
# @param [String] hostname The hostname to retrieve an address for.
67
# @param [Integer] type The family of address to return represented as a DNS type (either A or AAAA).
68
# @return Returns an array of IP addresses.
69
# @rtype [Array<IPAddr>]
70
def get(hostname, type = Dnsruby::Types::A)
71
hostname = hostname.downcase
72
@hostnames.fetch(hostname, {}).fetch(type, []).dup
73
end
74
75
# Add an IP address for the specified hostname.
76
#
77
# @param [String] hostname The hostname whose IP address is being defined.
78
# @param [IPAddr, String] ip_address The IP address that is being defined for the hostname. If this value is a
79
# string, it will be converted to an IPAddr instance.
80
def add(hostname, ip_address)
81
unless self.class.is_valid_hostname?(hostname)
82
# it is important to validate the hostname because assumptions about what characters it may contain are made
83
# when saving and loading it from the configuration
84
raise ::ArgumentError.new("Invalid hostname: #{hostname}")
85
end
86
87
ip_address = IPAddr.new(ip_address) if ip_address.is_a?(String) && Rex::Socket.is_ip_addr?(ip_address)
88
89
hostname = hostname.downcase.delete_suffix('.')
90
this_host = @hostnames.fetch(hostname, {})
91
if ip_address.family == ::Socket::AF_INET
92
type = Dnsruby::Types::A
93
else
94
type = Dnsruby::Types::AAAA
95
end
96
this_type = this_host.fetch(type, [])
97
this_type << ip_address unless this_type.include?(ip_address)
98
this_host[type] = this_type
99
@hostnames[hostname] = this_host
100
nil
101
end
102
103
# Delete an IP address for the specified hostname.
104
#
105
# @param [String] hostname The hostname whose IP address is being undefined.
106
# @param [IPAddr, String] ip_address The IP address that is being undefined. If this value is a string, it will be
107
# converted to an IPAddr instance.
108
def delete(hostname, ip_address)
109
ip_address = IPAddr.new(ip_address) if ip_address.is_a?(String) && Rex::Socket.is_ip_addr?(ip_address)
110
if ip_address.family == ::Socket::AF_INET
111
type = Dnsruby::Types::A
112
else
113
type = Dnsruby::Types::AAAA
114
end
115
116
hostname = hostname.downcase
117
this_host = @hostnames.fetch(hostname, {})
118
this_type = this_host.fetch(type, [])
119
this_type.delete(ip_address)
120
if this_type.empty?
121
this_host.delete(type)
122
else
123
this_host[type] = this_type
124
end
125
if this_host.empty?
126
@hostnames.delete(hostname)
127
else
128
@hostnames[hostname] = this_host
129
end
130
131
nil
132
end
133
134
# Delete all hostname to IP address definitions.
135
def flush
136
@hostnames.clear
137
end
138
139
def self.is_valid_hostname?(name)
140
# check if it appears to be a fully qualified domain name, e.g. www.metasploit.com
141
return true if Rex::Socket.is_name?(name)
142
143
# check if it appears to at least be a valid hostname, e.g. localhost
144
return true if (name =~ /^([a-z0-9][a-z0-9\-]{0,61})?[a-z0-9]$/i) && (name =~ /\s/).nil?
145
146
false
147
end
148
end
149
end
150
end
151
end
152
153