| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
from twisted.internet import task, defer |
|---|
| 5 |
from twisted.names import dns |
|---|
| 6 |
from twisted.names import common |
|---|
| 7 |
from twisted.names import client |
|---|
| 8 |
from twisted.names import resolve |
|---|
| 9 |
from twisted.python import log, failure |
|---|
| 10 |
from twisted.application import service |
|---|
| 11 |
|
|---|
| 12 |
class SecondaryAuthorityService(service.Service): |
|---|
| 13 |
calls = None |
|---|
| 14 |
|
|---|
| 15 |
def __init__(self, primary, domains): |
|---|
| 16 |
""" |
|---|
| 17 |
@param primary: The IP address of the server from which to perform |
|---|
| 18 |
zone transfers. |
|---|
| 19 |
|
|---|
| 20 |
@param domains: A sequence of domain names for which to perform |
|---|
| 21 |
zone transfers. |
|---|
| 22 |
""" |
|---|
| 23 |
self.primary = primary |
|---|
| 24 |
self.domains = [SecondaryAuthority(primary, d) for d in domains] |
|---|
| 25 |
|
|---|
| 26 |
def getAuthority(self): |
|---|
| 27 |
return resolve.ResolverChain(self.domains) |
|---|
| 28 |
|
|---|
| 29 |
def startService(self): |
|---|
| 30 |
service.Service.startService(self) |
|---|
| 31 |
self.calls = [task.LoopingCall(d.transfer) for d in self.domains] |
|---|
| 32 |
i = 0 |
|---|
| 33 |
from twisted.internet import reactor |
|---|
| 34 |
for c in self.calls: |
|---|
| 35 |
|
|---|
| 36 |
reactor.callLater(i, c.start, 60 * 60) |
|---|
| 37 |
i += 1 |
|---|
| 38 |
|
|---|
| 39 |
def stopService(self): |
|---|
| 40 |
service.Service.stopService(self) |
|---|
| 41 |
for c in self.calls: |
|---|
| 42 |
c.stop() |
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
from twisted.names.authority import FileAuthority |
|---|
| 46 |
|
|---|
| 47 |
class SecondaryAuthority(common.ResolverBase): |
|---|
| 48 |
"""An Authority that keeps itself updated by performing zone transfers""" |
|---|
| 49 |
|
|---|
| 50 |
transferring = False |
|---|
| 51 |
|
|---|
| 52 |
soa = records = None |
|---|
| 53 |
def __init__(self, primaryIP, domain): |
|---|
| 54 |
common.ResolverBase.__init__(self) |
|---|
| 55 |
self.primary = primaryIP |
|---|
| 56 |
self.domain = domain |
|---|
| 57 |
|
|---|
| 58 |
def transfer(self): |
|---|
| 59 |
if self.transferring: |
|---|
| 60 |
return |
|---|
| 61 |
self.transfering = True |
|---|
| 62 |
return client.Resolver(servers=[(self.primary, dns.PORT)] |
|---|
| 63 |
).lookupZone(self.domain |
|---|
| 64 |
).addCallback(self._cbZone |
|---|
| 65 |
).addErrback(self._ebZone |
|---|
| 66 |
) |
|---|
| 67 |
|
|---|
| 68 |
|
|---|
| 69 |
def _lookup(self, name, cls, type, timeout=None): |
|---|
| 70 |
if not self.soa or not self.records: |
|---|
| 71 |
return defer.fail(failure.Failure(dns.DomainError(name))) |
|---|
| 72 |
|
|---|
| 73 |
|
|---|
| 74 |
return FileAuthority.__dict__['_lookup'](self, name, cls, type, timeout) |
|---|
| 75 |
|
|---|
| 76 |
|
|---|
| 77 |
|
|---|
| 78 |
lookupZone = FileAuthority.__dict__['lookupZone'] |
|---|
| 79 |
|
|---|
| 80 |
def _cbZone(self, zone): |
|---|
| 81 |
ans, _, _ = zone |
|---|
| 82 |
self.records = r = {} |
|---|
| 83 |
for rec in ans: |
|---|
| 84 |
if not self.soa and rec.type == dns.SOA: |
|---|
| 85 |
self.soa = (str(rec.name).lower(), rec.payload) |
|---|
| 86 |
else: |
|---|
| 87 |
r.setdefault(str(rec.name).lower(), []).append(rec.payload) |
|---|
| 88 |
|
|---|
| 89 |
def _ebZone(self, failure): |
|---|
| 90 |
log.msg("Updating %s from %s failed during zone transfer" % (self.domain, self.primary)) |
|---|
| 91 |
log.err(failure) |
|---|
| 92 |
|
|---|
| 93 |
def update(self): |
|---|
| 94 |
self.transfer().addCallbacks(self._cbTransferred, self._ebTransferred) |
|---|
| 95 |
|
|---|
| 96 |
def _cbTransferred(self, result): |
|---|
| 97 |
self.transferring = False |
|---|
| 98 |
|
|---|
| 99 |
def _ebTransferred(self, failure): |
|---|
| 100 |
self.transferred = False |
|---|
| 101 |
log.msg("Transferring %s from %s failed after zone transfer" % (self.domain, self.primary)) |
|---|
| 102 |
log.err(failure) |
|---|