bin/check-dhcp-hosts
branchdns-new
changeset 604 9a23fca9167a
parent 603 b58236f9ea7b
child 605 26a307558602
equal deleted inserted replaced
603:b58236f9ea7b 604:9a23fca9167a
     1 #!/usr/bin/env python
       
     2 
       
     3 """
       
     4     Go through a dhcp conf file looking for fixed-address stanzas, and make sure that they are valid.
       
     5 """
       
     6 
       
     7 __version__ = '0.0.1-dev'
       
     8 
       
     9 import optparse
       
    10 import codecs
       
    11 import logging
       
    12 
       
    13 import socket
       
    14 
       
    15 log = logging.getLogger('main')
       
    16 
       
    17 # command-line options, global state
       
    18 options = None
       
    19 
       
    20 def parse_options (argv) :
       
    21     """
       
    22         Parse command-line arguments.
       
    23     """
       
    24 
       
    25     prog = argv[0]
       
    26 
       
    27     parser = optparse.OptionParser(
       
    28             prog        = prog,
       
    29             usage       = '%prog: [options]',
       
    30             version     = __version__,
       
    31 
       
    32             # module docstring
       
    33             description = __doc__,
       
    34     )
       
    35 
       
    36     # logging
       
    37     general = optparse.OptionGroup(parser, "General Options")
       
    38 
       
    39     general.add_option('-q', '--quiet',     dest='loglevel', action='store_const', const=logging.ERROR, help="Less output")
       
    40     general.add_option('-v', '--verbose',   dest='loglevel', action='store_const', const=logging.INFO,  help="More output")
       
    41     general.add_option('-D', '--debug',     dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output")
       
    42 
       
    43     parser.add_option_group(general)
       
    44 
       
    45     # input/output
       
    46     parser.add_option('-c', '--input-charset',  metavar='CHARSET',  default='utf-8', 
       
    47             help="Encoding used for input files")
       
    48 
       
    49     # 
       
    50     parser.add_option('--doctest',              action='store_true',
       
    51             help="Run module doctests")
       
    52 
       
    53     # defaults
       
    54     parser.set_defaults(
       
    55         loglevel            = logging.WARN,
       
    56     )
       
    57     
       
    58     # parse
       
    59     options, args = parser.parse_args(argv[1:])
       
    60 
       
    61     # configure
       
    62     logging.basicConfig(
       
    63         format  = prog + ': %(name)s: %(levelname)s %(funcName)s : %(message)s',
       
    64         level   = options.loglevel,
       
    65     )
       
    66 
       
    67     return options, args
       
    68 
       
    69 def parse_fixedaddrs (file) :
       
    70     """
       
    71         Go through lines in given .conf file, looking for fixed-address stanzas.
       
    72     """
       
    73 
       
    74     filename = file.name
       
    75 
       
    76     for lineno, line in enumerate(file) :
       
    77         # comments?
       
    78         if '#' in line :
       
    79             line, comment = line.split('#', 1)
       
    80 
       
    81         else :
       
    82             comment = None
       
    83 
       
    84         # whitespace
       
    85         line = line.strip()
       
    86 
       
    87         if not line :
       
    88             # empty
       
    89             continue
       
    90        
       
    91         # grep
       
    92         if 'fixed-address' in line :
       
    93             # great parsing :)
       
    94             fixedaddr = line.replace('fixed-address', '').replace(';', '').strip()
       
    95 
       
    96             log.debug("%s:%d: %s: %s", filename, lineno, fixedaddr, line)
       
    97         
       
    98             yield lineno, fixedaddr
       
    99 
       
   100 def resolve_addr (addr, af=socket.AF_INET, socktype=socket.SOCK_STREAM) :
       
   101     """
       
   102         Resolve given address for given AF_INET, returning a list of resolved addresses.
       
   103 
       
   104         Raises an Exception if failed.
       
   105 
       
   106         >>> resolve_addr('127.0.0.1')
       
   107         ['127.0.0.1']
       
   108     """
       
   109 
       
   110     if not addr :
       
   111         raise Exception("Empty addr: %r", addr)
       
   112 
       
   113     # resolve
       
   114     result = socket.getaddrinfo(addr, None, af, socktype)
       
   115    
       
   116     #log.debug("%s: %s", addr, result)
       
   117 
       
   118     # addresses
       
   119     addrs = list(sorted(set(sockaddr[0] for family, socktype, proto, canonname, sockaddr in result)))
       
   120 
       
   121     return addrs
       
   122 
       
   123 def check_file_hosts (file) :
       
   124     """
       
   125         Check all fixed-address parameters in given file.
       
   126     """
       
   127 
       
   128     filename = file.name
       
   129     fail = 0
       
   130 
       
   131     for lineno, addr in parse_fixedaddrs(file) :
       
   132         # lookup
       
   133         try :
       
   134             resolved = resolve_addr(addr)
       
   135 
       
   136         except Exception as e:
       
   137             log.warning("%s:%d: failed to resolve: %s: %s", filename, lineno, addr, e)
       
   138             fail += 1
       
   139 
       
   140         else :
       
   141             log.debug("%s:%d: %s: %r", filename, lineno, addr, resolved)
       
   142 
       
   143     return fail
       
   144 
       
   145 def open_file (path, mode, charset) :
       
   146     """
       
   147         Open unicode-enabled file from path, with - using stdio.
       
   148     """
       
   149 
       
   150     if path == '-' :
       
   151         # use stdin/out based on mode
       
   152         stream, func = {
       
   153             'r':    (sys.stdin, codecs.getreader),
       
   154             'w':    (sys.stdout, codecs.getwriter),
       
   155         }[mode[0]]
       
   156 
       
   157         # wrap
       
   158         return func(charset)(stream)
       
   159 
       
   160     else :
       
   161         # open
       
   162         return codecs.open(path, mode, charset)
       
   163 
       
   164 def main (argv) :
       
   165     global options
       
   166     
       
   167     options, args = parse_options(argv)
       
   168 
       
   169     if options.doctest :
       
   170         import doctest
       
   171         fail, total = doctest.testmod()
       
   172         return fail
       
   173 
       
   174     if args :
       
   175         # open files
       
   176         input_files = [open_file(path, 'r', options.input_charset) for path in args]
       
   177 
       
   178     else :
       
   179         # default to stdout
       
   180         input_files = [open_file('-', 'r', options.input_charset)]
       
   181    
       
   182     # process zone data
       
   183     for file in input_files :
       
   184         log.info("Reading zone: %s", file)
       
   185     
       
   186         fail = check_file_hosts(file)
       
   187 
       
   188         if fail :
       
   189             log.warn("DHCP hosts check failed: %d", fail)
       
   190             return 2
       
   191 
       
   192         else :
       
   193             log.info("DHCP hosts check OK")
       
   194 
       
   195     return 0
       
   196 
       
   197 if __name__ == '__main__':
       
   198     import sys
       
   199 
       
   200     sys.exit(main(sys.argv))
       
   201