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 28import os 29import os.path 30import sys 31 32if 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 * 39else: 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 49IGNORED_OPTIONS = [ 'cleaning-interval', 50 'datasize', 51 'interface-interval', 52 'max-cache-size', 53 'stacksize', 54 ] 55 56class 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