s3:tests: Fix authentication with smbget_user in smbget tests
[Samba.git] / python / samba / dnsserver.py
blob965977acdeb62acb778442a07f2a8f2ea0c601a5
1 # helper for DNS management tool
3 # Copyright (C) Amitay Isaacs 2011-2012
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import shlex
20 import socket
21 from samba.dcerpc import dnsserver, dnsp
22 from samba import WERRORError, werror
24 # Note: these are not quite the same as similar looking classes in
25 # provision/sambadns.py -- those ones are based on
26 # dnsp.DnssrvRpcRecord, these are based on dnsserver.DNS_RPC_RECORD.
27 # They encode the same information in slightly different ways.
29 # DNS_RPC_RECORD structures ([MS-DNSP]2.2.2.2.5 "DNS_RPC_RECORD") are
30 # used on the wire by DnssrvEnumRecords2. The dnsp.DnssrvRpcRecord
31 # versions have the in-database version of the same information, where
32 # the flags field is unpacked, and the struct ordering is different.
33 # See [MS-DNSP] 2.3.2.2 "DnsRecord".
35 # In both cases the structure and contents of .data depend on .wType.
36 # For example, if .wType is DNS_TYPE_A, .data is an IPv4 address. If
37 # the .wType is changed to DNS_TYPE_CNAME, the contents of .data will
38 # be interpreted as a cname blob, but the bytes there will still be
39 # those of the IPv4 address. If you don't also set the .data you may
40 # encounter stability problems. These DNS_RPC_RECORD subclasses
41 # attempt to hide that from you, but are only pretending -- any of
42 # them can represent any type of record.
45 class DNSParseError(ValueError):
46 pass
49 class ARecord(dnsserver.DNS_RPC_RECORD):
50 def __init__(self, ip_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
51 node_flag=0):
52 super(ARecord, self).__init__()
53 self.wType = dnsp.DNS_TYPE_A
54 self.dwFlags = rank | node_flag
55 self.dwSerial = serial
56 self.dwTtlSeconds = ttl
57 self.data = ip_addr
59 @classmethod
60 def from_string(cls, data, sep=None, **kwargs):
61 return cls(data, **kwargs)
64 class AAAARecord(dnsserver.DNS_RPC_RECORD):
66 def __init__(self, ip6_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
67 node_flag=0):
68 super(AAAARecord, self).__init__()
69 self.wType = dnsp.DNS_TYPE_AAAA
70 self.dwFlags = rank | node_flag
71 self.dwSerial = serial
72 self.dwTtlSeconds = ttl
73 self.data = ip6_addr
75 @classmethod
76 def from_string(cls, data, sep=None, **kwargs):
77 return cls(data, **kwargs)
80 class PTRRecord(dnsserver.DNS_RPC_RECORD):
82 def __init__(self, ptr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
83 node_flag=0):
84 super(PTRRecord, self).__init__()
85 self.wType = dnsp.DNS_TYPE_PTR
86 self.dwFlags = rank | node_flag
87 self.dwSerial = serial
88 self.dwTtlSeconds = ttl
89 ptr_name = dnsserver.DNS_RPC_NAME()
90 ptr_name.str = ptr
91 ptr_name.len = len(ptr)
92 self.data = ptr_name
94 @classmethod
95 def from_string(cls, data, sep=None, **kwargs):
96 return cls(data, **kwargs)
99 class CNAMERecord(dnsserver.DNS_RPC_RECORD):
101 def __init__(self, cname, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
102 node_flag=0):
103 super().__init__()
104 self.wType = dnsp.DNS_TYPE_CNAME
105 self.dwFlags = rank | node_flag
106 self.dwSerial = serial
107 self.dwTtlSeconds = ttl
108 cname_name = dnsserver.DNS_RPC_NAME()
109 cname_name.str = cname
110 cname_name.len = len(cname)
111 self.data = cname_name
113 @classmethod
114 def from_string(cls, data, sep=None, **kwargs):
115 return cls(data, **kwargs)
118 class NSRecord(dnsserver.DNS_RPC_RECORD):
120 def __init__(self, dns_server, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
121 node_flag=0):
122 super(NSRecord, self).__init__()
123 self.wType = dnsp.DNS_TYPE_NS
124 self.dwFlags = rank | node_flag
125 self.dwSerial = serial
126 self.dwTtlSeconds = ttl
127 ns = dnsserver.DNS_RPC_NAME()
128 ns.str = dns_server
129 ns.len = len(dns_server)
130 self.data = ns
132 @classmethod
133 def from_string(cls, data, sep=None, **kwargs):
134 return cls(data, **kwargs)
137 class MXRecord(dnsserver.DNS_RPC_RECORD):
139 def __init__(self, mail_server, preference, serial=1, ttl=900,
140 rank=dnsp.DNS_RANK_ZONE, node_flag=0):
141 super(MXRecord, self).__init__()
142 self.wType = dnsp.DNS_TYPE_MX
143 self.dwFlags = rank | node_flag
144 self.dwSerial = serial
145 self.dwTtlSeconds = ttl
146 mx = dnsserver.DNS_RPC_RECORD_NAME_PREFERENCE()
147 mx.wPreference = preference
148 mx.nameExchange.str = mail_server
149 mx.nameExchange.len = len(mail_server)
150 self.data = mx
152 @classmethod
153 def from_string(cls, data, sep=None, **kwargs):
154 try:
155 server, priority = data.split(sep)
156 priority = int(priority)
157 except ValueError as e:
158 raise DNSParseError("MX data must have server and priority "
159 "(space separated), not %r" % data) from e
160 return cls(server, priority, **kwargs)
163 class SOARecord(dnsserver.DNS_RPC_RECORD):
165 def __init__(self, mname, rname, serial=1, refresh=900, retry=600,
166 expire=86400, minimum=3600, ttl=3600, rank=dnsp.DNS_RANK_ZONE,
167 node_flag=dnsp.DNS_RPC_FLAG_AUTH_ZONE_ROOT):
168 super(SOARecord, self).__init__()
169 self.wType = dnsp.DNS_TYPE_SOA
170 self.dwFlags = rank | node_flag
171 self.dwSerial = serial
172 self.dwTtlSeconds = ttl
173 soa = dnsserver.DNS_RPC_RECORD_SOA()
174 soa.dwSerialNo = serial
175 soa.dwRefresh = refresh
176 soa.dwRetry = retry
177 soa.dwExpire = expire
178 soa.dwMinimumTtl = minimum
179 soa.NamePrimaryServer.str = mname
180 soa.NamePrimaryServer.len = len(mname)
181 soa.ZoneAdministratorEmail.str = rname
182 soa.ZoneAdministratorEmail.len = len(rname)
183 self.data = soa
185 @classmethod
186 def from_string(cls, data, sep=None, **kwargs):
187 args = data.split(sep)
188 if len(args) != 7:
189 raise DNSParseError('Data requires 7 space separated elements - '
190 'nameserver, email, serial, '
191 'refresh, retry, expire, minimumttl')
192 try:
193 for i in range(2, 7):
194 args[i] = int(args[i])
195 except ValueError as e:
196 raise DNSParseError("SOA serial, refresh, retry, expire, minimumttl' "
197 "should be integers") from e
198 return cls(*args, **kwargs)
201 class SRVRecord(dnsserver.DNS_RPC_RECORD):
203 def __init__(self, target, port, priority=0, weight=100, serial=1, ttl=900,
204 rank=dnsp.DNS_RANK_ZONE, node_flag=0):
205 super(SRVRecord, self).__init__()
206 self.wType = dnsp.DNS_TYPE_SRV
207 self.dwFlags = rank | node_flag
208 self.dwSerial = serial
209 self.dwTtlSeconds = ttl
210 srv = dnsserver.DNS_RPC_RECORD_SRV()
211 srv.wPriority = priority
212 srv.wWeight = weight
213 srv.wPort = port
214 srv.nameTarget.str = target
215 srv.nameTarget.len = len(target)
216 self.data = srv
218 @classmethod
219 def from_string(cls, data, sep=None, **kwargs):
220 try:
221 target, port, priority, weight = data.split(sep)
222 except ValueError as e:
223 raise DNSParseError("SRV data must have four space "
224 "separated elements: "
225 "server, port, priority, weight; "
226 "not %r" % data) from e
227 try:
228 args = (target, int(port), int(priority), int(weight))
229 except ValueError as e:
230 raise DNSParseError("SRV port, priority, and weight "
231 "must be integers") from e
233 return cls(*args, **kwargs)
236 class TXTRecord(dnsserver.DNS_RPC_RECORD):
238 def __init__(self, slist, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
239 node_flag=0):
240 super(TXTRecord, self).__init__()
241 self.wType = dnsp.DNS_TYPE_TXT
242 self.dwFlags = rank | node_flag
243 self.dwSerial = serial
244 self.dwTtlSeconds = ttl
245 if isinstance(slist, str):
246 slist = [slist]
247 names = []
248 for s in slist:
249 name = dnsserver.DNS_RPC_NAME()
250 name.str = s
251 name.len = len(s)
252 names.append(name)
253 txt = dnsserver.DNS_RPC_RECORD_STRING()
254 txt.count = len(slist)
255 txt.str = names
256 self.data = txt
258 @classmethod
259 def from_string(cls, data, sep=None, **kwargs):
260 slist = shlex.split(data)
261 return cls(slist, **kwargs)
265 # Don't add new Record types after this line
267 _RECORD_TYPE_LUT = {}
268 def _setup_record_type_lut():
269 for k, v in globals().items():
270 if k[-6:] == 'Record':
271 k = k[:-6]
272 flag = getattr(dnsp, 'DNS_TYPE_' + k)
273 _RECORD_TYPE_LUT[k] = v
274 _RECORD_TYPE_LUT[flag] = v
276 _setup_record_type_lut()
277 del _setup_record_type_lut
280 def record_from_string(t, data, sep=None, **kwargs):
281 """Get a DNS record of type t based on the data string.
282 Additional keywords (ttl, rank, etc) can be passed in.
284 t can be a dnsp.DNS_TYPE_* integer or a string like "A", "TXT", etc.
286 if isinstance(t, str):
287 t = t.upper()
288 try:
289 Record = _RECORD_TYPE_LUT[t]
290 except KeyError as e:
291 raise DNSParseError("Unsupported record type") from e
293 return Record.from_string(data, sep=sep, **kwargs)
296 def flag_from_string(rec_type):
297 rtype = rec_type.upper()
298 try:
299 return getattr(dnsp, 'DNS_TYPE_' + rtype)
300 except AttributeError as e:
301 raise DNSParseError('Unknown type of DNS record %s' % rec_type) from e
304 def recbuf_from_string(*args, **kwargs):
305 rec = record_from_string(*args, **kwargs)
306 buf = dnsserver.DNS_RPC_RECORD_BUF()
307 buf.rec = rec
308 return buf
311 def dns_name_equal(n1, n2):
312 """Match dns name (of type DNS_RPC_NAME)"""
313 return n1.str.rstrip('.').lower() == n2.str.rstrip('.').lower()
316 def ipv6_normalise(addr):
317 """Convert an AAAA address into a canonical form."""
318 packed = socket.inet_pton(socket.AF_INET6, addr)
319 return socket.inet_ntop(socket.AF_INET6, packed)
322 def dns_record_match(dns_conn, server, zone, name, record_type, data):
323 """Find a dns record that matches the specified data"""
325 # The matching is not as precises as that offered by
326 # dsdb_dns.match_record, which, for example, compares IPv6 records
327 # semantically rather than as strings. However that function
328 # compares database DnssrvRpcRecord structures, not wire
329 # DNS_RPC_RECORD structures.
331 # While it would be possible, perhaps desirable, to wrap that
332 # function for use in samba-tool, there is value in having a
333 # separate implementation for tests, to avoid the circularity of
334 # asserting the function matches itself.
336 urec = record_from_string(record_type, data)
338 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
340 try:
341 buflen, res = dns_conn.DnssrvEnumRecords2(
342 dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, zone, name, None,
343 record_type, select_flags, None, None)
344 except WERRORError as e:
345 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
346 # Either the zone doesn't exist, or there were no records.
347 # We can't differentiate the two.
348 return None
349 raise e
351 if not res or res.count == 0:
352 return None
354 for rec in res.rec[0].records:
355 if rec.wType != record_type:
356 continue
358 found = False
359 if record_type == dnsp.DNS_TYPE_A:
360 if rec.data == urec.data:
361 found = True
362 elif record_type == dnsp.DNS_TYPE_AAAA:
363 if ipv6_normalise(rec.data) == ipv6_normalise(urec.data):
364 found = True
365 elif record_type == dnsp.DNS_TYPE_PTR:
366 if dns_name_equal(rec.data, urec.data):
367 found = True
368 elif record_type == dnsp.DNS_TYPE_CNAME:
369 if dns_name_equal(rec.data, urec.data):
370 found = True
371 elif record_type == dnsp.DNS_TYPE_NS:
372 if dns_name_equal(rec.data, urec.data):
373 found = True
374 elif record_type == dnsp.DNS_TYPE_MX:
375 if dns_name_equal(rec.data.nameExchange, urec.data.nameExchange) and \
376 rec.data.wPreference == urec.data.wPreference:
377 found = True
378 elif record_type == dnsp.DNS_TYPE_SRV:
379 if rec.data.wPriority == urec.data.wPriority and \
380 rec.data.wWeight == urec.data.wWeight and \
381 rec.data.wPort == urec.data.wPort and \
382 dns_name_equal(rec.data.nameTarget, urec.data.nameTarget):
383 found = True
384 elif record_type == dnsp.DNS_TYPE_SOA:
385 if rec.data.dwSerialNo == urec.data.dwSerialNo and \
386 rec.data.dwRefresh == urec.data.dwRefresh and \
387 rec.data.dwRetry == urec.data.dwRetry and \
388 rec.data.dwExpire == urec.data.dwExpire and \
389 rec.data.dwMinimumTtl == urec.data.dwMinimumTtl and \
390 dns_name_equal(rec.data.NamePrimaryServer,
391 urec.data.NamePrimaryServer) and \
392 dns_name_equal(rec.data.ZoneAdministratorEmail,
393 urec.data.ZoneAdministratorEmail):
394 found = True
395 elif record_type == dnsp.DNS_TYPE_TXT:
396 if rec.data.count == urec.data.count:
397 found = True
398 for i in range(rec.data.count):
399 found = found and \
400 (rec.data.str[i].str == urec.data.str[i].str)
402 if found:
403 return rec
405 return None