Commit 02cdb61a authored by Ad Schellevis's avatar Ad Schellevis

refacor unboundctlwrapper to python, closes https://github.com/opnsense/core/issues/1505

parent 1a16bfba
#!/usr/local/bin/ruby #!/usr/local/bin/python2.7
=begin
Copyright (C) 2017 Fabian Franz """
* Copyright (c) 2017 Ad Schellevis
All rights reserved. Copyright (C) 2017 Fabian Franz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer. 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the 2. Redistributions in binary form must reproduce the above copyright
documentation and/or other materials provided with the distribution. notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
POSSIBILITY OF SUCH DAMAGE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
=end POSSIBILITY OF SUCH DAMAGE.
"""
import os
require 'json' import sys
require 'optparse' import re
import tempfile
supported_formats = %w{json} import subprocess
options = {format: 'json'} import argparse
import json
OptionParser.new do |opts|
opts.banner = "Usage: #{__FILE__} command" def unbound_control_reader(action):
opts.on("-c", "--cache", "Dump cache") do |c| with tempfile.NamedTemporaryFile() as output_stream:
options[:dump_cache] = c subprocess.call(['/usr/sbin/unbound-control', '-c', '/var/unbound/remotecontrol.conf', action],
end stdout=output_stream, stderr=open(os.devnull, 'wb'))
opts.on("-i", "--infra", "Dump infrastructure cache") do |c| output_stream.seek(0)
options[:dump_infra] = c for line in output_stream:
end yield line
opts.on("-f", "--format FORMAT") do |format|
if supported_formats.include? format # parse arguments
options[:format] = format parser = argparse.ArgumentParser()
else parser.add_argument('-c', '--cache', help='Dump cache', action="store_true", default=False)
puts "the specified format is not valid" parser.add_argument('-i', '--infra', help='Dump infrastructure cache', action="store_true", default=False)
exit parser.add_argument('-s', '--stats', help='Dump stats', action="store_true", default=False)
end parser.add_argument('-l', '--list-local-zones', help='List local Zones', action="store_true", default=False)
end parser.add_argument('-I', '--list-insecure', help='List Domain-Insecure Zones', action="store_true", default=False)
opts.on("-s", "--stats") do |stats| parser.add_argument('-d', '--list-local-data', help='List local data', action="store_true", default=False)
options[:stats] = stats parser.add_argument('-f', '--format', help='output format', action='store', choices=['json'], default='json')
end args = parser.parse_args()
opts.on("-l", "--list-local-zones", "List local Zones") do |llz|
options[:llz] = llz #
end output = None
opts.on("-I", "--list-insecure", "List Domain-Insecure Zones") do |i| if args.cache:
options[:insecure] = i output = list()
end for line in unbound_control_reader('dump_cache'):
opts.on("-d", "--list-local-data", "List local data") do |i| parts = re.split('^(\S+)\s+(?:([\d]*)\s+)?(IN)\s+(\S+)\s+(.*)$', line)
options[:lld] = i if line.find('IN') > -1 and not line.startswith('msg') and len(parts) > 5:
end output.append({'host': parts[1], 'ttl': parts[2], 'type': parts[3], 'rrtype': parts[4], 'value': parts[5]})
opts.on("-h", "--help", "Prints this help") do elif args.infra:
puts opts output = list()
exit for line in unbound_control_reader('dump_infra'):
end parts = line.split()
end.parse! if len(parts) > 2:
record = {'ip': parts.pop(0), 'host': parts.pop(0)}
while len(parts) > 0:
def dump_cache key = parts.pop(0)
raw = `unbound-control -c /var/unbound/remotecontrol.conf dump_cache`.split("\n") if key == 'lame':
raw = raw.select {|x| (x.include? "IN") && (x[0] != ";") && !(x.start_with? "msg ") } record['lame'] = True
raw.map do |line| continue
host,ttl, type, rrtype, value = line.scan(/^(\S+)\s+(?:([\d]*)\s+)?(IN)\s+(\S+)\s+(.*)$/).first record[key] = parts.pop(0)
{host: host, ttl: ttl, type: type, rrtype: rrtype, value: value} output.append(record)
end elif args.stats:
end output = dict()
for line in unbound_control_reader('stats'):
def stats full_key, value = line.split('=')
raw = `unbound-control -c /var/unbound/remotecontrol.conf stats`.split("\n") keys = full_key.split('.')
data = {} if keys[0] == 'histogram':
raw.each do |line| if 'histogram' not in output:
key,value = line.split("=") output['histogram'] = list()
key_parts = key.split(".") output['histogram'].append({
if key_parts[0] == 'histogram' 'from': (int(keys[1]), int(keys[2])),
data['histogram'] = [] unless data['histogram'] 'to': (int(keys[4]), int(keys[5])),
data['histogram'] << {from: key_parts[1..2].map(&:to_i), 'value': value.strip()
to: key_parts[4..5].map(&:to_i), })
value: value.to_i} else:
else ptr = output
key = key_parts.pop while len(keys) > 0 :
origin = data key = keys.pop(0)
key_parts.each do |kp| if len(keys) == 0:
unless origin[kp] ptr[key] = value.strip()
origin[kp] = {} elif key not in ptr:
end ptr[key] = dict()
origin = origin[kp] ptr = ptr[key]
end elif args.list_local_zones:
origin[key] = value.to_i output = list()
end for line in unbound_control_reader('list_local_zones'):
end parts = line.split()
data if len(parts) >= 2:
end output.append({'zone': parts[0], 'type': parts[1]})
elif args.list_insecure:
def dump_infra output = list()
raw = `unbound-control -c /var/unbound/remotecontrol.conf dump_infra`.split("\n") for line in unbound_control_reader('list_insecure'):
raw.map do |line| output.append(line)
elements = line.split(/\s+/) elif args.list_local_data:
data = {} output = list()
data['ip'] = elements.shift for line in unbound_control_reader('list_local_data'):
data['host'] = elements.shift parts = line.split()
while elements.count > 2 if len(parts) >= 5:
key = elements.shift output.append({'name': parts[0], 'ttl': parts[1], 'type': parts[2], 'rrtype': parts[3], 'value': parts[4]})
if key == 'lame' else:
data['lame'] = true parser.print_help()
next sys.exit(1)
end
tmp = elements.shift # flush output
data[key] = tmp =~ /^\d+$/ ? tmp.to_i : tmp if args.format == 'json':
end print (json.dumps(output))
data
end
end
def insecure
`unbound-control -c /var/unbound/remotecontrol.conf list_insecure`.split("\n")
end
def list_local_zones
raw = `unbound-control -c /var/unbound/remotecontrol.conf list_local_zones`.split("\n")
raw.map do |line|
z, t = line.split(/\s+/)
{zone: z, type: t}
end
end
def list_local_data
raw = `unbound-control -c /var/unbound/remotecontrol.conf list_local_data`.split("\n")
result = []
raw.map do |line|
line.strip!
next if line.length < 10 # not a valid entry
name, ttl, type, rrtype, value = line.split(/\s+/,5)
{name: name, ttl: ttl, type: type, rrtype: rrtype, value: value}
end.select {|x| x }
end
output = nil
if options[:dump_cache]
output = dump_cache
end
if options[:dump_infra]
output = dump_infra
end
if options[:stats]
output = stats
end
if options[:insecure]
output = insecure
end
if options[:llz]
output = list_local_zones
end
if options[:lld]
output = list_local_data
end
puts case options[:format]
when 'json'; then
output.to_json
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment