#!/usr/bin/python
#
# (C)opyright Xelerance 2007 - 2009, Paul Wouters <paul@xelerance.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

import commands
import re
import os
import sys
import getopt
import tempfile,shutil
from stat import *

global namedconf
namedconf = "unknown"
namedconfs = ( "/etc/bind/named.conf.options", "/etc/bind/named.conf", "/etc/named.conf", "/usr/local/etc/named.conf", "/etc/named/named.conf","/var/named/named.conf" )
for entry in namedconfs:
	if os.path.isfile(entry):
		namedconf = entry
		break

global unboundconf 
unboundconf = "unknown"
unboundconfs = ( "/etc/unbound/unbound.conf", "/usr/local/etc/unbound/unbound.conf", "/usr/local/etc/unbound.conf", "/etc/unbound.conf" )
for entry in unboundconfs:
	if os.path.isfile(entry):
		unboundconf = entry
		break

global basedir
if os.path.isfile("/etc/debian_version"):
	basedir="/etc/dnssec-conf/"
else:
	basedir="/etc/pki/dnssec-keys/"

global keyfile
keyfile = "%s/named.dnssec.keys"%basedir

global bversion
bversion = "unknown"
global production
production = 0
global testing
testing = 0
global harvest
harvest = 0
global restart
restart = 1
global nocheck
nocheck = 0

def usage():
	global basedir
	print "dnssec-configure: (re)configure the Bind and/or Unbound for DNSSEC and DLV"
	print "usage: dnssec-configure [-u] [-b] [--dnssec=<on|off>] --dlv=<off|on[dlvzone]>"
	print "                        [--production] [--testing] [--harvest] [--root]"
	print "                        [--norestart] [--nocheck]"
	print ""
	print "   -s                show current configuration(s)"
	print "   -b or -n          (re)configure the Bind nameserver (default yes if found)"
	print "   -u                (re)configure the Unbound nameserver (default yes if found)"
	print "   --norestart       Do not attempt to restart the DNS resolver daemon"
	print "   --nocheck         Do not check the resolver's config file before modifying"
	print ""
	print "   --dnssec= <on|of>  enable or disable DNSSEC"
	print "   --dlv= <off|on|dlvname> enable or disable DLV"
	print "          (default dlv as specified with 'on' is dlv.isc.org)" 
	print "   --basedir=         basedir for key files, default is %s"%basedir
	print "   --production      load production keys into configuration (default yes)"
	print "   --testing         load testing keys into configuration (default no)"
	print "   --harvest         load harvest keys into configuration (default no)"
	print "   --root            load root key into configuration [not yet implemented]"
	print ""
	print "Addition unbound options:"
	print "   --set=section:key:value  Set the option in the section to the desired value"
	print "   --query=section:key      Query the option in the section"
	print ""
	print "examples:"
	print "   dnssec-configure --dnssec=on --dlv=dlv.isc.org --production"
	print "   dnssec-configure -u --dnssec=on --dlv=on --production --testing --harvest"
	print "   dnssec-configure --norestart --dnssec=off --dlv=off"
	print "   dnssec-configure -u --set=section:option:value [--set=section:option:value]"
	print "   dnssec-configure -u --query=section:option [--query=section:option]"
	print "   dnssec-configure -s [-u] [-b]"
	print "   dnssec-configure -u --set=server:use-caps-for-id:yes"
	print "   dnssec-configure -u --query=server:use-caps-for-id --query=server:harden-glue"

def main(argv=None):
	global production
	global testing
	global harvest
	global restart
	global nocheck
	global namedconf
	global unboundconf
	global basedir
#	if commands.getoutput( "whoami" ) != "root":
#		sys.exit("root permission is required  to write config files"

	if argv is None:
		argv = sys.argv
	try:
		opts, args = getopt.getopt(argv[1:], "sbnuvh1:2:3:456789xy", ["show","bind","named", "unbound", "version","help","dnssec=","dlv=","basedir=","production","testing","harvest","root","set=", "query=", "norestart","nocheck"])
	except getopt.error, msg:
		#print >>sys.stderr, err.msg
		print >>sys.stderr, "ERROR parsing options"
		usage()
		sys.exit(2)

	# parse options
	dlvzone = "dlv.isc.org"
	# Using conf files for 'grep' will cause failure when using
	# includes, so we need to go through those as well
	bind = 0
	unbound = 0
	dnssec = -1
	dlv = -1
	production = 0
	testing = 0
	harvest = 0
	root = 0
	show = 0
	restartBind = 0
	restartUnbound = 0
	sets = []
	queries = []
	if not opts:
		usage()
		sys.exit()
		
	for o, a in opts:
		if o in ("-v", "--version"):
			print "dnssec-configure version 1.21"
			print "Author:\n Paul Wouters <paul@xelerance.com>"
			print "Source : http://www.xelerance.com/software/dnssec-conf/"
			sys.exit()
		if o in ("-h", "--help"):
			usage()
			sys.exit()

		if o in ("-n","--named","-b","--bind"):
			bind = 1
		if o in ("-u","--unbound"):
			unbound = 1
		if o in ("-x","--norestart"):
			restart = 0
		if o in ("-y","--nocheck"):
			nocheck = 1
		if o in ("-s","--show"):
			show = 1
		if o in ("-3","--basedir"):
			if not a:
				print "error: basedir requires argument"
				usage()
				sys.exit()
			else:
				basedir = a
		if o in ("--production"):
			production = 1
		if o in ("--testing"):
			testing = 1
		if o in ("--harvest"):
			harvest = 1
		if o in ("--root"):
			root = 1

		if o in ("--set","-8"):
			if not a:
				print "error: set requires an argument"
				usage()
				sys.exit()
			else:
				sets.insert(0,a)

		if o in ("--query","-8"):
			if not a:
				print "error: query requires an argument"
				usage()
				sys.exit()
			else:
				queries.insert(0,a)
		if o in ("-1","--dnssec"):
			if not a:
				print "error: no dnssec mode specified"
				usage()
				sys.exit()
			else:
				if a == "on":
					dnssec = 1
				elif a == "off":
					dnssec = 0
				else:
					print "error: dnssec can only be 'on' or 'off'"
					usage()
					sys.exit()
		if o in ("-2","--dlv"):
			if not a:
				print "error: no dlv mode specified"
				usage()
				sys.exit()
			else:
				if a == "on":
					dlv = 1
				elif a == "off":
					dlv = 0
				else:
					dlv = 1
					dlvzone = a

	if sets and queries:
			sys.exit("error: cannot --set and --query at the same time. Please use either --set or --query")

	if sets or queries:
		if( (not bind and not unbound) or (bind and unbound)):
			sys.exit("error: cannot --set or --query  an option unless one of -u and -b (and not both) are specified")

	if not bind and not unbound:
		# nothing specified, default is both
		bind = 1
		unbound = 1

	# find the proper config files
	if bind and namedconf == "unknown":
		sys.exit("error: bind configuration not found")
	if unbound and unboundconf == "unknown":
		sys.exit("error: unbound configuration not found")

	# is show, just show current config and quit
	if show:
		if bind:
			showCurrentConfig("bind")
		if unbound:
			showCurrentConfig("unbound")
		sys.exit()

	if queries:
		if bind:
			#not yet implemented - no interesting options really
			return
		if unbound:
			UnboundQueryOptions(queries)
		sys.exit()

	# check write perms
	if bind:
		try:
			fp = open(namedconf,"a")
			fp.close()
		except:
			sys.exit("error: cannot write %s"%namedconf)
	if unbound:
		try:
			fp = open(unboundconf,"a")
			fp.close()
		except:
			sys.exit("error: cannot write %s"%unboundconf)

	if sets:
		if bind:
			#not yet implemented - no interesting options really
			return
		if unbound:
			UnboundSetOptions(sets)
		sys.exit()

	if not dnssec:
		# then also disable dlv
		dlv = 0
	else:
		if not production and not testing and not harvest:
			production = 1

	if dlv == -1:
		print "error: dlv setting not specified"
		usage()
		sys.exit()

	if dnssec == -1:
		print "error: dnssec setting not specified"
		usage()
		sys.exit()


	if args:
		print "error:unknown arguments specified"
		usage()
		sys.exit()

	software = []

	if bind:
		software.append("bind")
	if unbound:
		software.append("unbound")
	software = ",".join(software)

	# change the options we need to change with --set so that we don't have to re-restart
	# the daemons later.
	if(sets):
		if unbound:
			UnboundSetOptions(sets)
		elif bind:
			sys.exit("error: bind daemon not supported for --set")
			#BindSetOptions(sets)
		else:
			sys.exit("error: unknown daemon for --set")

	if dnssec:
		if not dlv:
			dlvzone = ""
		else:
			# fix dots, eg ".arpa" and "arpa" to "arpa."
			if dlvzone != ".":
				if dlvzone[-1] != ".":
					dlvzone = dlvzone + "."
				if dlvzone[0] == ".":
					dlvzone = dlvzone[1:]

		#print "Enabling DNSSEC for: %s"%software
		if bind:
			restartBind = BindEnableDNSSEC(dlvzone)
		if unbound:
			restartUnbound = UnboundEnableDNSSEC(dlvzone)
	else:
		#print "Disabling DNSSEC for: %s"%software
		if bind:
			restartBind = BindDisableDNSSEC()
		if unbound:
			restartUnbound = UnboundDisableDNSSEC()

	# we modified the config files, verify and restart (not reload)
	if bind:
		if not nocheck:
			checkBindConfig()
		if restartBind and restart == 1:
			if os.path.isfile("/etc/debian_version"):
				bindname = "bind9"
			else:
				bindname = "named"
			restartDaemon(bindname)
	if unbound:
		if not nocheck:
			checkUnboundConfig()
		if restartUnbound and restart == 1:
			restartDaemon("unbound")



def restartDaemon(daemon):
	if os.path.isfile("/etc/debian_version"):
		cmd = "/usr/sbin/invoke-rc.d %s force-reload"%daemon
	else:
		cmd = "/sbin/service %s try-restart"%daemon
	(status, output) = commands.getstatusoutput(cmd)
        if status:
                print "ERROR:\n%s"%output

def checkUnboundConfig():
	global unboundconf
	checkConfig(unboundconf,"unbound-checkconf")

def checkBindConfig():
	global namedconf
	checkConfig(namedconf,"named-checkconf")

# Check if bind version is 9.3.3 - 9.6.x
def checkBindVersion():
	global bversion
	(status,bversion) = commands.getstatusoutput("named -v");
	if status != 0 or bversion == "":
		sys.exit("ERROR: unknown problem with named: %s "%bversion)
	try:
		if bversion[0:5] <> "BIND ":
			sys.exit("ERROR: unknown Bind version '%s'- aborted:"%bversion)
		if (bversion[5] in [ "4","8" ]):
			sys.exit("ERROR: ANCIENT Bind version '%s' not supported. We only support 9.3.3 and higher"%bversion)
		if (bversion[5] == "9") and (bversion[7] in [ "4","5","6" ]):
			bversion = "newstyle"
			return
		elif (bversion[5] == "9") and (bversion[7] =="3"):
			if  bversion[9] in ["1","2"]:
				sys.exit("ERROR: Bind version '%s' not supported. We only support 9.3.3 and higher"%bversion)
			bversion = "oldstyle"
			return
		sys.exit("ERROR: unknown Bind version '%s'- aborted"%bversion)
	except:
		sys.exit("ERROR: unknown Bind version syntax '%s'- aborted:"%bversion)

# check will abort everything if it fails
def checkConfig(conf, checkprog):
	if not os.path.isfile(conf):
		sys.exit("ERROR: %s not found for rewrite"%conf)
	# *-checkconf better be in our path
	(status, output) = commands.getstatusoutput("%s %s"%(checkprog,conf))
	if status != 0:
		sys.exit("ERROR: syntax check for %s %s failed:%s"%(checkprog, conf,output))

