Add HIP RR type
[third_party/dnspython] / dns / update.py
1 # Copyright (C) 2003-2007, 2009 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
25 class Update(dns.message.Message):
26     def __init__(self, zone, rdclass=dns.rdataclass.IN, keyring=None,
27                  keyname=None):
28         """Initialize a new DNS Update object.
29         
30         @param zone: The zone which is being updated.
31         @type zone: A dns.name.Name or string
32         @param rdclass: The class of the zone; defaults to dns.rdataclass.IN.
33         @type rdclass: An int designating the class, or a string whose value
34         is the name of a class.
35         @param keyring: The TSIG keyring to use; defaults to None.
36         @type keyring: dict
37         @param keyname: The name of the TSIG key to use; defaults to None.
38         The key must be defined in the keyring.  If a keyring is specified
39         but a keyname is not, then the key used will be the first key in the
40         keyring.  Note that the order of keys in a dictionary is not defined,
41         so applications should supply a keyname when a keyring is used, unless
42         they know the keyring contains only one key.
43         @type keyname: dns.name.Name or string
44         """
45         super(Update, self).__init__()
46         self.flags |= dns.opcode.to_flags(dns.opcode.UPDATE)
47         if isinstance(zone, (str, unicode)):
48             zone = dns.name.from_text(zone)
49         self.origin = zone
50         if isinstance(rdclass, str):
51             rdclass = dns.rdataclass.from_text(rdclass)
52         self.zone_rdclass = rdclass
53         self.find_rrset(self.question, self.origin, rdclass, dns.rdatatype.SOA,
54                         create=True, force_unique=True)
55         if not keyring is None:
56             self.use_tsig(keyring, keyname)
57
58     def _add_rr(self, name, ttl, rd, deleting=None, section=None):
59         """Add a single RR to the update section."""
60
61         if section is None:
62             section = self.authority
63         covers = rd.covers()
64         rrset = self.find_rrset(section, name, self.zone_rdclass, rd.rdtype,
65                                 covers, deleting, True, True)
66         rrset.add(rd, ttl)
67
68     def _add(self, replace, section, name, *args):
69         """Add records.  The first argument is the replace mode.  If
70         false, RRs are added to an existing RRset; if true, the RRset
71         is replaced with the specified contents.  The second
72         argument is the section to add to.  The third argument
73         is always a name.  The other arguments can be:
74
75                 - rdataset...
76
77                 - ttl, rdata...
78
79                 - ttl, rdtype, string..."""
80
81         if isinstance(name, (str, unicode)):
82             name = dns.name.from_text(name, None)
83         if isinstance(args[0], dns.rdataset.Rdataset):
84             for rds in args:
85                 if replace:
86                     self.delete(name, rds.rdtype)
87                 for rd in rds:
88                     self._add_rr(name, rds.ttl, rd, section=section)
89         else:
90             args = list(args)
91             ttl = int(args.pop(0))
92             if isinstance(args[0], dns.rdata.Rdata):
93                 if replace:
94                     self.delete(name, args[0].rdtype)
95                 for rd in args:
96                     self._add_rr(name, ttl, rd, section=section)
97             else:
98                 rdtype = args.pop(0)
99                 if isinstance(rdtype, str):
100                     rdtype = dns.rdatatype.from_text(rdtype)
101                 if replace:
102                     self.delete(name, rdtype)
103                 for s in args:
104                     rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s,
105                                              self.origin)
106                     self._add_rr(name, ttl, rd, section=section)
107
108     def add(self, name, *args):
109         """Add records.  The first argument is always a name.  The other
110         arguments can be:
111
112                 - rdataset...
113
114                 - ttl, rdata...
115
116                 - ttl, rdtype, string..."""
117         self._add(False, self.authority, name, *args)
118
119     def delete(self, name, *args):
120         """Delete records.  The first argument is always a name.  The other
121         arguments can be:
122
123                 - I{nothing}
124                 
125                 - rdataset...
126
127                 - rdata...
128
129                 - rdtype, [string...]"""
130
131         if isinstance(name, (str, unicode)):
132             name = dns.name.from_text(name, None)
133         if len(args) == 0:
134             rrset = self.find_rrset(self.authority, name, dns.rdataclass.ANY,
135                                     dns.rdatatype.ANY, dns.rdatatype.NONE,
136                                     dns.rdatatype.ANY, True, True)
137         elif isinstance(args[0], dns.rdataset.Rdataset):
138             for rds in args:
139                 for rd in rds:
140                     self._add_rr(name, 0, rd, dns.rdataclass.NONE)
141         else:
142             args = list(args)
143             if isinstance(args[0], dns.rdata.Rdata):
144                 for rd in args:
145                     self._add_rr(name, 0, rd, dns.rdataclass.NONE)
146             else:
147                 rdtype = args.pop(0)
148                 if isinstance(rdtype, str):
149                     rdtype = dns.rdatatype.from_text(rdtype)
150                 if len(args) == 0:
151                     rrset = self.find_rrset(self.authority, name,
152                                             self.zone_rdclass, rdtype,
153                                             dns.rdatatype.NONE,
154                                             dns.rdataclass.ANY,
155                                             True, True)
156                 else:
157                     for s in args:
158                         rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s,
159                                                  self.origin)
160                         self._add_rr(name, 0, rd, dns.rdataclass.NONE)
161
162     def replace(self, name, *args):
163         """Replace records.  The first argument is always a name.  The other
164         arguments can be:
165                 
166                 - rdataset...
167
168                 - ttl, rdata...
169
170                 - ttl, rdtype, string...
171
172         Note that if you want to replace the entire node, you should do
173         a delete of the name followed by one or more calls to add."""
174         
175         self._add(True, self.authority, name, *args)
176
177     def present(self, name, *args):
178         """Require that an owner name (and optionally an rdata type,
179         or specific rdataset) exists as a prerequisite to the
180         execution of the update.  The first argument is always a name.
181         The other arguments can be:
182                 
183                 - rdataset...
184
185                 - rdata...
186
187                 - rdtype, string..."""
188         
189         if isinstance(name, (str, unicode)):
190             name = dns.name.from_text(name, None)
191         if len(args) == 0:
192             rrset = self.find_rrset(self.answer, name,
193                                     dns.rdataclass.ANY, dns.rdatatype.ANY,
194                                     dns.rdatatype.NONE, None,
195                                     True, True)
196         elif isinstance(args[0], dns.rdataset.Rdataset) or \
197              isinstance(args[0], dns.rdata.Rdata) or \
198              len(args) > 1:
199             if len(args) > 1:
200                 # Add a 0 TTL
201                 args = list(args)
202                 args.insert(0, 0)
203             self._add(False, self.answer, name, *args)
204         else:
205             rdtype = args[0]
206             if isinstance(rdtype, str):
207                 rdtype = dns.rdatatype.from_text(rdtype)
208             rrset = self.find_rrset(self.answer, name,
209                                     dns.rdataclass.ANY, rdtype,
210                                     dns.rdatatype.NONE, None,
211                                     True, True)
212
213     def absent(self, name, rdtype=None):
214         """Require that an owner name (and optionally an rdata type) does
215         not exist as a prerequisite to the execution of the update."""
216
217         if isinstance(name, (str, unicode)):
218             name = dns.name.from_text(name, None)
219         if rdtype is None:
220             rrset = self.find_rrset(self.answer, name,
221                                     dns.rdataclass.NONE, dns.rdatatype.ANY, 
222                                     dns.rdatatype.NONE, None,
223                                     True, True)
224         else:
225             if isinstance(rdtype, str):
226                 rdtype = dns.rdatatype.from_text(rdtype)
227             rrset = self.find_rrset(self.answer, name,
228                                     dns.rdataclass.NONE, rdtype,
229                                     dns.rdatatype.NONE, None,
230                                     True, True)
231
232     def to_wire(self, origin=None, max_size=65535):
233         """Return a string containing the update in DNS compressed wire
234         format.
235         @rtype: string"""
236         if origin is None:
237             origin = self.origin
238         return super(Update, self).to_wire(origin, max_size)