Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 70 additions & 61 deletions ext/win32/lib/win32/resolv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@

=end

require 'win32/registry'

module Win32
module Resolv
API = Registry::API
Error = Registry::Error

def self.get_hosts_path
path = get_hosts_dir
path = File.expand_path('hosts', path)
Expand Down Expand Up @@ -47,89 +42,103 @@ module Win32
# Windows NT
#====================================================================
module Resolv
module SZ
refine Registry do
# ad hoc workaround for broken registry
def read_s(key)
type, str = read(key)
unless type == Registry::REG_SZ
warn "Broken registry, #{name}\\#{key} was #{Registry.type2name(type)}, ignored"
return String.new
begin
require 'win32/registry'
module SZ
refine Registry do
# ad hoc workaround for broken registry
def read_s(key)
type, str = read(key)
unless type == Registry::REG_SZ
warn "Broken registry, #{name}\\#{key} was #{Registry.type2name(type)}, ignored"
return String.new
end
str
end
str
end
end
using SZ
rescue LoadError
require "open3"
end
using SZ

TCPIP_NT = 'SYSTEM\CurrentControlSet\Services\Tcpip\Parameters'

class << self
private
def get_hosts_dir
Registry::HKEY_LOCAL_MACHINE.open(TCPIP_NT) do |reg|
reg.read_s_expand('DataBasePath')
end
get_item_property(TCPIP_NT, 'DataBasePath', expand: true)
end

def get_info
search = nil
nameserver = get_dns_server_list
Registry::HKEY_LOCAL_MACHINE.open(TCPIP_NT) do |reg|
begin
slist = reg.read_s('SearchList')
search = slist.split(/,\s*/) unless slist.empty?
rescue Registry::Error
end

if add_search = search.nil?
search = []
begin
nvdom = reg.read_s('NV Domain')
unless nvdom.empty?
@search = [ nvdom ]
if reg.read_i('UseDomainNameDevolution') != 0
if /^\w+\./ =~ nvdom
devo = $'
end
end
slist = get_item_property(TCPIP_NT, 'SearchList')
search = slist.split(/,\s*/) unless slist.empty?

if add_search = search.nil?
search = []
nvdom = get_item_property(TCPIP_NT, 'NV Domain')

unless nvdom.empty?
@search = [ nvdom ]
udmnd = get_item_property(TCPIP_NT, 'UseDomainNameDevolution').to_i
if udmnd != 0
if /^\w+\./ =~ nvdom
devo = $'
end
rescue Registry::Error
end
end
end

reg.open('Interfaces') do |h|
h.each_key do |iface, |
h.open(iface) do |regif|
next unless ns = %w[NameServer DhcpNameServer].find do |key|
begin
ns = regif.read_s(key)
rescue Registry::Error
else
break ns.split(/[,\s]\s*/) unless ns.empty?
end
end
next if (nameserver & ns).empty?

if add_search
begin
[ 'Domain', 'DhcpDomain' ].each do |key|
dom = regif.read_s(key)
unless dom.empty?
search.concat(dom.split(/,\s*/))
break
end
end
rescue Registry::Error
end
ifs = if defined?(Win32::Registry)
Registry::HKEY_LOCAL_MACHINE.open(TCPIP_NT + '\Interfaces') do |reg|
reg.keys
rescue Registry::Error
[]
end
else
cmd = "Get-ChildItem 'HKLM:\\#{TCPIP_NT}\\Interfaces' | ForEach-Object { $_.PSChildName }"
output, _ = Open3.capture2('powershell', '-Command', cmd)
output.split(/\n+/)
end

ifs.each do |iface|
next unless ns = %w[NameServer DhcpNameServer].find do |key|
ns = get_item_property(TCPIP_NT + '\Interfaces' + "\\#{iface}", key)
break ns.split(/[,\s]\s*/) unless ns.empty?
end

next if (nameserver & ns).empty?

if add_search
[ 'Domain', 'DhcpDomain' ].each do |key|
dom = get_item_property(TCPIP_NT + '\Interfaces' + "\\#{iface}", key)
unless dom.empty?
search.concat(dom.split(/,\s*/))
break
end
end
end
search << devo if add_search and devo
end
search << devo if add_search and devo
[ search.uniq, nameserver.uniq ]
end

def get_item_property(path, name, expand: false)
if defined?(Win32::Registry)
Registry::HKEY_LOCAL_MACHINE.open(path) do |reg|
expand ? reg.read_s_expand(name) : reg.read_s(name)
rescue Registry::Error
""
end
else
cmd = "Get-ItemProperty -Path 'HKLM:\\#{path}' -Name '#{name}' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty '#{name}'"
output, _ = Open3.capture2('powershell', '-Command', cmd)
output.strip
end
end
end
end
end
2 changes: 2 additions & 0 deletions ext/win32/resolv/resolv.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ w32error_make_error(DWORD e)
return rb_class_new_instance(1, &code, rb_path2class("Win32::Resolv::Error"));
}

NORETURN(static void w32error_raise(DWORD e));

