1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license 2 3# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. 4# 5# Permission to use, copy, modify, and distribute this software and its 6# documentation for any purpose with or without fee is hereby granted, 7# provided that the above copyright notice and this permission notice 8# appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18"""DNS Dynamic Update Support""" 19 20 21import dns.message 22import dns.name 23import dns.opcode 24import dns.rdata 25import dns.rdataclass 26import dns.rdataset 27import dns.tsig 28from ._compat import string_types 29 30 31class Update(dns.message.Message): 32 33 def __init__(self, zone, rdclass=dns.rdataclass.IN, keyring=None, 34 keyname=None, keyalgorithm=dns.tsig.default_algorithm): 35 """Initialize a new DNS Update object. 36 37 See the documentation of the Message class for a complete 38 description of the keyring dictionary. 39 40 *zone*, a ``dns.name.Name`` or ``text``, the zone which is being 41 updated. 42 43 *rdclass*, an ``int`` or ``text``, the class of the zone. 44 45 *keyring*, a ``dict``, the TSIG keyring to use. If a 46 *keyring* is specified but a *keyname* is not, then the key 47 used will be the first key in the *keyring*. Note that the 48 order of keys in a dictionary is not defined, so applications 49 should supply a keyname when a keyring is used, unless they 50 know the keyring contains only one key. 51 52 *keyname*, a ``dns.name.Name`` or ``None``, the name of the TSIG key 53 to use; defaults to ``None``. The key must be defined in the keyring. 54 55 *keyalgorithm*, a ``dns.name.Name``, the TSIG algorithm to use. 56 """ 57 super(Update, self).__init__() 58 self.flags |= dns.opcode.to_flags(dns.opcode.UPDATE) 59 if isinstance(zone, string_types): 60 zone = dns.name.from_text(zone) 61 self.origin = zone 62 if isinstance(rdclass, string_types): 63 rdclass = dns.rdataclass.from_text(rdclass) 64 self.zone_rdclass = rdclass 65 self.find_rrset(self.question, self.origin, rdclass, dns.rdatatype.SOA, 66 create=True, force_unique=True) 67 if keyring is not None: 68 self.use_tsig(keyring, keyname, algorithm=keyalgorithm) 69 70 def _add_rr(self, name, ttl, rd, deleting=None, section=None): 71 """Add a single RR to the update section.""" 72 73 if section is None: 74 section = self.authority 75 covers = rd.covers() 76 rrset = self.find_rrset(section, name, self.zone_rdclass, rd.rdtype, 77 covers, deleting, True, True) 78 rrset.add(rd, ttl) 79 80 def _add(self, replace, section, name, *args): 81 """Add records. 82 83 *replace* is the replacement mode. If ``False``, 84 RRs are added to an existing RRset; if ``True``, the RRset 85 is replaced with the specified contents. The second 86 argument is the section to add to. The third argument 87 is always a name. The other arguments can be: 88 89 - rdataset... 90 91 - ttl, rdata... 92 93 - ttl, rdtype, string... 94 """ 95 96 if isinstance(name, string_types): 97 name = dns.name.from_text(name, None) 98 if isinstance(args[0], dns.rdataset.Rdataset): 99 for rds in args: 100 if replace: 101 self.delete(name, rds.rdtype) 102 for rd in rds: 103 self._add_rr(name, rds.ttl, rd, section=section) 104 else: 105 args = list(args) 106 ttl = int(args.pop(0)) 107 if isinstance(args[0], dns.rdata.Rdata): 108 if replace: 109 self.delete(name, args[0].rdtype) 110 for rd in args: 111 self._add_rr(name, ttl, rd, section=section) 112 else: 113 rdtype = args.pop(0) 114 if isinstance(rdtype, string_types): 115 rdtype = dns.rdatatype.from_text(rdtype) 116 if replace: 117 self.delete(name, rdtype) 118 for s in args: 119 rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s, 120 self.origin) 121 self._add_rr(name, ttl, rd, section=section) 122 123 def add(self, name, *args): 124 """Add records. 125 126 The first argument is always a name. The other 127 arguments can be: 128 129 - rdataset... 130 131 - ttl, rdata... 132 133 - ttl, rdtype, string... 134 """ 135 136 self._add(False, self.authority, name, *args) 137 138 def delete(self, name, *args): 139 """Delete records. 140 141 The first argument is always a name. The other 142 arguments can be: 143 144 - *empty* 145 146 - rdataset... 147 148 - rdata... 149 150 - rdtype, [string...] 151 """ 152 153 if isinstance(name, string_types): 154 name = dns.name.from_text(name, None) 155 if len(args) == 0: 156 self.find_rrset(self.authority, name, dns.rdataclass.ANY, 157 dns.rdatatype.ANY, dns.rdatatype.NONE, 158 dns.rdatatype.ANY, True, True) 159 elif isinstance(args[0], dns.rdataset.Rdataset): 160 for rds in args: 161 for rd in rds: 162 self._add_rr(name, 0, rd, dns.rdataclass.NONE) 163 else: 164 args = list(args) 165 if isinstance(args[0], dns.rdata.Rdata): 166 for rd in args: 167 self._add_rr(name, 0, rd, dns.rdataclass.NONE) 168 else: 169 rdtype = args.pop(0) 170 if isinstance(rdtype, string_types): 171 rdtype = dns.rdatatype.from_text(rdtype) 172 if len(args) == 0: 173 self.find_rrset(self.authority, name, 174 self.zone_rdclass, rdtype, 175 dns.rdatatype.NONE, 176 dns.rdataclass.ANY, 177 True, True) 178 else: 179 for s in args: 180 rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s, 181 self.origin) 182 self._add_rr(name, 0, rd, dns.rdataclass.NONE) 183 184 def replace(self, name, *args): 185 """Replace records. 186 187 The first argument is always a name. The other 188 arguments can be: 189 190 - rdataset... 191 192 - ttl, rdata... 193 194 - ttl, rdtype, string... 195 196 Note that if you want to replace the entire node, you should do 197 a delete of the name followed by one or more calls to add. 198 """ 199 200 self._add(True, self.authority, name, *args) 201 202 def present(self, name, *args): 203 """Require that an owner name (and optionally an rdata type, 204 or specific rdataset) exists as a prerequisite to the 205 execution of the update. 206 207 The first argument is always a name. 208 The other arguments can be: 209 210 - rdataset... 211 212 - rdata... 213 214 - rdtype, string... 215 """ 216 217 if isinstance(name, string_types): 218 name = dns.name.from_text(name, None) 219 if len(args) == 0: 220 self.find_rrset(self.answer, name, 221 dns.rdataclass.ANY, dns.rdatatype.ANY, 222 dns.rdatatype.NONE, None, 223 True, True) 224 elif isinstance(args[0], dns.rdataset.Rdataset) or \ 225 isinstance(args[0], dns.rdata.Rdata) or \ 226 len(args) > 1: 227 if not isinstance(args[0], dns.rdataset.Rdataset): 228 # Add a 0 TTL 229 args = list(args) 230 args.insert(0, 0) 231 self._add(False, self.answer, name, *args) 232 else: 233 rdtype = args[0] 234 if isinstance(rdtype, string_types): 235 rdtype = dns.rdatatype.from_text(rdtype) 236 self.find_rrset(self.answer, name, 237 dns.rdataclass.ANY, rdtype, 238 dns.rdatatype.NONE, None, 239 True, True) 240 241 def absent(self, name, rdtype=None): 242 """Require that an owner name (and optionally an rdata type) does 243 not exist as a prerequisite to the execution of the update.""" 244 245 if isinstance(name, string_types): 246 name = dns.name.from_text(name, None) 247 if rdtype is None: 248 self.find_rrset(self.answer, name, 249 dns.rdataclass.NONE, dns.rdatatype.ANY, 250 dns.rdatatype.NONE, None, 251 True, True) 252 else: 253 if isinstance(rdtype, string_types): 254 rdtype = dns.rdatatype.from_text(rdtype) 255 self.find_rrset(self.answer, name, 256 dns.rdataclass.NONE, rdtype, 257 dns.rdatatype.NONE, None, 258 True, True) 259 260 def to_wire(self, origin=None, max_size=65535): 261 """Return a string containing the update in DNS compressed wire 262 format. 263 264 *origin*, a ``dns.name.Name`` or ``None``, the origin to be 265 appended to any relative names. If *origin* is ``None``, then 266 the origin of the ``dns.update.Update`` message object is used 267 (i.e. the *zone* parameter passed when the Update object was 268 created). 269 270 *max_size*, an ``int``, the maximum size of the wire format 271 output; default is 0, which means "the message's request 272 payload, if nonzero, or 65535". 273 274 Returns a ``binary``. 275 """ 276 277 if origin is None: 278 origin = self.origin 279 return super(Update, self).to_wire(origin, max_size) 280