# overwrite config file with tmpfile securely, if neccessary, and clean up
# returns 1 to restart daemon, 0 if that is not neccessary
def UpdateConfigFile(cfgfile,tmpfile):
	if not commands.getstatusoutput("cmp %s %sk"%(cfgfile,tmpfile))[0]:
		# config file did not change, we can't sys.exit since we might be doing 2 config files
		os.unlink(tmpfile)
		return 0
	try:
		os.unlink(cfgfile +".bak")
	except:
		pass
	shutil.copy2(cfgfile, cfgfile +".bak")
	shutil.copy2(tmpfile, cfgfile)
	# copy original owner/group to new file
	owner = os.stat(cfgfile)[ST_UID]
	group = os.stat(cfgfile)[ST_GID]
	os.chown(cfgfile,owner,group)
	# copy original permissions to new file
	shutil.copymode(cfgfile +".bak",cfgfile)
	os.unlink(tmpfile)
	return 1

# DNSSEC rewrite functions
def BindEnableDNSSEC(dlvzone):
	checkBindConfig()
	checkBindVersion()
	return BindSetDNSSEC("yes",dlvzone)

def BindDisableDNSSEC():
	checkBindConfig()
	checkBindVersion()
	return BindSetDNSSEC("no","")

def BindSetDNSSEC(yesno,dlvzone):
	global namedconf
	global bversion
	global production
	global testing
	global harvest
	global basedir
	global keyfile
	fp = open(namedconf)
	cnf = fp.readlines()
	fp.close()

	# find options sections. Assumptions for now: options { start on newline
	# and first }; on a line by itself is the options closing tag
	startopt = 0
	endopt = 0
	for index, item in enumerate(cnf):
        	if re.match("^\s*options\s*{",item):
			startopt = index
			break
	if not startopt:
		# TODO: write to stderr
		print "dnssec-configure: Bind: options section not found"
		return 0

	for index,item in enumerate(cnf[startopt:]):
		if re.match("^\s*}\s*;\s*\n*$",item):
			endopt = index
			break
	if not endopt:
		print cnf[14:]
		# TODO: write to stderr
		print "dnssec-configure: Bind: options section not found"
		return 0

	optionscfg = cnf[startopt:startopt+endopt+1] 

	enablefound = 0 
	validationfound = 0
	dlvfound = 0
	prodfound = 0
	testfound = 0
	harvestfound = 0
	
	for index,item in enumerate(optionscfg):
		# change entry if there
		if "dnssec-enable" in item:
			enablefound = 1
			optionscfg[index] = "\tdnssec-enable %s;\n"%yesno
		if "dnssec-validation" in item:
			validationfound = 1
			if bversion == "newstyle":
				optionscfg[index] = "\tdnssec-validation %s;\n"%yesno
			else:
				optionscfg[index] = "\t// only in bind 9.3.3 and newer: dnssec-validation %s;\n"%yesno
		if "dnssec-lookaside" in item:
			dlvfound = 1
			if not dlvzone:
				vals = item.split("dnssec-lookaside")[1]
				optionscfg[index] = "\t// dnssec-lookaside%s"%vals
			else:
				optionscfg[index] = "\tdnssec-lookaside . trust-anchor " + dlvzone + ";\n"

	# check for removing the dlv include
	for index, item in enumerate(cnf):
		if 'include "%s/dlv/'%basedir in item:
			if dlvzone == "off" or dlvzone == "":
				cnf.pop(index)

	# add entry for unfound settings
	if not enablefound:
		optionscfg = optionscfg[:-1] + ["\tdnssec-enable %s;\n"%yesno] + [ "};\n" ]
	if not validationfound:
		if bversion == "newstyle":
			optionscfg = optionscfg[:-1] + ["\tdnssec-validation %s;\n"%yesno]  + [ "};\n" ]
		else:
			optionscfg = optionscfg[:-1] + ["\t// only in bind 9.3.3 and newer: dnssec-validation %s;\n"%yesno]  + [ "};\n" ]
	if dlvzone:
		if not dlvfound:
			optionscfg = optionscfg[:-1] + ["\tdnssec-lookaside . trust-anchor %s;\n"%dlvzone]  + [ "};\n" ]
		if not os.path.isfile("%s/dlv/%sconf"%(basedir,dlvzone)):
			print "warning: %s/dlv/%sconf not found. If you trust your network use:"%(basedir,dlvzone)
			print "         dnskey-pull -t -o %basedir/dlv/%sconf %s' to obtain it"%(basedir,dlvzone,dlvzone)
	if not 'include "%s";'%keyfile in "".join(cnf):
		cnf = cnf + ['include "%s";\n'%keyfile]
	if dlvzone:
		if not 'include "%s/dlv/%sconf";'%(basedir,dlvzone) in "".join(cnf):
			cnf = cnf + ['include "%s/dlv/%sconf";\n'%(basedir,dlvzone)]


	(fptmp,fname) = tempfile.mkstemp(".conf")
	fpnew = os.fdopen(fptmp, 'w+b')
	fpnew.write("".join(cnf[0:startopt]))
	fpnew.write("".join(optionscfg))
	fpnew.write("".join(cnf[(startopt+endopt+1):]))
	fpnew.close()
	# before activating new config, rebuild the keyfile file as named also does not support wildcards for includes
	fpkeys = open(keyfile,"w")
	todo = []
	if production:
		todo += [ "production"]
		todo += [ "production/reverse"]
	if testing:
		todo += [ "testing"]
	if harvest:
		todo += [ "harvest"]
	if todo:
		for entry in todo:
			filelist = os.listdir("%s/%s"%(basedir,entry))
			filelist.sort()
			for line in filelist:
				if line[-5:] == ".conf":
					fpkeys.write('include "%s/%s/%s";\n'%(basedir,entry,line))
	else:
		fpkeys.write("// dnssec-configure: no keys selected, this should not happen\n")
	fpkeys.close()
	return UpdateConfigFile(namedconf,fname)