static void
w32error_raise(DWORD e)
{
Expand Down
72 changes: 50 additions & 22 deletions lib/resolv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

class Resolv

VERSION = "0.6.0"
VERSION = "0.6.2"

##
# Looks up the first IP address for +name+.
Expand Down Expand Up @@ -173,13 +173,16 @@ class ResolvError < StandardError; end

class ResolvTimeout < Timeout::Error; end

WINDOWS = /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/
private_constant :WINDOWS

##
# Resolv::Hosts is a hostname resolver that uses the system hosts file.

class Hosts
if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and
if WINDOWS
begin
require 'win32/resolv'
require 'win32/resolv' unless defined?(Win32::Resolv)
DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL
rescue LoadError
end
Expand Down Expand Up @@ -659,8 +662,20 @@ def self.free_request_id(host, port, id) # :nodoc:
}
end

def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
begin
case RUBY_PLATFORM
when *[
# https://www.rfc-editor.org/rfc/rfc6056.txt
# Appendix A. Survey of the Algorithms in Use by Some Popular Implementations
/freebsd/, /linux/, /netbsd/, /openbsd/, /solaris/,
/darwin/, # the same as FreeBSD
] then
def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
udpsock.bind(bind_host, 0)
end
else
# Sequential port assignment
def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
# Ephemeral port number range recommended by RFC 6056
port = random(1024..65535)
udpsock.bind(bind_host, port)
rescue Errno::EADDRINUSE, # POSIX
Expand Down Expand Up @@ -983,13 +998,13 @@ def Config.parse_resolv_conf(filename)
next unless keyword
case keyword
when 'nameserver'
nameserver.concat(args)
nameserver.concat(args.each(&:freeze))
when 'domain'
next if args.empty?
search = [args[0]]
search = [args[0].freeze]
when 'search'
next if args.empty?
search = args
search = args.each(&:freeze)
when 'options'
args.each {|arg|
case arg
Expand All @@ -1000,22 +1015,22 @@ def Config.parse_resolv_conf(filename)
end
}
}
return { :nameserver => nameserver, :search => search, :ndots => ndots }
return { :nameserver => nameserver.freeze, :search => search.freeze, :ndots => ndots.freeze }.freeze
end

def Config.default_config_hash(filename="/etc/resolv.conf")
if File.exist? filename
config_hash = Config.parse_resolv_conf(filename)
Config.parse_resolv_conf(filename)
elsif WINDOWS
require 'win32/resolv' unless defined?(Win32::Resolv)
search, nameserver = Win32::Resolv.get_resolv_info
config_hash = {}
config_hash[:nameserver] = nameserver if nameserver
config_hash[:search] = [search].flatten if search
config_hash
else
if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
require 'win32/resolv'
search, nameserver = Win32::Resolv.get_resolv_info
config_hash = {}
config_hash[:nameserver] = nameserver if nameserver
config_hash[:search] = [search].flatten if search
end
{}
end
config_hash || {}
end

def lazy_initialize
Expand Down Expand Up @@ -1664,6 +1679,7 @@ def get_labels
prev_index = @index
save_index = nil
d = []
size = -1
while true
raise DecodeError.new("limit exceeded") if @limit <= @index
case @data.getbyte(@index)
Expand All @@ -1684,7 +1700,10 @@ def get_labels
end
@index = idx
else
d << self.get_label
l = self.get_label
d << l
size += 1 + l.string.bytesize
raise DecodeError.new("name label data exceed 255 octets") if size > 255
end
end
end
Expand Down Expand Up @@ -2110,7 +2129,14 @@ class Resource < Query

attr_reader :ttl

ClassHash = {} # :nodoc:
ClassHash = Module.new do
module_function

def []=(type_class_value, klass)
type_value, class_value = type_class_value
Resource.const_set(:"Type#{type_value}_Class#{class_value}", klass)
end
end

def encode_rdata(msg) # :nodoc:
raise NotImplementedError.new
Expand Down Expand Up @@ -2148,7 +2174,9 @@ def hash # :nodoc:
end

def self.get_class(type_value, class_value) # :nodoc:
return ClassHash[[type_value, class_value]] ||
cache = :"Type#{type_value}_Class#{class_value}"

return (const_defined?(cache) && const_get(cache)) ||
Generic.create(type_value, class_value)
end

Expand Down Expand Up @@ -2577,7 +2605,7 @@ def initialize(flags, tag, value)
end

##
# Flags for this proprty:
# Flags for this property:
# - Bit 0 : 0 = not critical, 1 = critical

attr_reader :flags
Expand Down
7 changes: 7 additions & 0 deletions test/resolv/test_dns.rb
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,13 @@ def test_too_big_label_address
assert_operator(2**14, :<, m.to_s.length)
end

def test_too_long_address
too_long_address_message = [0, 0, 1, 0, 0, 0].pack("n*") + "\x01x" * 129 + [0, 0, 0].pack("cnn")
assert_raise_with_message(Resolv::DNS::DecodeError, /name label data exceed 255 octets/) do
Resolv::DNS::Message.decode too_long_address_message
end
end

def assert_no_fd_leak
socket = assert_throw(self) do |tag|
Resolv::DNS.stub(:bind_random_port, ->(s, *) {throw(tag, s)}) do
Expand Down
Loading