|
require 'rubygems'
|
|
require 'win32/open3'
|
|
|
|
module Proxy::DHCP
|
|
# Represents Microsoft DHCP Server manipulated via the netsh command
|
|
# executed on a Microsoft server under a service account
|
|
class NativeMS < Server
|
|
|
|
def initialize(options = {})
|
|
super options[:server]
|
|
end
|
|
|
|
def delRecord subnet, record
|
|
validate_subnet subnet
|
|
validate_record record
|
|
# TODO: Refactor this into the base class
|
|
raise InvalidRecord, "#{record} is static - unable to delete" unless record.deleteable?
|
|
|
|
mac = record.mac.gsub(/:/,"")
|
|
msg = "Removed DHCP reservation for #{record.name} => #{record.ip} - #{record.mac}"
|
|
cmd = "scope #{subnet.network} delete reservedip #{record.ip} #{mac}"
|
|
|
|
execute(cmd, msg)
|
|
subnet.delete(record)
|
|
end
|
|
|
|
def addRecord options={}
|
|
super(options)
|
|
ip = options[:ip]
|
|
mac = options[:mac]
|
|
name = options[:hostname]
|
|
subnet = find_subnet(IPAddr.new(ip))
|
|
|
|
msg = "Added DHCP reservation for #{name} => #{ip} - #{mac}"
|
|
cmd = "scope #{subnet.network} add reservedip #{ip} #{mac.gsub(/:/,"")} #{name}"
|
|
execute(cmd, msg)
|
|
|
|
return if options[:nextserver].nil? # This reservation is just for an IP and MAC
|
|
|
|
# TODO: Refactor these execs into a popen
|
|
cmd = "scope #{subnet.network} set reservedoptionvalue #{ip} #{Optcode[:filename]} String #{options[:filename]}"
|
|
execute(cmd, msg, true)
|
|
|
|
cmd = "scope #{subnet.network} set reservedoptionvalue #{ip} #{Optcode[:nextserver]} String #{options[:nextserver]}"
|
|
execute(cmd, msg, true)
|
|
|
|
cmd = "scope #{subnet.network} set reservedoptionvalue #{ip} #{Optcode[:hostname]} String #{options[:hostname]}"
|
|
execute(cmd, msg, true)
|
|
|
|
record = Proxy::DHCP::Reservation.new subnet, ip, mac, options
|
|
end
|
|
|
|
def loadSubnetData subnet
|
|
super
|
|
cmd = "scope #{subnet.network} show reservedip"
|
|
msg = "Enumerated hosts on #{subnet.network}"
|
|
|
|
# Extract the data
|
|
execute(cmd, msg).each do |line|
|
|
# 172.29.216.6 - 00-a0-e7-21-41-00-
|
|
if line =~ /^\s+([\w\.]+)\s+-\s+([-a-f\d]+)/
|
|
ip = $1
|
|
mac = $2.gsub(/-/,":").match(/^(.*?).$/)[1]
|
|
begin
|
|
Proxy::DHCP::Reservation.new(subnet, ip, mac) if subnet.include? ip
|
|
rescue Exception => e
|
|
logger.warn "Skipped #{line} - #{e}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def loadSubnetOptions subnet
|
|
super subnet
|
|
raise "invalid Subnet" unless subnet.is_a? Proxy::DHCP::Subnet
|
|
cmd = "scope #{subnet.network} Show OptionValue"
|
|
msg = "Queried #{subnet.network} options"
|
|
|
|
subnet.options = parse_options(execute(cmd, msg))
|
|
end
|
|
|
|
def loadRecordOptions record
|
|
raise "invalid Record" unless record.is_a? Proxy::DHCP::Record
|
|
subnet = record.subnet
|
|
raise "unable to find subnet for #{record}" if subnet.nil?
|
|
cmd = "scope #{subnet.network} Show ReservedOptionValue #{record.ip}"
|
|
msg = "Queried #{record.ip} options"
|
|
|
|
record.options = parse_options(execute(cmd, msg)).merge(:ip => record.ip, :mac => record.mac)
|
|
end
|
|
|
|
|
|
private
|
|
def loadSubnets
|
|
super
|
|
cmd = "show scope"
|
|
msg = "Enumerated the scopes on #{@name}"
|
|
|
|
execute(cmd, msg).each do |line|
|
|
# 172.29.216.0 - 255.255.254.0 -Active -DC BRS -
|
|
if match = line.match(/^\s*([\d\.]+)\s*-\s*([\d\.]+)\s*-\s*(Active|Disabled)/)
|
|
subnet = Proxy::DHCP::Subnet.new(self, match[1], match[2])
|
|
end
|
|
end
|
|
end
|
|
|
|
def execute cmd, msg=nil, error_only=false
|
|
tsecs = 5
|
|
response = nil
|
|
interpreter = SETTINGS.x86_64 ? 'c:\windows\sysnative\cmd.exe' : 'c:\windows\system32\cmd.exe'
|
|
command = interpreter + ' /c c:\Windows\System32\netsh.exe -c dhcp ' + "server #{name} #{cmd}"
|
|
|
|
std_in = std_out = std_err = nil
|
|
begin
|
|
timeout(tsecs) do
|
|
std_in, std_out, std_err = Open3.popen3(command)
|
|
response = std_out.readlines
|
|
response += std_err.readlines
|
|
end
|
|
rescue TimeoutError
|
|
raise Proxy::DHCP::Error.new("Netsh did not respond within #{tsecs} seconds")
|
|
ensure
|
|
std_in.close unless std_in.nil?
|
|
std_out.close unless std_in.nil?
|
|
std_err.close unless std_in.nil?
|
|
end
|
|
report msg, response, error_only
|
|
response
|
|
end
|
|
|
|
def report msg, response, error_only
|
|
if response.grep(/completed successfully/).empty?
|
|
logger.error "Netsh failed:\n" + response.join("\n")
|
|
msg.sub! /Removed/, "remove"
|
|
msg.sub! /Added/, "add"
|
|
msg.sub! /Enumerated/, "enumerate"
|
|
msg.sub! /Queried/, "query"
|
|
match = ""
|
|
msg = "Failed to #{msg}"
|
|
msg += ": No entry found" if response.grep(/not a reserved client/).size > 0
|
|
msg += ": #{match}" if (match = response.grep(/used by another client/)).size > 0
|
|
raise Proxy::DHCP::Error.new(msg)
|
|
else
|
|
logger.info msg unless error_only
|
|
end
|
|
rescue Proxy::DHCP::Error
|
|
raise
|
|
rescue
|
|
logger.error "Netsh failed:\n" + (response.is_a?(Array) ? response.join("\n") : "Response was not an array! #{response}")
|
|
raise Proxy::DHCP::Error.new("Unknown error while processing '#{msg}'")
|
|
end
|
|
|
|
def parse_options response
|
|
optionId = nil
|
|
options = {}
|
|
response.each do |line|
|
|
line.chomp!
|
|
break if line.match(/^Command completed/)
|
|
|
|
case line
|
|
when /OptionId : (\d+)/
|
|
optionId = $1
|
|
when /Option Element Value = (\S+)/
|
|
options[optionId] = $1
|
|
end
|
|
end
|
|
return options
|
|
end
|
|
end
|
|
end
|