update: list_files to copy all DHCP_CONFS..
#!/usr/bin/env python
"""
Go through a dhcp conf file looking for fixed-address stanzas, and make sure that they are valid.
"""
__version__ = '0.0.1-dev'
import optparse
import codecs
import logging
import socket
log = logging.getLogger('main')
# command-line options, global state
options = None
def parse_options (argv) :
"""
Parse command-line arguments.
"""
prog = argv[0]
parser = optparse.OptionParser(
prog = prog,
usage = '%prog: [options]',
version = __version__,
# module docstring
description = __doc__,
)
# logging
general = optparse.OptionGroup(parser, "General Options")
general.add_option('-q', '--quiet', dest='loglevel', action='store_const', const=logging.ERROR, help="Less output")
general.add_option('-v', '--verbose', dest='loglevel', action='store_const', const=logging.INFO, help="More output")
general.add_option('-D', '--debug', dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output")
parser.add_option_group(general)
# input/output
parser.add_option('-c', '--input-charset', metavar='CHARSET', default='utf-8',
help="Encoding used for input files")
#
parser.add_option('--doctest', action='store_true',
help="Run module doctests")
# defaults
parser.set_defaults(
loglevel = logging.WARN,
)
# parse
options, args = parser.parse_args(argv[1:])
# configure
logging.basicConfig(
format = prog + ': %(name)s: %(levelname)s %(funcName)s : %(message)s',
level = options.loglevel,
)
return options, args
def parse_fixedaddrs (file) :
"""
Go through lines in given .conf file, looking for fixed-address stanzas.
"""
filename = file.name
for lineno, line in enumerate(file) :
# comments?
if '#' in line :
line, comment = line.split('#', 1)
else :
comment = None
# whitespace
line = line.strip()
if not line :
# empty
continue
# grep
if 'fixed-address' in line :
# great parsing :)
fixedaddr = line.replace('fixed-address', '').replace(';', '').strip()
log.debug("%s:%d: %s: %s", filename, lineno, fixedaddr, line)
yield lineno, fixedaddr
def resolve_addr (addr, af=socket.AF_INET, socktype=socket.SOCK_STREAM) :
"""
Resolve given address for given AF_INET, returning a list of resolved addresses.
Raises an Exception if failed.
>>> resolve_addr('127.0.0.1')
['127.0.0.1']
"""
if not addr :
raise Exception("Empty addr: %r", addr)
# resolve
result = socket.getaddrinfo(addr, None, af, socktype)
#log.debug("%s: %s", addr, result)
# addresses
addrs = list(sorted(set(sockaddr[0] for family, socktype, proto, canonname, sockaddr in result)))
return addrs
def check_file_hosts (file) :
"""
Check all fixed-address parameters in given file.
"""
filename = file.name
fail = 0
for lineno, addr in parse_fixedaddrs(file) :
# lookup
try :
resolved = resolve_addr(addr)
except Exception as e:
log.warning("%s:%d: failed to resolve: %s: %s", filename, lineno, addr, e)
fail += 1
else :
log.debug("%s:%d: %s: %r", filename, lineno, addr, resolved)
return fail
def open_file (path, mode, charset) :
"""
Open unicode-enabled file from path, with - using stdio.
"""
if path == '-' :
# use stdin/out based on mode
stream, func = {
'r': (sys.stdin, codecs.getreader),
'w': (sys.stdout, codecs.getwriter),
}[mode[0]]
# wrap
return func(charset)(stream)
else :
# open
return codecs.open(path, mode, charset)
def main (argv) :
global options
options, args = parse_options(argv)
if options.doctest :
import doctest
fail, total = doctest.testmod()
return fail
if args :
# open files
input_files = [open_file(path, 'r', options.input_charset) for path in args]
else :
# default to stdout
input_files = [open_file('-', 'r', options.input_charset)]
# process zone data
for file in input_files :
log.info("Reading zone: %s", file)
fail = check_file_hosts(file)
if fail :
log.warn("DHCP hosts check failed: %d", fail)
return 2
else :
log.info("DHCP hosts check OK")
return 0
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv))