ctdb-scripts: Improve update and listing code
[samba4-gss.git] / python / samba / netcmd / dns.py
blob693fc9aa65f3d2ec24048359d22a150452503598
1 # 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/>.
18 import samba.getopt as options
19 from samba import WERRORError
20 from samba import werror
21 from struct import pack
22 from socket import inet_ntop, inet_pton
23 from socket import AF_INET
24 from socket import AF_INET6
25 import struct
26 import time
27 import ldb
28 from samba.ndr import ndr_unpack, ndr_pack
29 import re
31 from samba import remove_dc, dsdb_dns
32 from samba.samdb import SamDB
33 from samba.auth import system_session
35 from samba.netcmd import (
36 Command,
37 CommandError,
38 Option,
39 SuperCommand,
41 from samba.dcerpc import dnsp, dnsserver
43 from samba.dnsserver import record_from_string, DNSParseError, flag_from_string
44 from samba.dnsserver import dns_record_match
47 def dns_connect(server, lp, creds):
48 if server.lower() == 'localhost':
49 server = '127.0.0.1'
50 binding_str = "ncacn_ip_tcp:%s[sign]" % server
51 try:
52 dns_conn = dnsserver.dnsserver(binding_str, lp, creds)
53 except RuntimeError as e:
54 raise CommandError('Connecting to DNS RPC server %s failed with %s' % (server, e))
56 return dns_conn
59 class DnsConnWrapper:
60 """A wrapper around a dnsserver.dnsserver connection that makes it
61 harder not to report friendly messages.
63 If, rather than
65 dns_conn = dns_connect(server, lp, creds)
67 you use
69 dns_conn = DnsConnWrapper(server, lp, creds)
71 then various common errors (for example, misspelled zones) on
72 common operations will raise CommandErrors that turn into
73 relatively nice messages (when compared to tracebacks).
75 In addition, if you provide a messages keyword argument, it will
76 override the defaults. Note that providing None will turn off the
77 default, letting the original exception shine through.
79 messages = {
80 werror.WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST: (
81 f'Zone {zone} does not exist and so could not be deleted.'),
82 werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: None
84 res = dns_conn.DnssrvOperation2( # ...
85 messages=messages)
87 This example changes the message for ZONE_DOES_NOT_EXIST and
88 avoids catching NAME_DOES_NOT_EXIST.
90 Only WERRORErrors are intercepted.
91 """
93 default_messages = {
94 werror.WERR_DNS_ERROR_DS_UNAVAILABLE: "Could not contact RPC server",
95 werror.WERR_DNS_ERROR_ZONE_ALREADY_EXISTS: 'Zone already exists',
96 werror.WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST: 'The record does not exist',
97 werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: 'The zone does not exist',
98 werror.WERR_ACCESS_DENIED: 'Insufficient permissions',
101 def __init__(self, server, lp, creds):
102 self.dns_conn = dns_connect(server, lp, creds)
104 def __getattr__(self, name):
105 attr = getattr(self.dns_conn, name)
106 if name not in {
107 "DnssrvComplexOperation2",
108 "DnssrvEnumRecords2",
109 "DnssrvOperation2",
110 "DnssrvQuery2",
111 "DnssrvUpdateRecord2"}:
112 return attr
114 def f(*args, messages=None):
115 if messages is None:
116 messages = {}
118 try:
119 return attr(*args)
120 except WERRORError as e:
121 werr, errstr = e.args
122 if werr in messages:
123 if werr is None:
124 # None overrides a default message, leaving the bare exception
125 raise
126 raise CommandError(f"{messages[werr]} [{errstr}]", e)
127 if werr in self.default_messages:
128 raise CommandError(f"{self.default_messages[werr]} [{errstr}]", e)
129 raise
131 return f
134 def bool_string(flag):
135 if flag == 0:
136 ret = 'FALSE'
137 elif flag == 1:
138 ret = 'TRUE'
139 else:
140 ret = 'UNKNOWN (0x%x)' % flag
141 return ret
144 def enum_string(module, enum_defs, value):
145 ret = None
146 for e in enum_defs:
147 if value == getattr(module, e):
148 ret = e
149 break
150 if not ret:
151 ret = 'UNKNOWN (0x%x)' % value
152 return ret
155 def bitmap_string(module, bitmap_defs, value):
156 ret = ''
157 for b in bitmap_defs:
158 if value & getattr(module, b):
159 ret += '%s ' % b
160 if not ret:
161 ret = 'NONE'
162 return ret
165 def boot_method_string(boot_method):
166 enum_defs = ['DNS_BOOT_METHOD_UNINITIALIZED', 'DNS_BOOT_METHOD_FILE',
167 'DNS_BOOT_METHOD_REGISTRY', 'DNS_BOOT_METHOD_DIRECTORY']
168 return enum_string(dnsserver, enum_defs, boot_method)
171 def name_check_flag_string(check_flag):
172 enum_defs = ['DNS_ALLOW_RFC_NAMES_ONLY', 'DNS_ALLOW_NONRFC_NAMES',
173 'DNS_ALLOW_MULTIBYTE_NAMES', 'DNS_ALLOW_ALL_NAMES']
174 return enum_string(dnsserver, enum_defs, check_flag)
177 def zone_type_string(zone_type):
178 enum_defs = ['DNS_ZONE_TYPE_CACHE', 'DNS_ZONE_TYPE_PRIMARY',
179 'DNS_ZONE_TYPE_SECONDARY', 'DNS_ZONE_TYPE_STUB',
180 'DNS_ZONE_TYPE_FORWARDER', 'DNS_ZONE_TYPE_SECONDARY_CACHE']
181 return enum_string(dnsp, enum_defs, zone_type)
184 def zone_update_string(zone_update):
185 enum_defs = ['DNS_ZONE_UPDATE_OFF', 'DNS_ZONE_UPDATE_UNSECURE',
186 'DNS_ZONE_UPDATE_SECURE']
187 return enum_string(dnsp, enum_defs, zone_update)
190 def zone_secondary_security_string(security):
191 enum_defs = ['DNS_ZONE_SECSECURE_NO_SECURITY', 'DNS_ZONE_SECSECURE_NS_ONLY',
192 'DNS_ZONE_SECSECURE_LIST_ONLY', 'DNS_ZONE_SECSECURE_NO_XFER']
193 return enum_string(dnsserver, enum_defs, security)
196 def zone_notify_level_string(notify_level):
197 enum_defs = ['DNS_ZONE_NOTIFY_OFF', 'DNS_ZONE_NOTIFY_ALL_SECONDARIES',
198 'DNS_ZONE_NOTIFY_LIST_ONLY']
199 return enum_string(dnsserver, enum_defs, notify_level)
202 def dp_flags_string(dp_flags):
203 bitmap_defs = ['DNS_DP_AUTOCREATED', 'DNS_DP_LEGACY', 'DNS_DP_DOMAIN_DEFAULT',
204 'DNS_DP_FOREST_DEFAULT', 'DNS_DP_ENLISTED', 'DNS_DP_DELETED']
205 return bitmap_string(dnsserver, bitmap_defs, dp_flags)
208 def zone_flags_string(flags):
209 bitmap_defs = ['DNS_RPC_ZONE_PAUSED', 'DNS_RPC_ZONE_SHUTDOWN',
210 'DNS_RPC_ZONE_REVERSE', 'DNS_RPC_ZONE_AUTOCREATED',
211 'DNS_RPC_ZONE_DSINTEGRATED', 'DNS_RPC_ZONE_AGING',
212 'DNS_RPC_ZONE_UPDATE_UNSECURE', 'DNS_RPC_ZONE_UPDATE_SECURE',
213 'DNS_RPC_ZONE_READONLY']
214 return bitmap_string(dnsserver, bitmap_defs, flags)
217 def ip4_array_string(array):
218 ret = []
219 if not array:
220 return ret
221 for i in range(array.AddrCount):
222 addr = inet_ntop(AF_INET, pack('I', array.AddrArray[i]))
223 ret.append(addr)
224 return ret
227 def dns_addr_array_string(array):
228 ret = []
229 if not array:
230 return ret
231 for i in range(array.AddrCount):
232 if array.AddrArray[i].MaxSa[0] == 0x02:
233 x = struct.pack('4B', *array.AddrArray[i].MaxSa[4:8])
234 addr = inet_ntop(AF_INET, x)
235 elif array.AddrArray[i].MaxSa[0] == 0x17:
236 x = struct.pack('16B', *array.AddrArray[i].MaxSa[8:24])
237 addr = inet_ntop(AF_INET6, x)
238 else:
239 addr = 'UNKNOWN'
240 ret.append(addr)
241 return ret
244 def dns_type_flag(rec_type):
245 try:
246 return flag_from_string(rec_type)
247 except DNSParseError as e:
248 raise CommandError(*e.args)
251 def dns_client_version(cli_version):
252 version = cli_version.upper()
253 if version == 'W2K':
254 client_version = dnsserver.DNS_CLIENT_VERSION_W2K
255 elif version == 'DOTNET':
256 client_version = dnsserver.DNS_CLIENT_VERSION_DOTNET
257 elif version == 'LONGHORN':
258 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
259 else:
260 raise CommandError('Unknown client version %s' % cli_version)
261 return client_version
264 def print_serverinfo(outf, typeid, serverinfo):
265 outf.write(' dwVersion : 0x%x\n' % serverinfo.dwVersion)
266 outf.write(' fBootMethod : %s\n' % boot_method_string(serverinfo.fBootMethod))
267 outf.write(' fAdminConfigured : %s\n' % bool_string(serverinfo.fAdminConfigured))
268 outf.write(' fAllowUpdate : %s\n' % bool_string(serverinfo.fAllowUpdate))
269 outf.write(' fDsAvailable : %s\n' % bool_string(serverinfo.fDsAvailable))
270 outf.write(' pszServerName : %s\n' % serverinfo.pszServerName)
271 outf.write(' pszDsContainer : %s\n' % serverinfo.pszDsContainer)
273 if typeid != dnsserver.DNSSRV_TYPEID_SERVER_INFO:
274 outf.write(' aipServerAddrs : %s\n' %
275 ip4_array_string(serverinfo.aipServerAddrs))
276 outf.write(' aipListenAddrs : %s\n' %
277 ip4_array_string(serverinfo.aipListenAddrs))
278 outf.write(' aipForwarders : %s\n' %
279 ip4_array_string(serverinfo.aipForwarders))
280 else:
281 outf.write(' aipServerAddrs : %s\n' %
282 dns_addr_array_string(serverinfo.aipServerAddrs))
283 outf.write(' aipListenAddrs : %s\n' %
284 dns_addr_array_string(serverinfo.aipListenAddrs))
285 outf.write(' aipForwarders : %s\n' %
286 dns_addr_array_string(serverinfo.aipForwarders))
288 outf.write(' dwLogLevel : %d\n' % serverinfo.dwLogLevel)
289 outf.write(' dwDebugLevel : %d\n' % serverinfo.dwDebugLevel)
290 outf.write(' dwForwardTimeout : %d\n' % serverinfo.dwForwardTimeout)
291 outf.write(' dwRpcPrototol : 0x%x\n' % serverinfo.dwRpcProtocol)
292 outf.write(' dwNameCheckFlag : %s\n' % name_check_flag_string(serverinfo.dwNameCheckFlag))
293 outf.write(' cAddressAnswerLimit : %d\n' % serverinfo.cAddressAnswerLimit)
294 outf.write(' dwRecursionRetry : %d\n' % serverinfo.dwRecursionRetry)
295 outf.write(' dwRecursionTimeout : %d\n' % serverinfo.dwRecursionTimeout)
296 outf.write(' dwMaxCacheTtl : %d\n' % serverinfo.dwMaxCacheTtl)
297 outf.write(' dwDsPollingInterval : %d\n' % serverinfo.dwDsPollingInterval)
298 outf.write(' dwScavengingInterval : %d\n' % serverinfo.dwScavengingInterval)
299 outf.write(' dwDefaultRefreshInterval : %d\n' % serverinfo.dwDefaultRefreshInterval)
300 outf.write(' dwDefaultNoRefreshInterval : %d\n' % serverinfo.dwDefaultNoRefreshInterval)
301 outf.write(' fAutoReverseZones : %s\n' % bool_string(serverinfo.fAutoReverseZones))
302 outf.write(' fAutoCacheUpdate : %s\n' % bool_string(serverinfo.fAutoCacheUpdate))
303 outf.write(' fRecurseAfterForwarding : %s\n' % bool_string(serverinfo.fRecurseAfterForwarding))
304 outf.write(' fForwardDelegations : %s\n' % bool_string(serverinfo.fForwardDelegations))
305 outf.write(' fNoRecursion : %s\n' % bool_string(serverinfo.fNoRecursion))
306 outf.write(' fSecureResponses : %s\n' % bool_string(serverinfo.fSecureResponses))
307 outf.write(' fRoundRobin : %s\n' % bool_string(serverinfo.fRoundRobin))
308 outf.write(' fLocalNetPriority : %s\n' % bool_string(serverinfo.fLocalNetPriority))
309 outf.write(' fBindSecondaries : %s\n' % bool_string(serverinfo.fBindSecondaries))
310 outf.write(' fWriteAuthorityNs : %s\n' % bool_string(serverinfo.fWriteAuthorityNs))
311 outf.write(' fStrictFileParsing : %s\n' % bool_string(serverinfo.fStrictFileParsing))
312 outf.write(' fLooseWildcarding : %s\n' % bool_string(serverinfo.fLooseWildcarding))
313 outf.write(' fDefaultAgingState : %s\n' % bool_string(serverinfo.fDefaultAgingState))
315 if typeid != dnsserver.DNSSRV_TYPEID_SERVER_INFO_W2K:
316 outf.write(' dwRpcStructureVersion : 0x%x\n' % serverinfo.dwRpcStructureVersion)
317 outf.write(' aipLogFilter : %s\n' % dns_addr_array_string(serverinfo.aipLogFilter))
318 outf.write(' pwszLogFilePath : %s\n' % serverinfo.pwszLogFilePath)
319 outf.write(' pszDomainName : %s\n' % serverinfo.pszDomainName)
320 outf.write(' pszForestName : %s\n' % serverinfo.pszForestName)
321 outf.write(' pszDomainDirectoryPartition : %s\n' % serverinfo.pszDomainDirectoryPartition)
322 outf.write(' pszForestDirectoryPartition : %s\n' % serverinfo.pszForestDirectoryPartition)
324 outf.write(' dwLocalNetPriorityNetMask : 0x%x\n' % serverinfo.dwLocalNetPriorityNetMask)
325 outf.write(' dwLastScavengeTime : %d\n' % serverinfo.dwLastScavengeTime)
326 outf.write(' dwEventLogLevel : %d\n' % serverinfo.dwEventLogLevel)
327 outf.write(' dwLogFileMaxSize : %d\n' % serverinfo.dwLogFileMaxSize)
328 outf.write(' dwDsForestVersion : %d\n' % serverinfo.dwDsForestVersion)
329 outf.write(' dwDsDomainVersion : %d\n' % serverinfo.dwDsDomainVersion)
330 outf.write(' dwDsDsaVersion : %d\n' % serverinfo.dwDsDsaVersion)
332 if typeid == dnsserver.DNSSRV_TYPEID_SERVER_INFO:
333 outf.write(' fReadOnlyDC : %s\n' % bool_string(serverinfo.fReadOnlyDC))
336 def print_zoneinfo(outf, typeid, zoneinfo):
337 outf.write(' pszZoneName : %s\n' % zoneinfo.pszZoneName)
338 outf.write(' dwZoneType : %s\n' % zone_type_string(zoneinfo.dwZoneType))
339 outf.write(' fReverse : %s\n' % bool_string(zoneinfo.fReverse))
340 outf.write(' fAllowUpdate : %s\n' % zone_update_string(zoneinfo.fAllowUpdate))
341 outf.write(' fPaused : %s\n' % bool_string(zoneinfo.fPaused))
342 outf.write(' fShutdown : %s\n' % bool_string(zoneinfo.fShutdown))
343 outf.write(' fAutoCreated : %s\n' % bool_string(zoneinfo.fAutoCreated))
344 outf.write(' fUseDatabase : %s\n' % bool_string(zoneinfo.fUseDatabase))
345 outf.write(' pszDataFile : %s\n' % zoneinfo.pszDataFile)
346 if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
347 outf.write(' aipMasters : %s\n' %
348 ip4_array_string(zoneinfo.aipMasters))
349 else:
350 outf.write(' aipMasters : %s\n' %
351 dns_addr_array_string(zoneinfo.aipMasters))
352 outf.write(' fSecureSecondaries : %s\n' % zone_secondary_security_string(zoneinfo.fSecureSecondaries))
353 outf.write(' fNotifyLevel : %s\n' % zone_notify_level_string(zoneinfo.fNotifyLevel))
354 if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
355 outf.write(' aipSecondaries : %s\n' %
356 ip4_array_string(zoneinfo.aipSecondaries))
357 outf.write(' aipNotify : %s\n' %
358 ip4_array_string(zoneinfo.aipNotify))
359 else:
360 outf.write(' aipSecondaries : %s\n' %
361 dns_addr_array_string(zoneinfo.aipSecondaries))
362 outf.write(' aipNotify : %s\n' %
363 dns_addr_array_string(zoneinfo.aipNotify))
364 outf.write(' fUseWins : %s\n' % bool_string(zoneinfo.fUseWins))
365 outf.write(' fUseNbstat : %s\n' % bool_string(zoneinfo.fUseNbstat))
366 outf.write(' fAging : %s\n' % bool_string(zoneinfo.fAging))
367 outf.write(' dwNoRefreshInterval : %d\n' % zoneinfo.dwNoRefreshInterval)
368 outf.write(' dwRefreshInterval : %d\n' % zoneinfo.dwRefreshInterval)
369 outf.write(' dwAvailForScavengeTime : %d\n' % zoneinfo.dwAvailForScavengeTime)
370 if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
371 outf.write(' aipScavengeServers : %s\n' %
372 ip4_array_string(zoneinfo.aipScavengeServers))
373 else:
374 outf.write(' aipScavengeServers : %s\n' %
375 dns_addr_array_string(zoneinfo.aipScavengeServers))
377 if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO_W2K:
378 outf.write(' dwRpcStructureVersion : 0x%x\n' % zoneinfo.dwRpcStructureVersion)
379 outf.write(' dwForwarderTimeout : %d\n' % zoneinfo.dwForwarderTimeout)
380 outf.write(' fForwarderSlave : %d\n' % zoneinfo.fForwarderSlave)
381 if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
382 outf.write(' aipLocalMasters : %s\n' %
383 ip4_array_string(zoneinfo.aipLocalMasters))
384 else:
385 outf.write(' aipLocalMasters : %s\n' %
386 dns_addr_array_string(zoneinfo.aipLocalMasters))
387 outf.write(' dwDpFlags : %s\n' % dp_flags_string(zoneinfo.dwDpFlags))
388 outf.write(' pszDpFqdn : %s\n' % zoneinfo.pszDpFqdn)
389 outf.write(' pwszZoneDn : %s\n' % zoneinfo.pwszZoneDn)
390 outf.write(' dwLastSuccessfulSoaCheck : %d\n' % zoneinfo.dwLastSuccessfulSoaCheck)
391 outf.write(' dwLastSuccessfulXfr : %d\n' % zoneinfo.dwLastSuccessfulXfr)
393 if typeid == dnsserver.DNSSRV_TYPEID_ZONE_INFO:
394 outf.write(' fQueuedForBackgroundLoad : %s\n' % bool_string(zoneinfo.fQueuedForBackgroundLoad))
395 outf.write(' fBackgroundLoadInProgress : %s\n' % bool_string(zoneinfo.fBackgroundLoadInProgress))
396 outf.write(' fReadOnlyZone : %s\n' % bool_string(zoneinfo.fReadOnlyZone))
397 outf.write(' dwLastXfrAttempt : %d\n' % zoneinfo.dwLastXfrAttempt)
398 outf.write(' dwLastXfrResult : %d\n' % zoneinfo.dwLastXfrResult)
401 def print_zone(outf, typeid, zone):
402 outf.write(' pszZoneName : %s\n' % zone.pszZoneName)
403 outf.write(' Flags : %s\n' % zone_flags_string(zone.Flags))
404 outf.write(' ZoneType : %s\n' % zone_type_string(zone.ZoneType))
405 outf.write(' Version : %s\n' % zone.Version)
407 if typeid != dnsserver.DNSSRV_TYPEID_ZONE_W2K:
408 outf.write(' dwDpFlags : %s\n' % dp_flags_string(zone.dwDpFlags))
409 outf.write(' pszDpFqdn : %s\n' % zone.pszDpFqdn)
412 def print_enumzones(outf, typeid, zones):
413 outf.write(' %d zone(s) found\n' % zones.dwZoneCount)
414 for zone in zones.ZoneArray:
415 outf.write('\n')
416 print_zone(outf, typeid, zone)
419 def print_dns_record(outf, rec):
420 if rec.wType == dnsp.DNS_TYPE_A:
421 mesg = 'A: %s' % (rec.data)
422 elif rec.wType == dnsp.DNS_TYPE_AAAA:
423 mesg = 'AAAA: %s' % (rec.data)
424 elif rec.wType == dnsp.DNS_TYPE_PTR:
425 mesg = 'PTR: %s' % (rec.data.str)
426 elif rec.wType == dnsp.DNS_TYPE_NS:
427 mesg = 'NS: %s' % (rec.data.str)
428 elif rec.wType == dnsp.DNS_TYPE_CNAME:
429 mesg = 'CNAME: %s' % (rec.data.str)
430 elif rec.wType == dnsp.DNS_TYPE_SOA:
431 mesg = 'SOA: serial=%d, refresh=%d, retry=%d, expire=%d, minttl=%d, ns=%s, email=%s' % (
432 rec.data.dwSerialNo,
433 rec.data.dwRefresh,
434 rec.data.dwRetry,
435 rec.data.dwExpire,
436 rec.data.dwMinimumTtl,
437 rec.data.NamePrimaryServer.str,
438 rec.data.ZoneAdministratorEmail.str)
439 elif rec.wType == dnsp.DNS_TYPE_MX:
440 mesg = 'MX: %s (%d)' % (rec.data.nameExchange.str, rec.data.wPreference)
441 elif rec.wType == dnsp.DNS_TYPE_SRV:
442 mesg = 'SRV: %s (%d, %d, %d)' % (rec.data.nameTarget.str, rec.data.wPort,
443 rec.data.wPriority, rec.data.wWeight)
444 elif rec.wType == dnsp.DNS_TYPE_TXT:
445 slist = ['"%s"' % name.str for name in rec.data.str]
446 mesg = 'TXT: %s' % ','.join(slist)
447 else:
448 mesg = 'Unknown: '
449 outf.write(' %s (flags=%x, serial=%d, ttl=%d)\n' % (
450 mesg, rec.dwFlags, rec.dwSerial, rec.dwTtlSeconds))
453 def print_dnsrecords(outf, records):
454 for rec in records.rec:
455 outf.write(' Name=%s, Records=%d, Children=%d\n' % (
456 rec.dnsNodeName.str,
457 rec.wRecordCount,
458 rec.dwChildCount))
459 for dns_rec in rec.records:
460 print_dns_record(outf, dns_rec)
463 # Convert data into a dns record
464 def data_to_dns_record(record_type, data):
465 try:
466 rec = record_from_string(record_type, data)
467 except DNSParseError as e:
468 raise CommandError(*e.args) from None
470 return rec
473 class cmd_serverinfo(Command):
474 """Query for Server information."""
476 synopsis = '%prog <server> [options]'
478 takes_args = ['server']
480 takes_optiongroups = {
481 "sambaopts": options.SambaOptions,
482 "versionopts": options.VersionOptions,
483 "credopts": options.CredentialsOptions,
486 takes_options = [
487 Option('--client-version', help='Client Version',
488 default='longhorn', metavar='w2k|dotnet|longhorn',
489 choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
492 def run(self, server, cli_ver, sambaopts=None, credopts=None,
493 versionopts=None):
494 self.lp = sambaopts.get_loadparm()
495 self.creds = credopts.get_credentials(self.lp)
496 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
498 client_version = dns_client_version(cli_ver)
500 typeid, res = dns_conn.DnssrvQuery2(client_version, 0, server,
501 None, 'ServerInfo')
502 print_serverinfo(self.outf, typeid, res)
505 def _add_integer_options(table, takes_options, integer_properties):
506 """Generate options for cmd_zoneoptions"""
507 for k, doc, _min, _max in table:
508 o = '--' + k.lower()
509 opt = Option(o,
510 help=f"{doc} [{_min}-{_max}]",
511 type="int",
512 dest=k)
513 takes_options.append(opt)
514 integer_properties.append((k, _min, _max, o))
517 class cmd_zoneoptions(Command):
518 """Change zone aging options."""
520 synopsis = '%prog <server> <zone> [options]'
522 takes_args = ['server', 'zone']
524 takes_optiongroups = {
525 "sambaopts": options.SambaOptions,
526 "versionopts": options.VersionOptions,
527 "credopts": options.CredentialsOptions,
530 takes_options = [
531 Option('--client-version', help='Client Version',
532 default='longhorn', metavar='w2k|dotnet|longhorn',
533 choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
534 Option('--mark-old-records-static', metavar="YYYY-MM-DD",
535 help="Make records older than this (YYYY-MM-DD) static"),
536 Option('--mark-records-static-regex', metavar="REGEXP",
537 help="Make records matching this regular expression static"),
538 Option('--mark-records-dynamic-regex', metavar="REGEXP",
539 help="Make records matching this regular expression dynamic"),
540 Option('-n', '--dry-run', action='store_true',
541 help="Don't change anything, say what would happen"),
544 integer_properties = []
545 # Any zone parameter that is stored as an integer (which is most of
546 # them) can be added to this table. The name should be the dnsp
547 # mixed case name, which will get munged into a lowercase name for
548 # the option. (e.g. "Aging" becomes "--aging").
550 # Note: just because we add a name here doesn't mean we will use
551 # it.
552 _add_integer_options([
553 # ( name, help-string, min, max )
554 ('Aging', 'Enable record aging', 0, 1),
555 ('NoRefreshInterval',
556 'Aging no refresh interval in hours (0: use default)',
557 0, 10 * 365 * 24),
558 ('RefreshInterval',
559 'Aging refresh interval in hours (0: use default)',
560 0, 10 * 365 * 24),
562 takes_options,
563 integer_properties)
565 def run(self, server, zone, cli_ver, sambaopts=None, credopts=None,
566 versionopts=None, dry_run=False,
567 mark_old_records_static=None,
568 mark_records_static_regex=None,
569 mark_records_dynamic_regex=None,
570 **kwargs):
571 self.lp = sambaopts.get_loadparm()
572 self.creds = credopts.get_credentials(self.lp)
573 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
575 client_version = dns_client_version(cli_ver)
576 nap_type = dnsserver.DNSSRV_TYPEID_NAME_AND_PARAM
578 for k, _min, _max, o in self.integer_properties:
579 if kwargs.get(k) is None:
580 continue
581 v = kwargs[k]
582 if _min is not None and v < _min:
583 raise CommandError(f"{o} must be at least {_min}")
584 if _max is not None and v > _max:
585 raise CommandError(f"{o} can't exceed {_max}")
587 name_param = dnsserver.DNS_RPC_NAME_AND_PARAM()
588 name_param.dwParam = v
589 name_param.pszNodeName = k
590 if dry_run:
591 print(f"would set {k} to {v} for {zone}", file=self.outf)
592 continue
593 try:
594 dns_conn.DnssrvOperation2(client_version,
596 server,
597 zone,
599 'ResetDwordProperty',
600 nap_type,
601 name_param)
602 except WERRORError as e:
603 raise CommandError(f"Could not set {k} to {v}") from None
605 print(f"Set {k} to {v}", file=self.outf)
607 # We don't want to allow more than one of these --mark-*
608 # options at a time, as they are sensitive to ordering and
609 # the order is not documented.
610 n_mark_options = 0
611 for x in (mark_old_records_static,
612 mark_records_static_regex,
613 mark_records_dynamic_regex):
614 if x is not None:
615 n_mark_options += 1
617 if n_mark_options > 1:
618 raise CommandError("Multiple --mark-* options will not work\n")
620 if mark_old_records_static is not None:
621 self.mark_old_records_static(server, zone,
622 mark_old_records_static,
623 dry_run)
625 if mark_records_static_regex is not None:
626 self.mark_records_static_regex(server,
627 zone,
628 mark_records_static_regex,
629 dry_run)
631 if mark_records_dynamic_regex is not None:
632 self.mark_records_dynamic_regex(server,
633 zone,
634 mark_records_dynamic_regex,
635 dry_run)
638 def _get_dns_nodes(self, server, zone_name):
639 samdb = SamDB(url="ldap://%s" % server,
640 session_info=system_session(),
641 credentials=self.creds, lp=self.lp)
643 zone_dn = (f"DC={zone_name},CN=MicrosoftDNS,DC=DomainDNSZones,"
644 f"{samdb.get_default_basedn()}")
646 nodes = samdb.search(base=zone_dn,
647 scope=ldb.SCOPE_SUBTREE,
648 expression=("(&(objectClass=dnsNode)"
649 "(!(dNSTombstoned=TRUE)))"),
650 attrs=["dnsRecord", "name"])
651 return samdb, nodes
653 def mark_old_records_static(self, server, zone_name, date_string, dry_run):
654 try:
655 ts = time.strptime(date_string, "%Y-%m-%d")
656 t = time.mktime(ts)
657 except ValueError as e:
658 raise CommandError(f"Invalid date {date_string}: should be YYY-MM-DD")
659 threshold = dsdb_dns.unix_to_dns_timestamp(int(t))
661 samdb, nodes = self._get_dns_nodes(server, zone_name)
663 for node in nodes:
664 if "dnsRecord" not in node:
665 continue
667 values = list(node["dnsRecord"])
668 changes = 0
669 for i, v in enumerate(values):
670 rec = ndr_unpack(dnsp.DnssrvRpcRecord, v)
671 if rec.dwTimeStamp < threshold and rec.dwTimeStamp != 0:
672 rec.dwTimeStamp = 0
673 values[i] = ndr_pack(rec)
674 changes += 1
676 if changes == 0:
677 continue
679 name = node["name"][0].decode()
681 if dry_run:
682 print(f"would make {changes}/{len(values)} records static "
683 f"on {name}.{zone_name}.", file=self.outf)
684 continue
686 msg = ldb.Message.from_dict(samdb,
687 {'dn': node.dn,
688 'dnsRecord': values
690 ldb.FLAG_MOD_REPLACE)
691 samdb.modify(msg)
692 print(f"made {changes}/{len(values)} records static on "
693 f"{name}.{zone_name}.", file=self.outf)
695 def mark_records_static_regex(self, server, zone_name, regex, dry_run):
696 """Make the records of nodes with matching names static.
698 r = re.compile(regex)
699 samdb, nodes = self._get_dns_nodes(server, zone_name)
701 for node in nodes:
702 name = node["name"][0].decode()
703 if not r.search(name):
704 continue
705 if "dnsRecord" not in node:
706 continue
708 values = list(node["dnsRecord"])
709 if len(values) == 0:
710 continue
712 changes = 0
713 for i, v in enumerate(values):
714 rec = ndr_unpack(dnsp.DnssrvRpcRecord, v)
715 if rec.dwTimeStamp != 0:
716 rec.dwTimeStamp = 0
717 values[i] = ndr_pack(rec)
718 changes += 1
720 if changes == 0:
721 continue
723 if dry_run:
724 print(f"would make {changes}/{len(values)} records static "
725 f"on {name}.{zone_name}.", file=self.outf)
726 continue
728 msg = ldb.Message.from_dict(samdb,
729 {'dn': node.dn,
730 'dnsRecord': values
732 ldb.FLAG_MOD_REPLACE)
733 samdb.modify(msg)
734 print(f"made {changes}/{len(values)} records static on "
735 f"{name}.{zone_name}.", file=self.outf)
737 def mark_records_dynamic_regex(self, server, zone_name, regex, dry_run):
738 """Make the records of nodes with matching names dynamic, with a
739 current timestamp. In this case we only adjust the A, AAAA,
740 and TXT records.
742 r = re.compile(regex)
743 samdb, nodes = self._get_dns_nodes(server, zone_name)
744 now = time.time()
745 dns_timestamp = dsdb_dns.unix_to_dns_timestamp(int(now))
746 safe_wtypes = {
747 dnsp.DNS_TYPE_A,
748 dnsp.DNS_TYPE_AAAA,
749 dnsp.DNS_TYPE_TXT
752 for node in nodes:
753 name = node["name"][0].decode()
754 if not r.search(name):
755 continue
756 if "dnsRecord" not in node:
757 continue
759 values = list(node["dnsRecord"])
760 if len(values) == 0:
761 continue
763 changes = 0
764 for i, v in enumerate(values):
765 rec = ndr_unpack(dnsp.DnssrvRpcRecord, v)
766 if rec.wType in safe_wtypes and rec.dwTimeStamp == 0:
767 rec.dwTimeStamp = dns_timestamp
768 values[i] = ndr_pack(rec)
769 changes += 1
771 if changes == 0:
772 continue
774 if dry_run:
775 print(f"would make {changes}/{len(values)} records dynamic "
776 f"on {name}.{zone_name}.", file=self.outf)
777 continue
779 msg = ldb.Message.from_dict(samdb,
780 {'dn': node.dn,
781 'dnsRecord': values
783 ldb.FLAG_MOD_REPLACE)
784 samdb.modify(msg)
785 print(f"made {changes}/{len(values)} records dynamic on "
786 f"{name}.{zone_name}.", file=self.outf)
789 class cmd_zoneinfo(Command):
790 """Query for zone information."""
792 synopsis = '%prog <server> <zone> [options]'
794 takes_args = ['server', 'zone']
796 takes_optiongroups = {
797 "sambaopts": options.SambaOptions,
798 "versionopts": options.VersionOptions,
799 "credopts": options.CredentialsOptions,
802 takes_options = [
803 Option('--client-version', help='Client Version',
804 default='longhorn', metavar='w2k|dotnet|longhorn',
805 choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
808 def run(self, server, zone, cli_ver, sambaopts=None, credopts=None,
809 versionopts=None):
810 self.lp = sambaopts.get_loadparm()
811 self.creds = credopts.get_credentials(self.lp)
812 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
814 client_version = dns_client_version(cli_ver)
816 typeid, res = dns_conn.DnssrvQuery2(client_version, 0, server, zone,
817 'ZoneInfo')
818 print_zoneinfo(self.outf, typeid, res)
821 class cmd_zonelist(Command):
822 """Query for zones."""
824 synopsis = '%prog <server> [options]'
826 takes_args = ['server']
828 takes_optiongroups = {
829 "sambaopts": options.SambaOptions,
830 "versionopts": options.VersionOptions,
831 "credopts": options.CredentialsOptions,
834 takes_options = [
835 Option('--client-version', help='Client Version',
836 default='longhorn', metavar='w2k|dotnet|longhorn',
837 choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
838 Option('--primary', help='List primary zones (default)',
839 action='store_true', dest='primary'),
840 Option('--secondary', help='List secondary zones',
841 action='store_true', dest='secondary'),
842 Option('--cache', help='List cached zones',
843 action='store_true', dest='cache'),
844 Option('--auto', help='List automatically created zones',
845 action='store_true', dest='auto'),
846 Option('--forward', help='List forward zones',
847 action='store_true', dest='forward'),
848 Option('--reverse', help='List reverse zones',
849 action='store_true', dest='reverse'),
850 Option('--ds', help='List directory integrated zones',
851 action='store_true', dest='ds'),
852 Option('--non-ds', help='List non-directory zones',
853 action='store_true', dest='nonds')
856 def run(self, server, cli_ver, primary=False, secondary=False, cache=False,
857 auto=False, forward=False, reverse=False, ds=False, nonds=False,
858 sambaopts=None, credopts=None, versionopts=None):
859 request_filter = 0
861 if primary:
862 request_filter |= dnsserver.DNS_ZONE_REQUEST_PRIMARY
863 if secondary:
864 request_filter |= dnsserver.DNS_ZONE_REQUEST_SECONDARY
865 if cache:
866 request_filter |= dnsserver.DNS_ZONE_REQUEST_CACHE
867 if auto:
868 request_filter |= dnsserver.DNS_ZONE_REQUEST_AUTO
869 if forward:
870 request_filter |= dnsserver.DNS_ZONE_REQUEST_FORWARD
871 if reverse:
872 request_filter |= dnsserver.DNS_ZONE_REQUEST_REVERSE
873 if ds:
874 request_filter |= dnsserver.DNS_ZONE_REQUEST_DS
875 if nonds:
876 request_filter |= dnsserver.DNS_ZONE_REQUEST_NON_DS
878 if request_filter == 0:
879 request_filter = dnsserver.DNS_ZONE_REQUEST_PRIMARY
881 self.lp = sambaopts.get_loadparm()
882 self.creds = credopts.get_credentials(self.lp)
883 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
885 client_version = dns_client_version(cli_ver)
887 typeid, res = dns_conn.DnssrvComplexOperation2(client_version,
888 0, server, None,
889 'EnumZones',
890 dnsserver.DNSSRV_TYPEID_DWORD,
891 request_filter)
893 if client_version == dnsserver.DNS_CLIENT_VERSION_W2K:
894 typeid = dnsserver.DNSSRV_TYPEID_ZONE_W2K
895 else:
896 typeid = dnsserver.DNSSRV_TYPEID_ZONE
897 print_enumzones(self.outf, typeid, res)
900 class cmd_zonecreate(Command):
901 """Create a zone."""
903 synopsis = '%prog <server> <zone> [options]'
905 takes_args = ['server', 'zone']
907 takes_optiongroups = {
908 "sambaopts": options.SambaOptions,
909 "versionopts": options.VersionOptions,
910 "credopts": options.CredentialsOptions,
913 takes_options = [
914 Option('--client-version', help='Client Version',
915 default='longhorn', metavar='w2k|dotnet|longhorn',
916 choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
917 Option('--dns-directory-partition',
918 help='Specify the naming context for the new zone, which '
919 'affects the replication scope (domain or forest wide '
920 'replication, default: domain).',
921 default='domain',
922 metavar='domain|forest',
923 choices=['domain', 'forest'],
924 dest='dns_dp'),
927 def run(self,
928 server,
929 zone,
930 cli_ver,
931 dns_dp,
932 sambaopts=None,
933 credopts=None,
934 versionopts=None):
935 self.lp = sambaopts.get_loadparm()
936 self.creds = credopts.get_credentials(self.lp)
937 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
939 zone = zone.lower()
941 dns_directorypartition = dnsserver.DNS_DP_DOMAIN_DEFAULT
942 if dns_dp == 'forest':
943 dns_directorypartition = dnsserver.DNS_DP_FOREST_DEFAULT
945 client_version = dns_client_version(cli_ver)
946 if client_version == dnsserver.DNS_CLIENT_VERSION_W2K:
947 typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE_W2K
948 zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_W2K()
949 zone_create_info.pszZoneName = zone
950 zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
951 zone_create_info.fAging = 0
952 zone_create_info.fDsIntegrated = 1
953 zone_create_info.fLoadExisting = 1
954 elif client_version == dnsserver.DNS_CLIENT_VERSION_DOTNET:
955 typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE_DOTNET
956 zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_DOTNET()
957 zone_create_info.pszZoneName = zone
958 zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
959 zone_create_info.fAging = 0
960 zone_create_info.fDsIntegrated = 1
961 zone_create_info.fLoadExisting = 1
962 zone_create_info.dwDpFlags = dns_directorypartition
963 else:
964 typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE
965 zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_LONGHORN()
966 zone_create_info.pszZoneName = zone
967 zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
968 zone_create_info.fAging = 0
969 zone_create_info.fDsIntegrated = 1
970 zone_create_info.fLoadExisting = 1
971 zone_create_info.dwDpFlags = dns_directorypartition
973 dns_conn.DnssrvOperation2(client_version, 0, server, None,
974 0, 'ZoneCreate', typeid,
975 zone_create_info)
977 typeid = dnsserver.DNSSRV_TYPEID_NAME_AND_PARAM
978 name_and_param = dnsserver.DNS_RPC_NAME_AND_PARAM()
979 name_and_param.pszNodeName = 'AllowUpdate'
980 name_and_param.dwParam = dnsp.DNS_ZONE_UPDATE_SECURE
982 messages = {
983 werror.WERR_DNS_ERROR_ZONE_ALREADY_EXISTS: (
984 f'Zone "{zone}" already exists.')
987 dns_conn.DnssrvOperation2(client_version, 0, server, zone,
988 0, 'ResetDwordProperty', typeid,
989 name_and_param, messages=messages)
991 self.outf.write('Zone %s created successfully\n' % zone)
994 class cmd_zonedelete(Command):
995 """Delete a zone."""
997 synopsis = '%prog <server> <zone> [options]'
999 takes_args = ['server', 'zone']
1001 takes_optiongroups = {
1002 "sambaopts": options.SambaOptions,
1003 "versionopts": options.VersionOptions,
1004 "credopts": options.CredentialsOptions,
1007 def run(self, server, zone, sambaopts=None, credopts=None,
1008 versionopts=None):
1010 self.lp = sambaopts.get_loadparm()
1011 self.creds = credopts.get_credentials(self.lp)
1012 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
1014 zone = zone.lower()
1016 messages = {
1017 werror.WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST: (
1018 f'Zone {zone} does not exist and so could not be deleted.'),
1020 res = dns_conn.DnssrvOperation2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
1021 0, server, zone, 0, 'DeleteZoneFromDs',
1022 dnsserver.DNSSRV_TYPEID_NULL,
1023 None, messages=messages)
1025 self.outf.write('Zone %s deleted successfully\n' % zone)
1028 class cmd_query(Command):
1029 """Query a name."""
1031 synopsis = ('%prog <server> <zone> <name> '
1032 '<A|AAAA|PTR|CNAME|MX|NS|SOA|SRV|TXT|ALL> [options]')
1034 takes_args = ['server', 'zone', 'name', 'rtype']
1036 takes_optiongroups = {
1037 "sambaopts": options.SambaOptions,
1038 "versionopts": options.VersionOptions,
1039 "credopts": options.CredentialsOptions,
1042 takes_options = [
1043 Option('--authority', help='Search authoritative records (default)',
1044 action='store_true', dest='authority'),
1045 Option('--cache', help='Search cached records',
1046 action='store_true', dest='cache'),
1047 Option('--glue', help='Search glue records',
1048 action='store_true', dest='glue'),
1049 Option('--root', help='Search root hints',
1050 action='store_true', dest='root'),
1051 Option('--additional', help='List additional records',
1052 action='store_true', dest='additional'),
1053 Option('--no-children', help='Do not list children',
1054 action='store_true', dest='no_children'),
1055 Option('--only-children', help='List only children',
1056 action='store_true', dest='only_children')
1059 def run(self, server, zone, name, rtype, authority=False, cache=False,
1060 glue=False, root=False, additional=False, no_children=False,
1061 only_children=False, sambaopts=None, credopts=None,
1062 versionopts=None):
1063 record_type = dns_type_flag(rtype)
1065 if name.find('*') != -1:
1066 self.outf.write('use "@" to dump entire domain, looking up %s\n' %
1067 name)
1069 select_flags = 0
1070 if authority:
1071 select_flags |= dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
1072 if cache:
1073 select_flags |= dnsserver.DNS_RPC_VIEW_CACHE_DATA
1074 if glue:
1075 select_flags |= dnsserver.DNS_RPC_VIEW_GLUE_DATA
1076 if root:
1077 select_flags |= dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA
1078 if additional:
1079 select_flags |= dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA
1080 if no_children:
1081 select_flags |= dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1082 if only_children:
1083 select_flags |= dnsserver.DNS_RPC_VIEW_ONLY_CHILDREN
1085 if select_flags == 0:
1086 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
1088 if select_flags == dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA:
1089 self.outf.write('Specify either --authority or --root along with --additional.\n')
1090 self.outf.write('Assuming --authority.\n')
1091 select_flags |= dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
1093 self.lp = sambaopts.get_loadparm()
1094 self.creds = credopts.get_credentials(self.lp)
1095 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
1097 messages = {
1098 werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: (
1099 'Record or zone does not exist.')
1101 buflen, res = dns_conn.DnssrvEnumRecords2(
1102 dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, zone, name,
1103 None, record_type, select_flags, None, None,
1104 messages=messages)
1106 print_dnsrecords(self.outf, res)
1109 class cmd_roothints(Command):
1110 """Query root hints."""
1112 synopsis = '%prog <server> [<name>] [options]'
1114 takes_args = ['server', 'name?']
1116 takes_optiongroups = {
1117 "sambaopts": options.SambaOptions,
1118 "versionopts": options.VersionOptions,
1119 "credopts": options.CredentialsOptions,
1122 def run(self, server, name='.', sambaopts=None, credopts=None,
1123 versionopts=None):
1124 record_type = dnsp.DNS_TYPE_NS
1125 select_flags = (dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA |
1126 dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA)
1128 self.lp = sambaopts.get_loadparm()
1129 self.creds = credopts.get_credentials(self.lp)
1130 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
1132 buflen, res = dns_conn.DnssrvEnumRecords2(
1133 dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, '..RootHints',
1134 name, None, record_type, select_flags, None, None)
1135 print_dnsrecords(self.outf, res)
1138 class cmd_add_record(Command):
1139 """Add a DNS record
1141 For each type data contents are as follows:
1142 A ipv4_address_string
1143 AAAA ipv6_address_string
1144 PTR fqdn_string
1145 CNAME fqdn_string
1146 NS fqdn_string
1147 MX "fqdn_string preference"
1148 SRV "fqdn_string port priority weight"
1149 TXT "'string1' 'string2' ..."
1152 synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SRV|TXT> <data>'
1154 takes_args = ['server', 'zone', 'name', 'rtype', 'data']
1156 takes_optiongroups = {
1157 "sambaopts": options.SambaOptions,
1158 "versionopts": options.VersionOptions,
1159 "credopts": options.CredentialsOptions,
1162 def run(self, server, zone, name, rtype, data, sambaopts=None,
1163 credopts=None, versionopts=None):
1165 if rtype.upper() not in ('A', 'AAAA', 'PTR', 'CNAME', 'NS', 'MX', 'SRV', 'TXT'):
1166 raise CommandError('Adding record of type %s is not supported' % rtype)
1168 record_type = dns_type_flag(rtype)
1169 rec = data_to_dns_record(record_type, data)
1171 self.lp = sambaopts.get_loadparm()
1172 self.creds = credopts.get_credentials(self.lp)
1173 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
1175 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1176 add_rec_buf.rec = rec
1178 messages = {
1179 werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: (
1180 'Zone does not exist; record could not be added. '
1181 f'zone[{zone}] name[{name}'),
1182 werror.WERR_DNS_ERROR_RECORD_ALREADY_EXISTS: (
1183 'Record already exists; record could not be added. '
1184 f'zone[{zone}] name[{name}]')
1186 dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
1187 0, server, zone, name, add_rec_buf, None,
1188 messages=messages)
1190 self.outf.write('Record added successfully\n')
1193 class cmd_update_record(Command):
1194 """Update a DNS record
1196 For each type data contents are as follows:
1197 A ipv4_address_string
1198 AAAA ipv6_address_string
1199 PTR fqdn_string
1200 CNAME fqdn_string
1201 NS fqdn_string
1202 MX "fqdn_string preference"
1203 SOA "fqdn_dns fqdn_email serial refresh retry expire minimumttl"
1204 SRV "fqdn_string port priority weight"
1205 TXT "'string1' 'string2' ..."
1208 synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SOA|SRV|TXT> <olddata> <newdata>'
1210 takes_args = ['server', 'zone', 'name', 'rtype', 'olddata', 'newdata']
1212 takes_optiongroups = {
1213 "sambaopts": options.SambaOptions,
1214 "versionopts": options.VersionOptions,
1215 "credopts": options.CredentialsOptions,
1218 def run(self, server, zone, name, rtype, olddata, newdata,
1219 sambaopts=None, credopts=None, versionopts=None):
1221 rtype = rtype.upper()
1222 if rtype not in ('A', 'AAAA', 'PTR', 'CNAME', 'NS', 'MX', 'SOA', 'SRV', 'TXT'):
1223 raise CommandError('Updating record of type %s is not supported' % rtype)
1225 try:
1226 if rtype == 'A':
1227 inet_pton(AF_INET, newdata)
1228 elif rtype == 'AAAA':
1229 inet_pton(AF_INET6, newdata)
1230 except OSError as e:
1231 raise CommandError(f"bad data for {rtype}: {e!r}")
1233 record_type = dns_type_flag(rtype)
1234 rec = data_to_dns_record(record_type, newdata)
1236 self.lp = sambaopts.get_loadparm()
1237 self.creds = credopts.get_credentials(self.lp)
1238 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
1240 try:
1241 rec_match = dns_record_match(dns_conn.dns_conn, server, zone,
1242 name, record_type, olddata)
1243 except DNSParseError as e:
1244 raise CommandError(*e.args) from None
1246 if not rec_match:
1247 raise CommandError('Record or zone does not exist.')
1249 # Copy properties from existing record to new record
1250 rec.dwFlags = rec_match.dwFlags
1251 rec.dwSerial = rec_match.dwSerial
1252 rec.dwTtlSeconds = rec_match.dwTtlSeconds
1253 rec.dwTimeStamp = rec_match.dwTimeStamp
1255 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1256 add_rec_buf.rec = rec
1258 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1259 del_rec_buf.rec = rec_match
1261 messages = {
1262 werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: (
1263 f'Zone {zone} does not exist; record could not be updated.'),
1266 dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
1268 server,
1269 zone,
1270 name,
1271 add_rec_buf,
1272 del_rec_buf,
1273 messages=messages)
1275 self.outf.write('Record updated successfully\n')
1278 class cmd_delete_record(Command):
1279 """Delete a DNS record
1281 For each type data contents are as follows:
1282 A ipv4_address_string
1283 AAAA ipv6_address_string
1284 PTR fqdn_string
1285 CNAME fqdn_string
1286 NS fqdn_string
1287 MX "fqdn_string preference"
1288 SRV "fqdn_string port priority weight"
1289 TXT "'string1' 'string2' ..."
1292 synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SRV|TXT> <data>'
1294 takes_args = ['server', 'zone', 'name', 'rtype', 'data']
1296 takes_optiongroups = {
1297 "sambaopts": options.SambaOptions,
1298 "versionopts": options.VersionOptions,
1299 "credopts": options.CredentialsOptions,
1302 def run(self, server, zone, name, rtype, data, sambaopts=None, credopts=None, versionopts=None):
1304 if rtype.upper() not in ('A', 'AAAA', 'PTR', 'CNAME', 'NS', 'MX', 'SRV', 'TXT'):
1305 raise CommandError('Deleting record of type %s is not supported' % rtype)
1307 record_type = dns_type_flag(rtype)
1308 rec = data_to_dns_record(record_type, data)
1310 self.lp = sambaopts.get_loadparm()
1311 self.creds = credopts.get_credentials(self.lp)
1312 dns_conn = DnsConnWrapper(server, self.lp, self.creds)
1314 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1315 del_rec_buf.rec = rec
1317 messages = {
1318 werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: (
1319 'Zone does not exist; record could not be deleted. '
1320 f'zone[{zone}] name[{name}'),
1321 werror.WERR_DNS_ERROR_RECORD_ALREADY_EXISTS: (
1322 'Record already exists; record could not be deleted. '
1323 f'zone[{zone}] name[{name}]')
1325 dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
1327 server,
1328 zone,
1329 name,
1330 None,
1331 del_rec_buf,
1332 messages=messages)
1334 self.outf.write('Record deleted successfully\n')
1337 class cmd_cleanup_record(Command):
1338 """Cleanup DNS records for a DNS host.
1340 example:
1342 samba-tool dns cleanup dc1 dc1.samdom.test.site -U USER%PASSWORD
1344 NOTE: This command in many cases will only mark the `dNSTombstoned` attr
1345 as `TRUE` on the DNS records. Querying will no longer return results but
1346 there may still be some placeholder entries in the database.
1349 synopsis = '%prog <server> <dnshostname>'
1351 takes_args = ['server', 'dnshostname']
1353 takes_optiongroups = {
1354 "sambaopts": options.SambaOptions,
1355 "versionopts": options.VersionOptions,
1356 "credopts": options.CredentialsOptions,
1359 takes_options = [
1360 Option("-v", "--verbose", help="Be verbose", action="store_true"),
1361 Option("-q", "--quiet", help="Be quiet", action="store_true"),
1364 def run(self, server, dnshostname, sambaopts=None, credopts=None,
1365 versionopts=None, verbose=False, quiet=False):
1366 lp = sambaopts.get_loadparm()
1367 creds = credopts.get_credentials(lp)
1369 logger = self.get_logger(verbose=verbose, quiet=quiet)
1371 samdb = SamDB(url="ldap://%s" % server,
1372 session_info=system_session(),
1373 credentials=creds, lp=lp)
1375 remove_dc.remove_dns_references(samdb, logger, dnshostname,
1376 ignore_no_name=True)
1379 class cmd_dns(SuperCommand):
1380 """Domain Name Service (DNS) management."""
1382 subcommands = {}
1383 subcommands['serverinfo'] = cmd_serverinfo()
1384 subcommands['zoneoptions'] = cmd_zoneoptions()
1385 subcommands['zoneinfo'] = cmd_zoneinfo()
1386 subcommands['zonelist'] = cmd_zonelist()
1387 subcommands['zonecreate'] = cmd_zonecreate()
1388 subcommands['zonedelete'] = cmd_zonedelete()
1389 subcommands['query'] = cmd_query()
1390 subcommands['roothints'] = cmd_roothints()
1391 subcommands['add'] = cmd_add_record()
1392 subcommands['update'] = cmd_update_record()
1393 subcommands['delete'] = cmd_delete_record()
1394 subcommands['cleanup'] = cmd_cleanup_record()