def UnboundEnableDNSSEC(dlvzone):
	global nocheck
	if not nocheck:
		checkUnboundConfig()
	return UnboundSetDNSSEC("yes",dlvzone)

def UnboundDisableDNSSEC():
	global nocheck
	if not nocheck:
		checkUnboundConfig()
	return UnboundSetDNSSEC("no","")

# easiest is to remember is start of server: section
#val-permissive-mode: yes
def UnboundSetDNSSEC(yesno,dlvzone):
	global unboundconf
	global production
	global testing
	global harvest
	try:
		fp = open(unboundconf)
		cnf = fp.readlines()
		fp.close()
	except:
		sys.exit("error: failed to open %s"%unboundconf)
	# flip the yesno arround because we say 'yes' to disable dnssec
	if yesno == "yes":
		yesno = "no"
	else:
		yesno = "yes"

	# first try if we find the option, if not add to start of server: section
	serversect = 0
	permissive = 0
	commentpermissive = 0
	dlvanchorfile = 0
	prodfound = 0
	revfound = 0
	testfound = 0
	harvestfound = 0
	for index, item in enumerate(cnf):
        	if re.match("^\s*server:\s*",item):
			serversect = index
        	if re.match("^\s*val-permissive-mode\s*:",item):
			permissive = index
        	if re.match("^\s*#\s*val-permissive-mode\s*:",item):
			commentpermissive = index
		if re.match("^\s*#*\s*dlv-anchor-file\s*:",item):
			if not dlvzone:
				# comment out if not a comment
				if not re.match("^\s*#.*",item):
					cnf[index] = "\t#"
			else:
				dlvanchorfile = index
		if re.match("^\s*trusted-keys-file:", item):
			if "%s/production/*.conf"%basedir in item:
				prodfound = index
				if not production:
					cnf[index] =  '\t# trusted-keys-file: "%s/production/*.conf"\n'%basedir
			elif "%s/production/reverse/*.conf"%basedir in item:
				revfound = index
				if not production:
					cnf[index] =  '\t# trusted-keys-file: "%s/reverse/*.conf"\n'%basedir
			elif "%s/testing/*.conf"%basedir in item:
				testfound = index
				if not testing:
					cnf[index] =  '\t# trusted-keys-file: "%s/testing/*.conf"\n'%basedir
			elif "%s/harvest/*.conf"%basedir in item:
				harvestfound = index
				if not harvest:
					cnf[index] =  '\t# trusted-keys-file: "/harvest/*.conf"\n'%basedir

	if permissive:
		(head,tail) = cnf[permissive].split("val-permissive-mode")
		cnf[permissive] = "%sval-permissive-mode: %s\n"%(head,yesno)
	elif commentpermissive:
		(head,tail) = cnf[commentpermissive].split("#",1)
		cnf[commentpermissive] = "%sval-permissive-mode: %s\n"%(re.sub("#","",head),yesno)
	else:
		cnf.insert(serversect+1, "\tval-permissive-mode: %s"%yesno )
	if dlvzone:
		if dlvanchorfile:
			cnf[dlvanchorfile] = '\tdlv-anchor-file: "%s/dlv/%skey"\n'%(basedir,dlvzone)
		else:
			cnf.insert(serversect+1, "\tdlv-anchor-file: %s/dlv/%skey\n"%(basedir,dlvzone))

		if not os.path.isfile("%s/dlv/%skey"%(basedir,dlvzone)):
			print "warning: %s/dlv/%skey not found. If you trust your network run 'dnskey-pull' to obtain it"%(basedir,dlvzone)
	else:
		if dlvanchorfile:
			cnf[dlvanchorfile] = '\t# dlv-anchor-file: "%s/dlv/%skey"\n'%(basedir,dlvzone)

	# add potential missing lines to include trusted-keys-file 
	if not prodfound and production:
		cnf.insert(serversect+1, '\ttrusted-keys-file: "%s/production/*.conf"\n'%basedir)
	if not revfound and production:
		cnf.insert(serversect+1, '\ttrusted-keys-file: "%s/production/reverse/*.conf"\n'%basedir)
	if not testfound and testing:
		cnf.insert(serversect+1, '\ttrusted-keys-file: "%s/testing/*.conf"\n'%basedir)
	if not harvestfound and harvest:
		cnf.insert(serversect+1, '\ttrusted-keys-file: "%s/harvest/*.conf"\n'%basedir)
	
	(fptmp,fname) = tempfile.mkstemp(".conf")
	fpnew = os.fdopen(fptmp, 'w+b')
	try:
		fpnew.write("".join(cnf))
	except:
		print "BROKE"
		print cnf
	fpnew.close()
	return UpdateConfigFile(unboundconf,fname)

