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 to represent a nsd.conf file 25# 26# 27 28import os.path 29import sys 30 31if os.path.exists('../bind2nsd/Config.py'): 32 sys.path.append('../bind2nsd') 33 from Utils import * 34else: 35 from bind2nsd.Utils import * 36 37 38class NsdKey: 39 40 def __init__(self, name): 41 self.name = name 42 self.algorithm = '' 43 self.secret = '' 44 return 45 46 def dump(self, ofile): 47 if ofile == sys.stdout: 48 print >> ofile, '=> NsdKey:' 49 print >> ofile,' name = %s' % (self.name) 50 print >> ofile,' algorithm = %s' % (self.algorithm) 51 print >> ofile,' secret = %s' % (self.secret) 52 else: 53 print >> ofile, 'key:' 54 print >> ofile,' name: "%s"' % (self.name) 55 print >> ofile,' algorithm: %s' % (self.algorithm) 56 print >> ofile,' secret: "%s"' % (self.secret) 57 return 58 59 def setName(self, val): 60 self.name = val 61 return 62 63 def getName(self): 64 return self.name 65 66 def setAlgorithm(self, val): 67 self.algorithm = val 68 return 69 70 def getAlgorithm(self): 71 return self.algorithm 72 73 def setSecret(self, val): 74 self.secret = val 75 return 76 77 def getSecret(self): 78 return self.secret 79 80class NsdZone: 81 82 def __init__(self, name, config, ipkeymap): 83 self.name = name 84 self.config = config 85 self.include = '' 86 self.zonefile = '' 87 self.type = '' 88 self.allow_notify = [] # empty unless we're a slave zone 89 self.also_notify = [] # empty unless we're a master zone 90 self.provide_xfr = [] 91 self.oldrootdir = '' 92 self.ipkeymap = ipkeymap 93 return 94 95 def dump(self, ofile): 96 if ofile == sys.stdout: 97 print >> ofile, '=> NsdZone:' 98 print >> ofile, ' name = %s' % (self.name) 99 print >> ofile, ' include = %s' % (self.include) 100 print >> ofile, ' zonefile = %s' % (self.zonefile) 101 print >> ofile, ' type = %s' % (self.type) 102 print >> ofile, ' allow_notify = %s' % (str(self.allow_notify)) 103 print >> ofile, ' also_notify = %s' % (str(self.also_notify)) 104 print >> ofile, ' provide_xfr = %s' % (str(self.provide_xfr)) 105 else: 106 print >> ofile, '' 107 print >> ofile, 'zone:' 108 print >> ofile, ' name: "%s"' % (self.name) 109 print >> ofile, ' zonefile: "%s"' % (self.zonefile) 110 111 # BOZO: this is a hack to avoid errors in the zone compiler 112 # and needs to be handled more intelligently; this allows the 113 # zone file to be non-existent (done by some ISPs) but still 114 # listed in the named.conf 115 if not os.path.exists(self.oldrootdir + self.zonefile): 116 report_error('? missing zone file "%s"' % \ 117 (self.oldrootdir + self.zonefile)) 118 119 nlist = self.allow_notify 120 if self.type == 'slave': 121 print >> ofile, ' # this is a slave zone. masters are listed next.' 122 for ii in nlist: 123 print >> ofile, ' allow-notify: %s NOKEY' % (ii) 124 print >> ofile, ' request-xfr: %s NOKEY' % (ii) 125 else: 126 print >> ofile, ' include: "%s"' % (self.include) 127 for ii in self.also_notify: 128 print >> ofile, ' notify: %s NOKEY' % (ii) 129 130 for ii in self.provide_xfr: 131 if ii in self.ipkeymap: 132 print >> ofile, ' provide-xfr: %s %s' % (ii, self.ipkeymap[ii]) 133 print >> ofile, ' notify: %s %s' % (ii, self.ipkeymap[ii]) 134 else: 135 print >> ofile, ' provide-xfr: %s NOKEY' % (ii) 136 print >> ofile, ' notify: %s NOKEY' % (ii) 137 138 return 139 140 def setName(self, name): 141 self.name = name 142 return 143 144 def getName(self): 145 return self.name 146 147 def setOldRootDir(self, name): 148 self.oldrootdir = name 149 return 150 151 def getOldRootDir(self): 152 return self.oldrootdir 153 154 def setZonefile(self, file): 155 self.zonefile = file 156 return 157 158 def getZonefile(self): 159 return self.zonefile 160 161 def setType(self, type): 162 self.type = type 163 return 164 165 def getType(self): 166 return self.type 167 168 def addMaster(self, quad): 169 self.masters.append(quad) 170 return 171 172 def getMasters(self): 173 return self.masters 174 175 def setInclude(self, name): 176 self.include = name 177 return 178 179 def getInclude(self): 180 return self.include 181 182 def setAllowNotify(self, quad_list): 183 self.allow_notify = quad_list 184 return 185 186 def getAllowNotify(self): 187 return self.allow_notify 188 189 def addAllowNotify(self, nlist): 190 self.allow_notify.append(nlist) 191 return 192 193 def setAlsoNotify(self, quad_list): 194 self.also_notify = quad_list 195 return 196 197 def getAlsoNotify(self): 198 return self.also_notify 199 200 def addAlsoNotify(self, nlist): 201 self.also_notify.append(nlist) 202 return 203 204 def setProvideXfr(self, quad_list): 205 self.provide_xfr = quad_list 206 return 207 208 def getProvideXfr(self): 209 return self.provide_xfr 210 211class NsdOptions: 212 def __init__(self, config): 213 self.database = config.getValue('database') 214 self.difffile = config.getValue('difffile') 215 self.identity = config.getValue('identity') 216 self.ip_address = config.getValue('ip-address') 217 self.logfile = config.getValue('logfile') 218 self.pidfile = config.getValue('pidfile') 219 self.port = config.getValue('port') 220 self.statistics = config.getValue('statistics') 221 self.username = config.getValue('username') 222 self.xfrd_reload_timeout = config.getValue('xfrd-reload-timeout') 223 return 224 225 def dump(self, ofile): 226 if ofile == sys.stdout: 227 print >> ofile, '=> Options:' 228 print >> ofile, ' database: %s' % (self.database) 229 print >> ofile, ' difffile: %s' % (self.difffile) 230 print >> ofile, ' identity: %s' % (self.identity) 231 print >> ofile, ' ip-address: %s' % (self.ip_address) 232 print >> ofile, ' logfile: %s' % (self.logfile) 233 print >> ofile, ' pidfile: %s' % (self.pidfile) 234 print >> ofile, ' port: %s' % (self.port) 235 print >> ofile, ' statistics: %s' % (self.statistics) 236 print >> ofile, ' username: %s' % (self.username) 237 print >> ofile, ' xfrd-reload-timeout: %s' % (self.xfrd_reload_timeout) 238 else: 239 print >> ofile, '#' 240 print >> ofile, '# All of the values below have been pulled from' 241 print >> ofile, '# either defaults or the config file' 242 print >> ofile, '#' 243 print >> ofile, '' 244 print >> ofile, 'server:' 245 print >> ofile, ' database: %s' % (self.database) 246 print >> ofile, ' difffile: %s' % (self.difffile) 247 print >> ofile, ' identity: %s' % (self.identity) 248 iplist = self.ip_address.split() 249 for ii in iplist: 250 print >> ofile, ' ip-address: %s' % (ii.strip()) 251 print >> ofile, ' logfile: %s' % (self.logfile) 252 print >> ofile, ' pidfile: %s' % (self.pidfile) 253 print >> ofile, ' port: %s' % (self.port) 254 print >> ofile, ' statistics: %s' % (self.statistics) 255 print >> ofile, ' username: %s' % (self.username) 256 print >> ofile, ' xfrd-reload-timeout: %s' % \ 257 (self.xfrd_reload_timeout) 258 print >> ofile, '' 259 return 260 261 def setDatabase(self, name): 262 self.database = name 263 return 264 265 def getDatabase(self): 266 return self.database 267 268 def setDifffile(self, name): 269 self.difffile = name 270 return 271 272 def getDifffile(self): 273 return self.difffile 274 275 def setIdentity(self, name): 276 self.identity = name 277 return 278 279 def getIdentity(self): 280 return self.identity 281 282 def setIpAddress(self, quad): 283 self.ip_address = quad 284 return 285 286 def getIpAddress(self): 287 return self.ip_address 288 289 def setLogfile(self, val): 290 self.logfile = val 291 return 292 293 def getLogfile(self): 294 return self.logfile 295 296 def setPort(self, val): 297 self.port = val 298 return 299 300 def getPort(self): 301 return self.port 302 303 def setPidfile(self, val): 304 self.pidfile = val 305 return 306 307 def getPidfile(self): 308 return self.pidfile 309 310 def setStatistics(self, val): 311 self.statistics = val 312 return 313 314 def getStatistics(self): 315 return self.statistics 316 317 def setXfrdReloadTimeout(self, val): 318 self.xfrd_reload_timeout = val 319 return 320 321 def getXfrdReloadTimeout(self): 322 return self.xfrd_reload_timeout 323 324class NsdConf: 325 326 def __init__(self, config): 327 self.config = config 328 self.fname = config.getValue('nsd_conf') 329 self.files = config.getValue('nsd_files') 330 self.preamble = config.getValue('nsd_preamble') 331 self.acl_list = config.getValue('acl_list') 332 self.oldrootdir = '' 333 self.options = NsdOptions(config) 334 self.includes = [] 335 self.zones = {} 336 self.keys = {} 337 self.ipkeymap = {} 338 return 339 340 def populate(self, named): 341 self.oldrootdir = named.getOptions().getDirectory().replace('"','') 342 if self.oldrootdir[len(self.oldrootdir)-1] != '/': 343 self.oldrootdir += '/' 344 345 klist = named.getKeys() 346 for ii in klist: 347 oldk = named.getKey(ii) 348 k = NsdKey(oldk.getName()) 349 k.setAlgorithm(oldk.getAlgorithm()) 350 k.setSecret(oldk.getSecret()) 351 self.keys[k.getName()] = k 352 353 #-- map each of the key ip addresses for faster lookup on dump() 354 iplist = oldk.getIpAddrs() 355 for jj in iplist: 356 if jj not in self.ipkeymap: 357 self.ipkeymap[jj] = k.getName() 358 359 zlist = named.getZones() 360 for ii in zlist: 361 oldz = named.getZone(ii) 362 z = NsdZone(oldz.getName(), self.config, self.ipkeymap) 363 z.setZonefile(oldz.getFile()) 364 z.setInclude(self.acl_list) 365 z.setOldRootDir(self.oldrootdir) 366 if 'slave' in oldz.getType().split(): 367 z.setType('slave') # not used, but nice to know 368 nlist = oldz.getMasters() 369 for ii in nlist: 370 z.addAllowNotify(ii) 371 if len(oldz.getAllowNotify()) > 0: 372 nlist = oldz.getAllowNotify() 373 else: 374 nlist = named.getOptions().getAllowNotify() 375 for ii in nlist: 376 z.addAllowNotify(ii) 377 else: 378 z.setType('master') 379 if len(oldz.getAlsoNotify()): 380 nlist = oldz.getAlsoNotify() 381 else: 382 nlist = named.getOptions().getAlsoNotify() 383 for ii in nlist: 384 z.addAlsoNotify(ii) 385 if len(named.getOptions().getAllowTransfer()) > 0: 386 z.setProvideXfr(named.getOptions().getAllowTransfer()) 387 self.zones[z.getName()] = z 388 389 return 390 391 def dump(self): 392 report_info('=> NsdConf: dumping data from \"%s\"...' % (self.fname)) 393 self.options.dump(sys.stdout) 394 report_info('=> NsdConf: list of includes...') 395 report_info( str(self.includes)) 396 397 report_info('=> NsdConf: zone info...') 398 zlist = self.zones.keys() 399 zlist.sort() 400 for ii in zlist: 401 self.zones[ii].dump(sys.stdout) 402 403 report_info('=> NsdConf: key info...') 404 klist = self.keys.keys() 405 klist.sort() 406 for ii in klist(): 407 self.keys[ii].dump(sys.stdout) 408 409 return 410 411 def write_conf(self): 412 nfile = open(self.fname, 'w+') 413 self.options.dump(nfile) 414 415 klist = self.keys.keys() 416 klist.sort() 417 for ii in klist: 418 self.keys[ii].dump(nfile) 419 420 zlist = self.zones.keys() 421 zlist.sort() 422 for ii in zlist: 423 report_info(' writing info for zone "%s"...' % (self.zones[ii].getName())) 424 self.zones[ii].dump(nfile) 425 426 nfile.close() 427 return 428 429 def do_generate(self, newfd, start, stop, step, lhs, rrtype, rhs): 430 #-- write the equivalent of the $GENERATE 431 for ii in range(start, stop, step): 432 left = lhs.replace('$', str(ii)) 433 right = rhs.replace('$', str(ii)) 434 print >> newfd, '%s %s %s' % (left, rrtype, right) 435 return 436 437 def make_zone_copy(self, oldpath, newpath): 438 #-- copy the zone file line-by-line so we can catch $GENERATE usage 439 report_info('=> copying "%s" to "%s"' % (oldpath, newpath)) 440 441 if not os.path.exists(oldpath): 442 os.system('touch ' + newpath) 443 return 444 445 oldfd = open(oldpath, 'r+') 446 newfd = open(newpath, 'w+') 447 line = oldfd.readline() 448 while line: 449 fields = line.split() 450 if len(fields) > 0 and fields[0].strip() == '$GENERATE': 451 report_info('=> processing "%s"...' % (line.strip())) 452 index = 1 453 454 #-- determine start/stop range 455 indices = fields[index].split('-') 456 start = 0 457 stop = 0 458 if len(indices) == 2: 459 start = int(indices[0]) 460 stop = int(indices[1]) 461 if stop < start: 462 bail('? stop < start in range "%s"' % (fields[1].strip())) 463 else: 464 bail('? invalid range "%s"' % (fields[1].strip())) 465 index += 1 466 467 #-- determine increment 468 step = 1 469 if len(fields) == 6: 470 step = int(fields[index]) 471 index += 1 472 473 #-- get lhs 474 lhs = fields[index].strip() 475 index += 1 476 477 #-- get RR type 478 rrtype = fields[index].strip() 479 if rrtype not in [ 'NS', 'PTR', 'A', 'AAAA', 'DNAME', 'CNAME' ]: 480 bail('? illegal RR type "%s"' % (rrtype)) 481 index += 1 482 483 #-- get rhs 484 rhs = fields[index].strip() 485 index += 1 486 487 #-- do the $GENERATE 488 self.do_generate(newfd, start, stop, step, lhs, rrtype, rhs) 489 490 else: #-- just copy the line as is 491 print >> newfd, line, 492 493 line = oldfd.readline() 494 495 oldfd.close() 496 newfd.close() 497 498 return 499 500 def write_zone_files(self): 501 acls = {} 502 503 zlist = self.zones.keys() 504 zlist.sort() 505 for ii in zlist: 506 oldpath = self.oldrootdir + self.zones[ii].getZonefile() 507 newpath = self.config.getValue('tmpdir') + self.zones[ii].getZonefile() 508 if not os.path.exists(os.path.dirname(newpath)): 509 os.makedirs(os.path.dirname(newpath)) 510 self.make_zone_copy(oldpath, newpath) 511 512 incl = self.zones[ii].getInclude() 513 if acls.has_key(incl): 514 acls[incl] += 1 515 else: 516 acls[incl] = 1 517 518 alist = acls.keys() 519 alist.sort() 520 for ii in alist: 521 newpath = self.config.getValue('tmpdir') + ii 522 run_cmd('touch ' + newpath, 'touch "%s"' % (newpath)) 523 524 return 525 526 def addZone(self, zone): 527 name = zone.getName() 528 self.zones[name] = zone 529 return 530 531 def getZones(self): 532 return self.zones 533 534 def addKey(self, key): 535 self.keys[key.getName()] = key 536 return 537 538 def getKeys(self): 539 return self.keys 540 541 def getKey(self, name): 542 if name in self.keys: 543 return self.keys[name] 544 else: 545 return None 546 547