1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """DNS Dynamic Update Support"""
19
20
21 import dns.message
22 import dns.name
23 import dns.opcode
24 import dns.rdata
25 import dns.rdataclass
26 import dns.rdataset
27 import dns.tsig
28 from ._compat import string_types
29
30
31 -class Update(dns.message.Message):
32
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):
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
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
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
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