# show current dnssec and dlv status. returns enabled/disabled for DNSSEC and the the dlv zone or disabled for DLV.
def showCurrentConfig(nstype):
	dnssecStatus = "unknown"
	# if dlv keyword not  present in config, it means disabled
	dlvStatus    = "disabled"


	if nstype == "unbound":
		# check if valid config first, then we can assume certain things
		checkUnboundConfig()
		try:
			fp = open(unboundconf)
			cnf = fp.readlines()
			fp.close()
		except:
			sys.exit("error: failed to find %s"%unboundconf)
		for line in cnf:
			if re.match("^\s*val-permissive-mode\s*:",line):
				if "yes" in line.split(":")[1]:
					dnssecStatus = "disabled"
				else:
					dnssecStatus = "enabled"
			if re.match("^\s*dlv-anchor-file\s*:",line):
					dlvfile = re.sub('"','', line.split(":")[1].strip())
					try:
						dlvfp = open(dlvfile)
						lines = dlvfp.readlines()
						dlvfp.close()
					except:
						dlvStatus = "unknown"
					for entry in lines:
						if entry.strip()[0] == ';':
							continue
						# first non comment line is our dlv line. first word is zone name
						else:
							try:
								dlvStatus = entry.strip().split(" ",1)[0]
							except:
								dlvStatus = "unknown"
		print "Unbound DNSSEC:%s"%dnssecStatus
		print "Unbound DLV:%s"%dlvStatus

	elif nstype =="bind":
		# check if valid config first, then we can assume certain things
		checkBindConfig()
		fp = open(namedconf)
		cnf = fp.readlines()
		fp.close()
		for line in cnf:
			if re.match("^\s*dnssec-validation\s*",line):
				if "yes" in line.split("dnssec-validation")[1]:
					dnssecStatus = "enabled"
				else:
					dnssecStatus = "disabled"
			if re.match("^\s*dnssec-lookaside\s*",line):
					try:
						dlvStatus = re.sub(";","", line.split("trust-anchor")[1].strip())
					except:
						dlvStatus = "unknown"
		print "Bind DNSSEC:%s"%dnssecStatus
		print "Bind DLV:%s"%dlvStatus

	else:
		usage()
		sys.exit()

# queries contains a set of strings "section:option"
def UnboundQueryOptions(queries):
	global unboundconf

	# remove section bit, we don't need/use it for unbound. makes lookups easier
	for index,entry in enumerate(queries):
		try:
			queries[index] = entry.split(":",1)[1]
		except:
			# remove bogus query from set
			queries.pop(index)
	try:
		fp = open(unboundconf)
		cnf = fp.readlines()
		fp.close()
	except:
		sys.exit("error: failed to open %s"%unboundconf)
	section = "unknown"
	for line in cnf:
		line = line.strip()
		if not line:
			continue
		if line[0] == "#":
			continue
		try:
			(opt,val) = line.split(":")
			if not val:
				section = opt
		except:
			continue
		opt = opt.strip()
		for query in queries:
			if query == opt:
				print "%s:%s:%s"%(section,opt,val.strip())

# sets contains a set of strings "section:option:value"
# assumption is only one of these may occur!! 
def UnboundSetOptions(sets):
	global unboundconf

	try:
		fp = open(unboundconf)
		cnf = fp.readlines()
		fp.close()
	except:
		sys.exit("error: failed to open %s"%unboundconf)

	curSection = ""

	# read the sets, strip out the section, and turn it into a dictionary for easy lookup
	setdict = {}
	for setting in sets:
		print "processing %s"%setting
		try:
			(sect,opt,val) = setting.split(":")
		except:
			sys.exit("--set requires value in format section:option:value")
		setdict[opt] = val

	# iterate over lines in config file, remember current section, then pick from supplied sets
	# to rewrite. Delete from set what we changed. if we see new section, insert lines from
	# set we didnt match yet before continuing.
	sectionsFound = {}
	for index, item in enumerate(cnf):
		item = item.strip()
		# Is this a comment line? For now, we ignore all comments and empty lines
		if not item:
			continue
		if item[0] == "#":
			continue
		# check if this is a section seperator
		if re.match("^\s*[^:]*:\s*$",item):
			sectline = index
			sectname = item.split(":")[0].strip()
			sectionsFound[sectname] = sectline
			continue
		if item.strip()[0] != "#":
			# we should now have a line that has option:value
			try:
				(optname,optval) = item.strip().split(":",1)
				if setdict.has_key(optname):
					cnf[index] = "\t%s: %s\n"%(item.split(":")[0],setdict[optname])
					del setdict[optname]
			except:
				pass
	# now setdict only has new settings left that were not present. 
	if setdict:
		for optname in setdict.keys():
			cnf.insert(1+sectionsFound["server"],"\t%s: %s\n"%(optname,setdict[optname]))
			del setdict[optname]
	if setdict:
		sys.exit("error: UnboundSetOptions: odd we have options left to do?")
			
	(fptmp,fname) = tempfile.mkstemp(".conf")
	fpnew = os.fdopen(fptmp, 'w+b')
	try:
		fpnew.write("".join(cnf))
	except:
		print "BROKE"
		print cnf
	fpnew.close()
	return UpdateConfigFile(unboundconf,fname)

if __name__ == "__main__":
	sys.exit(main())
