bin/process-zone
changeset 535 bddc9a060a73
parent 527 3a2221124592
child 546 91c073d5615a
equal deleted inserted replaced
534:879917cebb9c 535:bddc9a060a73
     8 
     8 
     9 import optparse
     9 import optparse
    10 import codecs
    10 import codecs
    11 import logging
    11 import logging
    12 
    12 
    13 log = logging.getLogger()
    13 log = logging.getLogger('main')
    14 
    14 
    15 # command-line options, global state
    15 # command-line options, global state
    16 options = None
    16 options = None
    17 
    17 
    18 def parse_options (argv) :
    18 def parse_options (argv) :
    19     """
    19     """
    20         Parse command-line arguments.
    20         Parse command-line arguments.
    21     """
    21     """
    22 
    22 
       
    23     prog = argv[0]
       
    24 
    23     parser = optparse.OptionParser(
    25     parser = optparse.OptionParser(
    24             prog        = argv[0],
    26             prog        = prog,
    25             usage       = '%prog: [options]',
    27             usage       = '%prog: [options]',
    26             version     = __version__,
    28             version     = __version__,
    27 
    29 
    28             # module docstring
    30             # module docstring
    29             description = __doc__,
    31             description = __doc__,
    36     general.add_option('-v', '--verbose',   dest='loglevel', action='store_const', const=logging.INFO,  help="More output")
    38     general.add_option('-v', '--verbose',   dest='loglevel', action='store_const', const=logging.INFO,  help="More output")
    37     general.add_option('-D', '--debug',     dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output")
    39     general.add_option('-D', '--debug',     dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output")
    38 
    40 
    39     parser.add_option_group(general)
    41     parser.add_option_group(general)
    40 
    42 
       
    43     # input/output
    41     parser.add_option('-c', '--input-charset',  metavar='CHARSET',  default='utf-8', 
    44     parser.add_option('-c', '--input-charset',  metavar='CHARSET',  default='utf-8', 
    42             help="Encoding used for input files")
    45             help="Encoding used for input files")
    43 
    46 
    44     parser.add_option('-o', '--output',         metavar='FILE',     default='-',
    47     parser.add_option('-o', '--output',         metavar='FILE',     default='-',
    45             help="Write to output file; default stdout")
    48             help="Write to output file; default stdout")
    46 
    49 
    47     parser.add_option('--output-charset',       metavar='CHARSET',  default='utf-8', 
    50     parser.add_option('--output-charset',       metavar='CHARSET',  default='utf-8', 
    48             help="Encoding used for output files")
    51             help="Encoding used for output files")
    49 
    52 
       
    53     # check stage
       
    54     parser.add_option('--check-hosts',          action='store_true',
       
    55             help="Check that host/IPs are unique. Use --quiet to silence warnings, and test exit status")
       
    56 
       
    57     parser.add_option('--check-exempt',         action='append',
       
    58             help="Allow given names to have multiple records")
       
    59 
       
    60     # forward stage
    50     parser.add_option('--forward-zone',         action='store_true', 
    61     parser.add_option('--forward-zone',         action='store_true', 
    51             help="Generate forward zone")
    62             help="Generate forward zone")
    52 
    63 
    53     parser.add_option('--forward-txt',          action='store_true',
    64     parser.add_option('--forward-txt',          action='store_true',
    54             help="Generate TXT records for forward zone")
    65             help="Generate TXT records for forward zone")
    55 
    66 
    56     parser.add_option('--forward-mx',           metavar='MX',
    67     parser.add_option('--forward-mx',           metavar='MX',
    57             help="Generate MX records for forward zone")
    68             help="Generate MX records for forward zone")
    58 
    69 
       
    70     # reverse stage
    59     parser.add_option('--reverse-domain',       metavar='DOMAIN',
    71     parser.add_option('--reverse-domain',       metavar='DOMAIN',
    60             help="Domain to use for hosts in reverse zone")
    72             help="Domain to use for hosts in reverse zone")
    61 
    73 
    62     parser.add_option('--reverse-zone',         metavar='NET',
    74     parser.add_option('--reverse-zone',         metavar='NET',
    63             help="Generate forward zone for given subnet (x.z.y)")
    75             help="Generate forward zone for given subnet (x.z.y)")
    64 
    76 
    65     # defaults
    77     # defaults
    66     parser.set_defaults(
    78     parser.set_defaults(
    67         loglevel            = logging.WARN,
    79         loglevel            = logging.WARN,
       
    80 
       
    81         check_exempt        = [],
    68     )
    82     )
    69     
    83     
    70     # parse
    84     # parse
    71     options, args = parser.parse_args(argv[1:])
    85     options, args = parser.parse_args(argv[1:])
    72 
    86 
    73     # configure
    87     # configure
    74     logging.basicConfig(
    88     logging.basicConfig(
    75         format  = '%(processName)s: %(name)s: %(levelname)s %(funcName)s : %(message)s',
    89         format  = prog + ': %(name)s: %(levelname)s %(funcName)s : %(message)s',
    76         level   = options.loglevel,
    90         level   = options.loglevel,
    77     )
    91     )
    78 
    92 
    79     return options, args
    93     return options, args
    80 
    94 
   164     for lineno, line in enumerate(file) :
   178     for lineno, line in enumerate(file) :
   165         data = parse_record(line)
   179         data = parse_record(line)
   166 
   180 
   167         if data :
   181         if data :
   168             yield data
   182             yield data
       
   183 
       
   184 def check_zone_hosts (zone, whitelist=None) :
       
   185     """
       
   186         Parse host/IP pairs from the zone, and verify that they are unique.
       
   187 
       
   188         As an exception, names listed in the given whitelist may have multiple IPs.
       
   189     """
       
   190 
       
   191     by_name = {}
       
   192     by_ip = {}
       
   193 
       
   194     fail = None
       
   195 
       
   196     for item in zone :
       
   197         text = ' '.join(pp for pp in item if pp)
       
   198         name, ttl, type, data, comment = item
       
   199 
       
   200         # name
       
   201         if name not in by_name :
       
   202             by_name[name] = text
       
   203 
       
   204         elif name in whitelist :
       
   205             log.debug("Duplicate whitelist entry: %r", item)
       
   206 
       
   207         else :
       
   208             # fail!
       
   209             log.warn("Duplicate name: %s <-> %s", text, by_name[name])
       
   210             fail = True
       
   211 
       
   212         # ip
       
   213         if type == 'A' :
       
   214             ip = data
       
   215 
       
   216             if ip not in by_ip :
       
   217                 by_ip[ip] = text
       
   218 
       
   219             else :
       
   220                 # fail!
       
   221                 log.warn("Duplicate IP: %s <-> %s", text, by_ip[ip])
       
   222                 fail = True
       
   223 
       
   224     return fail
   169 
   225 
   170 def process_zone_forwards (zone, txt=False, mx=False) :
   226 def process_zone_forwards (zone, txt=False, mx=False) :
   171     """
   227     """
   172         Process zone data -> forward zone data.
   228         Process zone data -> forward zone data.
   173     """
   229     """
   305     for file in input_files :
   361     for file in input_files :
   306         log.info("Reading zone: %s", file)
   362         log.info("Reading zone: %s", file)
   307 
   363 
   308         zone += list(parse_zone(file))
   364         zone += list(parse_zone(file))
   309 
   365 
       
   366     # check?
       
   367     if options.check_hosts :
       
   368         whitelist = set(options.check_exempt)
       
   369 
       
   370         log.debug("checking hosts; whitelist=%r", whitelist)
       
   371 
       
   372         if check_zone_hosts(zone, whitelist=whitelist) :
       
   373             log.warn("Hosts check failed")
       
   374             return 2
       
   375 
       
   376         else :
       
   377             log.info("Hosts check OK")
       
   378 
   310     # output file
   379     # output file
   311     output = open_file(options.output, 'w', options.output_charset)
   380     output = open_file(options.output, 'w', options.output_charset)
   312 
   381 
   313     if options.forward_zone :
   382     if options.forward_zone :
   314         log.info("Write forward zone: %s", output)
   383         log.info("Write forward zone: %s", output)
   323             log.error("--reverse-zone requires --reverse-domain")
   392             log.error("--reverse-zone requires --reverse-domain")
   324             return 1
   393             return 1
   325 
   394 
   326         zone = list(process_zone_reverse(zone, origin=origin, domain=domain))
   395         zone = list(process_zone_reverse(zone, origin=origin, domain=domain))
   327 
   396 
       
   397     elif options.check_hosts :
       
   398         # we only did that, done
       
   399         return 0
       
   400 
   328     else :
   401     else :
   329         log.warn("Nothing to do")
   402         log.warn("Nothing to do")
       
   403         return 1
   330 
   404 
   331     write_zone(output, zone)
   405     write_zone(output, zone)
   332 
   406 
   333     return 0
   407     return 0
   334 
   408