Home | History | Annotate | Line # | Download | only in bind2nsd
      1 #!/usr/bin/env python
      2 # Copyright (c) 2007, Secure64 Software Corporation
      3 # 
      4 # Permission is hereby granted, free of charge, to any person obtaining a copy
      5 # of this software and associated documentation files (the "Software"), to deal
      6 # in the Software without restriction, including without limitation the rights
      7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      8 # copies of the Software, and to permit persons to whom the Software is
      9 # furnished to do so, subject to the following conditions:
     10 # 
     11 # The above copyright notice and this permission notice shall be included in
     12 # all copies or substantial portions of the Software.
     13 # 
     14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     20 # THE SOFTWARE.
     21 # 
     22 #
     23 #
     24 #	class containing the parser for BIND configuration files
     25 #
     26 #
     27 
     28 import os
     29 import os.path
     30 import sys
     31 
     32 if os.path.exists('../bind2nsd/Config.py'):
     33    sys.path.append('../bind2nsd')
     34    from NamedConf import *
     35    from Zone import *
     36    from Key import *
     37    from Tokenizer import *
     38    from Utils import *
     39 else:
     40    from bind2nsd.NamedConf import *
     41    from bind2nsd.Zone import *
     42    from bind2nsd.Key import *
     43    from bind2nsd.Tokenizer import *
     44    from bind2nsd.Utils import *
     45 
     46 
     47 #-- the following options {} clauses are currently currently ignored,
     48 #   typically because they are not implemented or not applicable to NSD
     49 IGNORED_OPTIONS = [ 'cleaning-interval',
     50                     'datasize',
     51                     'interface-interval',
     52 		    'max-cache-size',
     53                     'stacksize',
     54                   ]
     55 
     56 class Parser:
     57 
     58    def __init__(self, named):
     59       self.named = named
     60       return
     61 
     62    def handle_include(self, tokens):
     63       (ttype, val, curline) = tokens.get()
     64       if ttype == T_STRING:
     65          fname = val.replace('"', '')
     66          self.named.includes.append(fname)
     67          report_info('   include file "%s"...' % (fname))
     68          if os.path.exists(fname):
     69             self.parse(Tokenizer(fname))
     70          else:
     71             report_error('? missing include file "%s"' % (fname))
     72       else:
     73          bail('? dude.  where is the filename string after "include"?')
     74       (ttype, val, curline) = tokens.get()
     75       if ttype != T_SEMI:
     76          bail('? need a semicolon to end the "include" (%d: %s)' % (ttype, val))
     77       return
     78    
     79    def handle_key(self, tokens):
     80       (ttype, val, curline) = tokens.get()
     81       if ttype == T_STRING:
     82          name = val.replace('"', '')
     83          key = Key(name)
     84          (ttype, val, curline) = tokens.get()
     85          if ttype != T_LBRACE:
     86             bail('? need opening brace for "key" defn (%d: %s)' % (ttype, val))
     87          (ttype, val, curline) = tokens.get()
     88          while ttype != T_RBRACE:
     89             if ttype == T_KEYWORD and val == 'algorithm':
     90                (ttype, val, curline) = tokens.get()
     91  	       if ttype == T_KEYWORD or ttype == T_NAME:
     92 	          key.setAlgorithm(val)
     93 	       else:
     94 	          bail('? bogosity after "algorithm" (%d: %s)' % (ttype, val))
     95 	    elif ttype == T_KEYWORD and val == 'secret':
     96 	       (ttype, val, curline) = tokens.get()
     97 	       if ttype == T_STRING:
     98 	          s = val.replace('"','')
     99 		  key.setSecret(s)
    100 	       else:
    101 	          bail('? bogosity after "secret" (%d: %s)' % (ttype, val))
    102 	    elif ttype == T_SEMI:
    103 	       pass
    104 	    (ttype, val, curline) = tokens.get()
    105       else:
    106          bail('? need to have a string after "key" (%d: %s)' % (ttype, val))
    107 
    108       self.named.addKey(key)
    109       (ttype, val, curline) = tokens.get()
    110       if ttype != T_SEMI:
    111          bail('? need a semicolon to end the "key" (%d: %s)' % (ttype, val))
    112       return
    113    
    114    def handle_server(self, tokens):
    115       (ttype, val, curline) = tokens.get()
    116       if ttype == T_QUAD:
    117          ipaddr = val
    118          (ttype, val, curline) = tokens.get()
    119          if ttype != T_LBRACE:
    120             bail('? need opening brace for "server" (%d: %s)' % (ttype, val))
    121          (ttype, val, curline) = tokens.get()
    122          while ttype != T_RBRACE:
    123             if ttype == T_KEYWORD and val == 'keys':
    124                (ttype, val, curline) = tokens.get()
    125  	       if ttype == T_LBRACE:
    126 	          nlist = self.name_list(val, tokens)
    127 		  for ii in nlist:
    128 		     self.named.keys[ii].addIpAddr(ipaddr)
    129 	       else:
    130 	          bail('? bogosity after "keys" (%d: %s)' % (ttype, val))
    131 	    (ttype, val, curline) = tokens.get()
    132       else:
    133          bail('? need to have an IP address after "server" (%d: %s)' % \
    134 	      (ttype, val))
    135 
    136       (ttype, val, curline) = tokens.get()
    137       if ttype != T_SEMI:
    138          bail('? need a semicolon to end the "server" (%d: %s)' % (ttype, val))
    139       return
    140    
    141    def name_list(self, name, tokens):
    142       nlist = []
    143       (ttype, val, curline) = tokens.get()
    144       if ttype == T_LBRACE or ttype == T_LPAREN:
    145          (ttype, val, curline) = tokens.get()
    146          while ttype != T_RBRACE and ttype != T_RPAREN:
    147             if ttype == T_NAME:
    148 	       nlist.append(val)
    149             elif ttype == T_SEMI or ttype == T_COMMENT:
    150                pass
    151             else:
    152                bail('? was expecting a name in "%s" (%d, %d: %s)' % \
    153 	            (name, ttype, curline, val.strip()))
    154             (ttype, val, curline) = tokens.get()
    155          (ttype, val, curline) = tokens.get()
    156          if ttype != T_SEMI:
    157             bail('? junk after closing brace of "%s" section' % (name))
    158 
    159       return nlist
    160 
    161    def quad_list(self, name, tokens):
    162       qlist = []
    163       (ttype, val, curline) = tokens.get()
    164       if ttype == T_LBRACE or ttype == T_LPAREN:
    165          (ttype, val, curline) = tokens.get()
    166          while ttype != T_RBRACE and ttype != T_RPAREN:
    167             if ttype == T_QUAD:
    168 	       qlist.append(val)
    169             elif ttype == T_SEMI or ttype == T_COMMENT:
    170                pass
    171             else:
    172                bail('? was expecting a quad in "%s" (%d, %d: %s)' % \
    173 	            (name, ttype, curline, val.strip()))
    174             (ttype, val, curline) = tokens.get()
    175          (ttype, val, curline) = tokens.get()
    176          if ttype != T_SEMI:
    177             bail('? junk after closing brace of "%s" section' % (name))
    178 
    179       return qlist
    180 
    181    def quad_list_with_keys(self, name, tokens, named):
    182       qlist = []
    183       (ttype, val, curline) = tokens.get()
    184       if ttype == T_LBRACE or ttype == T_LPAREN:
    185          (ttype, val, curline) = tokens.get()
    186          while ttype != T_RBRACE and ttype != T_RPAREN:
    187             if ttype == T_QUAD:
    188 	       qlist.append(val)
    189             elif ttype == T_SEMI or ttype == T_COMMENT:
    190                pass
    191 	    elif ttype == T_KEYWORD and val == 'key':
    192                (ttype, val, curline) = tokens.get()
    193 	       if ttype == T_NAME:
    194 	          if val in named.getKeys():
    195 		     qlist.append(val)
    196 	       else:
    197                   bail('? was expecting a key name in "%s" (%d, %d: %s)' % \
    198 	               (name, ttype, curline, val.strip()))
    199             else:
    200                bail('? was expecting a quad in "%s" (%d, %d: %s)' % \
    201 	            (name, ttype, curline, val.strip()))
    202             (ttype, val, curline) = tokens.get()
    203          (ttype, val, curline) = tokens.get()
    204          if ttype != T_SEMI:
    205             bail('? junk after closing brace of "%s" section' % (name))
    206 
    207       return qlist
    208 
    209    def skip_value(self, name, tokens):
    210       (ttype, val, curline) = tokens.get()
    211       if ttype == T_NAME or ttype == T_STRING:
    212          pass
    213       else:
    214          bail('? need something after "%s" keyword (%d: %s)' % 
    215 	      (name, ttype, val))
    216       return
    217 
    218    def handle_options(self, tokens):
    219       report_info('=> processing server options')
    220       optsDone = False
    221       depth = 0
    222       (ttype, val, curline) = tokens.get()
    223       if ttype == T_LBRACE:
    224          depth += 1
    225       else:
    226          bail('? urk.  bad info after "options" keyword.')
    227       (ttype, val, curline) = tokens.get()
    228 
    229       while not optsDone:
    230          if ttype == T_KEYWORD and val == 'directory':
    231             (ttype, val, curline) = tokens.get()
    232             if ttype == T_STRING:
    233                self.named.options.setDirectory(val)
    234 	    else:
    235                bail('? need string after "directory" keyword')
    236 
    237 	 elif ttype == T_KEYWORD and val == 'dump-file':
    238 	    (ttype, val, curline) = tokens.get()
    239 	    if ttype == T_STRING:
    240 	       self.named.options.setDumpFile(val)
    241 	    else:
    242 	       bail('? need string after "dump-file" keyword')
    243 
    244 	 elif ttype == T_KEYWORD and val == 'pid-file':
    245 	    (ttype, val, curline) = tokens.get()
    246 	    if ttype == T_STRING:
    247 	       self.named.options.setPidFile(val)
    248 	    else:
    249 	       bail('? need string after "pid-file" keyword')
    250 
    251 	 elif ttype == T_KEYWORD and val == 'coresize':
    252 	    (ttype, val, curline) = tokens.get()
    253 	    if ttype == T_NAME or ttype == T_KEYWORD:
    254 	       self.named.options.setCoresize(val)
    255 	    else:
    256 	       bail('? need name after "coresize" keyword (%d: %s)' % \
    257 	            (ttype, val))
    258 
    259 	 elif ttype == T_KEYWORD and val == 'version':
    260 	    (ttype, val, curline) = tokens.get()
    261 	    if ttype == T_STRING:
    262 	       self.named.options.setVersion(val)
    263 	    else:
    264 	       bail('? need string after "version" keyword')
    265 
    266 	 elif ttype == T_KEYWORD and val == 'transfer-format':
    267 	    (ttype, val, curline) = tokens.get()
    268 	    if ttype == T_NAME or ttype == T_KEYWORD:
    269 	       self.named.options.setTransferFormat(val)
    270 	    else:
    271 	       bail('? need name after "transfer-format" keyword (%d: %s)' % \
    272 	            (ttype, val))
    273 
    274          elif ttype == T_KEYWORD and val == 'statistics-file':
    275             (ttype, val, curline) = tokens.get()
    276             if ttype == T_STRING:
    277                self.named.options.setStatisticsFile(val)
    278             else:
    279   	       bail('? need string after "statistics-file" keyword')
    280 
    281 	 elif ttype == T_KEYWORD and val == 'allow-transfer':
    282 	    qlist = self.quad_list_with_keys(val, tokens, self.named)
    283 	    self.named.options.setAllowTransfer(qlist)
    284 
    285 	 elif ttype == T_KEYWORD and val == 'allow-notify':
    286 	    qlist = self.quad_list(val, tokens)
    287 	    self.named.options.setAllowNotify(qlist)
    288 
    289 	 elif ttype == T_KEYWORD and val == 'also-notify':
    290 	    qlist = self.quad_list(val, tokens)
    291 	    self.named.options.setAlsoNotify(qlist)
    292 
    293 	 elif ttype == T_KEYWORD and val == 'allow-recursion':
    294 	    qlist = self.quad_list(val, tokens)
    295 	    self.named.options.setAllowRecursion(qlist)
    296 
    297          elif ttype == T_KEYWORD and val == 'check-names':
    298             (ttype, val, curline) = tokens.get()
    299             info = []
    300             while ttype != T_SEMI:
    301   	       info.append(val)
    302                (ttype, val, curline) = tokens.get()
    303             self.named.options.addCheckNames(' '.join(info))
    304    
    305          elif ttype == T_KEYWORD and val in IGNORED_OPTIONS:
    306 	    self.skip_value(val, tokens)
    307 
    308 	 elif ttype == T_KEYWORD and val == 'recursive-clients':
    309 	    (ttype, val, curline) = tokens.get()
    310 	    if ttype == T_QUAD:		# well, it's a number, actually...
    311 	       self.named.options.setRecursiveClients(val)
    312 	    else:
    313 	       bail('? need value after "recursive-clients" keyword (%d: %s)'
    314 	            % (ttype, val))
    315 
    316          elif ttype == T_RBRACE:
    317             optDone = True
    318             break
    319          (ttype, val, curline) = tokens.get()
    320 
    321       (ttype, val, curline) = tokens.get()
    322       if ttype != T_SEMI:
    323          bail('? um, junk after closing brace of "options" section')
    324    
    325       return
    326 
    327    def handle_zone(self, tokens):
    328       (ttype, val, curline) = tokens.get()
    329       if ttype != T_STRING:
    330          bail('? expected string for zone name (%d: %s)' % (ttype, val))
    331       zname = val.replace('"','')
    332       zone = Zone(zname)
    333    
    334       (ttype, val, curline) = tokens.get()
    335       if ttype == T_KEYWORD and (val == 'in' or val == 'IN'):
    336          (ttype, val, curline) = tokens.get()
    337       if ttype == T_LBRACE or ttype == T_LPAREN:
    338          (ttype, val, curline) = tokens.get()
    339          while ttype != T_RBRACE and ttype != T_RPAREN:
    340             if ttype == T_KEYWORD and val == 'type':
    341                (ttype, val, curline) = tokens.get()
    342   	       info = []
    343   	       while ttype == T_KEYWORD or ttype == T_NAME:
    344   	          info.append(val)
    345                   (ttype, val, curline) = tokens.get()
    346   	       zone.setType(' '.join(info))
    347 
    348             elif ttype == T_KEYWORD and val == 'file':
    349                (ttype, val, curline) = tokens.get()
    350 	       if ttype != T_STRING:
    351 	          bail('? eh?  no string for zone file name? (%d: %s)' % \
    352 		       (ttype, val))
    353 	       fname = val.replace('"','')
    354 	       zone.setFile(fname)
    355 
    356 	    elif ttype == T_KEYWORD and val == 'masters':
    357 	       qlist = self.quad_list(val, tokens)
    358 	       zone.setMasters(qlist)
    359 
    360 	    elif ttype == T_KEYWORD and val == 'allow-transfer':
    361 	       qlist = self.quad_list_with_keys(val, tokens, self.named)
    362 	       zone.setAllowTransfers(qlist)
    363 
    364 	    elif ttype == T_KEYWORD and val == 'also-notify':
    365 	       qlist = self.quad_list(val, tokens)
    366 	       zone.setAlsoNotify(qlist)
    367 
    368 	    elif ttype == T_SEMI:
    369 	       pass
    370 
    371 	    else:
    372 	       bail('? bugger.  do NOT grok "%s" yet (%d: %s @ line %s)' % \
    373 	            (val, ttype, val, curline))
    374 
    375 	    (ttype, val, curline) = tokens.get()
    376 
    377 	 self.named.addZone(zone)
    378 
    379       else:
    380          bail('? expected left brace to start zone definition (%d: %s)' % \
    381 	      (ttype, val))
    382 
    383       (ttype, val, curline) = tokens.get()
    384       if ttype != T_SEMI:
    385          bail('? need a semicolon to end the "zone" (%d: %s)' % (ttype, val))
    386 
    387       return
    388    
    389    def ignore_section(self, name, tokens):
    390       report_info('=> ignoring %s' % (name))
    391       done = False
    392       depth = 0
    393       (ttype, val, curline) = tokens.get()
    394       if ttype == T_LBRACE:
    395          depth += 1
    396       else:
    397          bail('? urk.  bad info after "%s" keyword.' % (name))
    398       (ttype, val, curline) = tokens.get()
    399       while not done:
    400          if ttype == T_LBRACE:
    401             depth += 1
    402          elif ttype == T_RBRACE:
    403             depth -= 1
    404             if depth == 0:
    405                break
    406             else:
    407                pass
    408          (ttype, val, curline) = tokens.get()
    409       (ttype, val, curline) = tokens.get()
    410       if ttype != T_SEMI:
    411          bail('? um, junk after closing brace of "%s" section' % (name))
    412       return
    413 
    414    def parse(self, tokens):
    415       (ttype, val, curline) = tokens.get()
    416       while ttype != T_EOF:
    417          if ttype == T_COMMENT:
    418             pass
    419 
    420          elif ttype == T_KEYWORD and val == 'controls':
    421 	    self.ignore_section(val, tokens)
    422    
    423          elif ttype == T_KEYWORD and val == 'key':
    424 	    self.handle_key(tokens)
    425 
    426          elif ttype == T_KEYWORD and val == 'include':
    427 	    self.handle_include(tokens)
    428 
    429          elif ttype == T_KEYWORD and val == 'logging':
    430 	    self.ignore_section(val, tokens)
    431    
    432          elif ttype == T_KEYWORD and val == 'lwres':
    433 	    self.ignore_section(val, tokens)
    434    
    435          elif ttype == T_KEYWORD and val == 'options':
    436 	    self.handle_options(tokens)
    437 
    438          elif ttype == T_KEYWORD and val == 'server':
    439 	    self.handle_server(tokens)
    440 
    441          elif ttype == T_KEYWORD and val == 'trusted-keys':
    442 	    self.ignore_section(val, tokens)
    443 
    444          elif ttype == T_KEYWORD and val == 'view':
    445 	    self.ignore_section(val, tokens)
    446 
    447          elif ttype == T_KEYWORD and val == 'zone':
    448 	    self.handle_zone(tokens)
    449 
    450          else:
    451             bail('? ew.  what _is_ this? -> %2d: %s @ line %d' \
    452 	         % (ttype, val, curline))
    453          (ttype, val, curline) = tokens.get()
    454    
    455       return
    456 
    457