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