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 28 29 30class UpdateSection(dns.enum.IntEnum): 31 """Update sections""" 32 ZONE = 0 33 PREREQ = 1 34 UPDATE = 2 35 ADDITIONAL = 3 36 37 @classmethod 38 def _maximum(cls): 39 return 3 40 41 42class UpdateMessage(dns.message.Message): 43 44 _section_enum = UpdateSection 45 46 def __init__(self, zone=None, rdclass=dns.rdataclass.IN, keyring=None, 47 keyname=None, keyalgorithm=dns.tsig.default_algorithm, 48 id=None): 49 """Initialize a new DNS Update object. 50 51 See the documentation of the Message class for a complete 52 description of the keyring dictionary. 53 54 *zone*, a ``dns.name.Name``, ``str``, or ``None``, the zone 55 which is being updated. ``None`` should only be used by dnspython's 56 message constructors, as a zone is required for the convenience 57 methods like ``add()``, ``replace()``, etc. 58 59 *rdclass*, an ``int`` or ``str``, the class of the zone. 60 61 The *keyring*, *keyname*, and *keyalgorithm* parameters are passed to 62 ``use_tsig()``; see its documentation for details. 63 """ 64 super().__init__(id=id) 65 self.flags |= dns.opcode.to_flags(dns.opcode.UPDATE) 66 if isinstance(zone, str): 67 zone = dns.name.from_text(zone) 68 self.origin = zone 69 rdclass = dns.rdataclass.RdataClass.make(rdclass) 70 self.zone_rdclass = rdclass 71 if self.origin: 72 self.find_rrset(self.zone, self.origin, rdclass, dns.rdatatype.SOA, 73 create=True, force_unique=True) 74 if keyring is not None: 75 self.use_tsig(keyring, keyname, algorithm=keyalgorithm) 76 77 @property 78 def zone(self): 79 """The zone section.""" 80 return self.sections[0] 81 82 @zone.setter 83 def zone(self, v): 84 self.sections[0] = v 85 86 @property 87 def prerequisite(self): 88 """The prerequisite section.""" 89 return self.sections[1] 90 91 @prerequisite.setter 92 def prerequisite(self, v): 93 self.sections[1] = v 94 95 @property 96 def update(self): 97 """The update section.""" 98 return self.sections[2] 99 100 @update.setter 101 def update(self, v): 102 self.sections[2] = v 103 104 def _add_rr(self, name, ttl, rd, deleting=None, section=None): 105 """Add a single RR to the update section.""" 106 107 if section is None: 108 section = self.update 109 covers = rd.covers() 110 rrset = self.find_rrset(section, name, self.zone_rdclass, rd.rdtype, 111 covers, deleting, True, True) 112 rrset.add(rd, ttl) 113 114 def _add(self, replace, section, name, *args): 115 """Add records. 116 117 *replace* is the replacement mode. If ``False``, 118 RRs are added to an existing RRset; if ``True``, the RRset 119 is replaced with the specified contents. The second 120 argument is the section to add to. The third argument 121 is always a name. The other arguments can be: 122 123 - rdataset... 124 125 - ttl, rdata... 126 127 - ttl, rdtype, string... 128 """ 129 130 if isinstance(name, str): 131 name = dns.name.from_text(name, None) 132 if isinstance(args[0], dns.rdataset.Rdataset): 133 for rds in args: 134 if replace: 135 self.delete(name, rds.rdtype) 136 for rd in rds: 137 self._add_rr(name, rds.ttl, rd, section=section) 138 else: 139 args = list(args) 140 ttl = int(args.pop(0)) 141 if isinstance(args[0], dns.rdata.Rdata): 142 if replace: 143 self.delete(name, args[0].rdtype) 144 for rd in args: 145 self._add_rr(name, ttl, rd, section=section) 146 else: 147 rdtype = dns.rdatatype.RdataType.make(args.pop(0)) 148 if replace: 149 self.delete(name, rdtype) 150 for s in args: 151 rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s, 152 self.origin) 153 self._add_rr(name, ttl, rd, section=section) 154 155 def add(self, name, *args): 156 """Add records. 157 158 The first argument is always a name. The other 159 arguments can be: 160 161 - rdataset... 162 163 - ttl, rdata... 164 165 - ttl, rdtype, string... 166 """ 167 168 self._add(False, self.update, name, *args) 169 170 def delete(self, name, *args): 171 """Delete records. 172 173 The first argument is always a name. The other 174 arguments can be: 175 176 - *empty* 177 178 - rdataset... 179 180 - rdata... 181 182 - rdtype, [string...] 183 """ 184 185 if isinstance(name, str): 186 name = dns.name.from_text(name, None) 187 if len(args) == 0: 188 self.find_rrset(self.update, name, dns.rdataclass.ANY, 189 dns.rdatatype.ANY, dns.rdatatype.NONE, 190 dns.rdatatype.ANY, True, True) 191 elif isinstance(args[0], dns.rdataset.Rdataset): 192 for rds in args: 193 for rd in rds: 194 self._add_rr(name, 0, rd, dns.rdataclass.NONE) 195 else: 196 args = list(args) 197 if isinstance(args[0], dns.rdata.Rdata): 198 for rd in args: 199 self._add_rr(name, 0, rd, dns.rdataclass.NONE) 200 else: 201 rdtype = dns.rdatatype.RdataType.make(args.pop(0)) 202 if len(args) == 0: 203 self.find_rrset(self.update, name, 204 self.zone_rdclass, rdtype, 205 dns.rdatatype.NONE, 206 dns.rdataclass.ANY, 207 True, True) 208 else: 209 for s in args: 210 rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s, 211 self.origin) 212 self._add_rr(name, 0, rd, dns.rdataclass.NONE) 213 214 def replace(self, name, *args): 215 """Replace records. 216 217 The first argument is always a name. The other 218 arguments can be: 219 220 - rdataset... 221 222 - ttl, rdata... 223 224 - ttl, rdtype, string... 225 226 Note that if you want to replace the entire node, you should do 227 a delete of the name followed by one or more calls to add. 228 """ 229 230 self._add(True, self.update, name, *args) 231 232 def present(self, name, *args): 233 """Require that an owner name (and optionally an rdata type, 234 or specific rdataset) exists as a prerequisite to the 235 execution of the update. 236 237 The first argument is always a name. 238 The other arguments can be: 239 240 - rdataset... 241 242 - rdata... 243 244 - rdtype, string... 245 """ 246 247 if isinstance(name, str): 248 name = dns.name.from_text(name, None) 249 if len(args) == 0: 250 self.find_rrset(self.prerequisite, name, 251 dns.rdataclass.ANY, dns.rdatatype.ANY, 252 dns.rdatatype.NONE, None, 253 True, True) 254 elif isinstance(args[0], dns.rdataset.Rdataset) or \ 255 isinstance(args[0], dns.rdata.Rdata) or \ 256 len(args) > 1: 257 if not isinstance(args[0], dns.rdataset.Rdataset): 258 # Add a 0 TTL 259 args = list(args) 260 args.insert(0, 0) 261 self._add(False, self.prerequisite, name, *args) 262 else: 263 rdtype = dns.rdatatype.RdataType.make(args[0]) 264 self.find_rrset(self.prerequisite, name, 265 dns.rdataclass.ANY, rdtype, 266 dns.rdatatype.NONE, None, 267 True, True) 268 269 def absent(self, name, rdtype=None): 270 """Require that an owner name (and optionally an rdata type) does 271 not exist as a prerequisite to the execution of the update.""" 272 273 if isinstance(name, str): 274 name = dns.name.from_text(name, None) 275 if rdtype is None: 276 self.find_rrset(self.prerequisite, name, 277 dns.rdataclass.NONE, dns.rdatatype.ANY, 278 dns.rdatatype.NONE, None, 279 True, True) 280 else: 281 rdtype = dns.rdatatype.RdataType.make(rdtype) 282 self.find_rrset(self.prerequisite, name, 283 dns.rdataclass.NONE, rdtype, 284 dns.rdatatype.NONE, None, 285 True, True) 286 287 def _get_one_rr_per_rrset(self, value): 288 # Updates are always one_rr_per_rrset 289 return True 290 291 def _parse_rr_header(self, section, name, rdclass, rdtype): 292 deleting = None 293 empty = False 294 if section == UpdateSection.ZONE: 295 if dns.rdataclass.is_metaclass(rdclass) or \ 296 rdtype != dns.rdatatype.SOA or \ 297 self.zone: 298 raise dns.exception.FormError 299 else: 300 if not self.zone: 301 raise dns.exception.FormError 302 if rdclass in (dns.rdataclass.ANY, dns.rdataclass.NONE): 303 deleting = rdclass 304 rdclass = self.zone[0].rdclass 305 empty = (deleting == dns.rdataclass.ANY or 306 section == UpdateSection.PREREQ) 307 return (rdclass, rdtype, deleting, empty) 308 309# backwards compatibility 310Update = UpdateMessage 311 312### BEGIN generated UpdateSection constants 313 314ZONE = UpdateSection.ZONE 315PREREQ = UpdateSection.PREREQ 316UPDATE = UpdateSection.UPDATE 317ADDITIONAL = UpdateSection.ADDITIONAL 318 319### END generated UpdateSection constants 320