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
28 from samba
.ndr
import ndr_unpack
, ndr_pack
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 (
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':
50 binding_str
= "ncacn_ip_tcp:%s[sign]" % server
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
))
60 """A wrapper around a dnsserver.dnsserver connection that makes it
61 harder not to report friendly messages.
65 dns_conn = dns_connect(server, lp, creds)
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.
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( # ...
87 This example changes the message for ZONE_DOES_NOT_EXIST and
88 avoids catching NAME_DOES_NOT_EXIST.
90 Only WERRORErrors are intercepted.
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
)
107 "DnssrvComplexOperation2",
108 "DnssrvEnumRecords2",
111 "DnssrvUpdateRecord2"}:
114 def f(*args
, messages
=None):
120 except WERRORError
as e
:
121 werr
, errstr
= e
.args
124 # None overrides a default message, leaving the bare exception
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
)
134 def bool_string(flag
):
140 ret
= 'UNKNOWN (0x%x)' % flag
144 def enum_string(module
, enum_defs
, value
):
147 if value
== getattr(module
, e
):
151 ret
= 'UNKNOWN (0x%x)' % value
155 def bitmap_string(module
, bitmap_defs
, value
):
157 for b
in bitmap_defs
:
158 if value
& getattr(module
, b
):
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
):
221 for i
in range(array
.AddrCount
):
222 addr
= inet_ntop(AF_INET
, pack('I', array
.AddrArray
[i
]))
227 def dns_addr_array_string(array
):
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
)
244 def dns_type_flag(rec_type
):
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()
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
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
))
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
))
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
))
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
))
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
))
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
:
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' % (
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
)
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' % (
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
):
466 rec
= record_from_string(record_type
, data
)
467 except DNSParseError
as e
:
468 raise CommandError(*e
.args
) from None
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
,
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,
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
,
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
:
510 help=f
"{doc} [{_min}-{_max}]",
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
,
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
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)',
559 'Aging refresh interval in hours (0: use default)',
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,
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:
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
591 print(f
"would set {k} to {v} for {zone}", file=self
.outf
)
594 dns_conn
.DnssrvOperation2(client_version
,
599 'ResetDwordProperty',
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.
611 for x
in (mark_old_records_static
,
612 mark_records_static_regex
,
613 mark_records_dynamic_regex
):
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
,
625 if mark_records_static_regex
is not None:
626 self
.mark_records_static_regex(server
,
628 mark_records_static_regex
,
631 if mark_records_dynamic_regex
is not None:
632 self
.mark_records_dynamic_regex(server
,
634 mark_records_dynamic_regex
,
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"])
653 def mark_old_records_static(self
, server
, zone_name
, date_string
, dry_run
):
655 ts
= time
.strptime(date_string
, "%Y-%m-%d")
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
)
664 if "dnsRecord" not in node
:
667 values
= list(node
["dnsRecord"])
669 for i
, v
in enumerate(values
):
670 rec
= ndr_unpack(dnsp
.DnssrvRpcRecord
, v
)
671 if rec
.dwTimeStamp
< threshold
and rec
.dwTimeStamp
!= 0:
673 values
[i
] = ndr_pack(rec
)
679 name
= node
["name"][0].decode()
682 print(f
"would make {changes}/{len(values)} records static "
683 f
"on {name}.{zone_name}.", file=self
.outf
)
686 msg
= ldb
.Message
.from_dict(samdb
,
690 ldb
.FLAG_MOD_REPLACE
)
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
)
702 name
= node
["name"][0].decode()
703 if not r
.search(name
):
705 if "dnsRecord" not in node
:
708 values
= list(node
["dnsRecord"])
713 for i
, v
in enumerate(values
):
714 rec
= ndr_unpack(dnsp
.DnssrvRpcRecord
, v
)
715 if rec
.dwTimeStamp
!= 0:
717 values
[i
] = ndr_pack(rec
)
724 print(f
"would make {changes}/{len(values)} records static "
725 f
"on {name}.{zone_name}.", file=self
.outf
)
728 msg
= ldb
.Message
.from_dict(samdb
,
732 ldb
.FLAG_MOD_REPLACE
)
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,
742 r
= re
.compile(regex
)
743 samdb
, nodes
= self
._get
_dns
_nodes
(server
, zone_name
)
745 dns_timestamp
= dsdb_dns
.unix_to_dns_timestamp(int(now
))
753 name
= node
["name"][0].decode()
754 if not r
.search(name
):
756 if "dnsRecord" not in node
:
759 values
= list(node
["dnsRecord"])
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
)
775 print(f
"would make {changes}/{len(values)} records dynamic "
776 f
"on {name}.{zone_name}.", file=self
.outf
)
779 msg
= ldb
.Message
.from_dict(samdb
,
783 ldb
.FLAG_MOD_REPLACE
)
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
,
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,
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
,
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
,
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):
862 request_filter |
= dnsserver
.DNS_ZONE_REQUEST_PRIMARY
864 request_filter |
= dnsserver
.DNS_ZONE_REQUEST_SECONDARY
866 request_filter |
= dnsserver
.DNS_ZONE_REQUEST_CACHE
868 request_filter |
= dnsserver
.DNS_ZONE_REQUEST_AUTO
870 request_filter |
= dnsserver
.DNS_ZONE_REQUEST_FORWARD
872 request_filter |
= dnsserver
.DNS_ZONE_REQUEST_REVERSE
874 request_filter |
= dnsserver
.DNS_ZONE_REQUEST_DS
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
,
890 dnsserver
.DNSSRV_TYPEID_DWORD
,
893 if client_version
== dnsserver
.DNS_CLIENT_VERSION_W2K
:
894 typeid
= dnsserver
.DNSSRV_TYPEID_ZONE_W2K
896 typeid
= dnsserver
.DNSSRV_TYPEID_ZONE
897 print_enumzones(self
.outf
, typeid
, res
)
900 class cmd_zonecreate(Command
):
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
,
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).',
922 metavar
='domain|forest',
923 choices
=['domain', 'forest'],
935 self
.lp
= sambaopts
.get_loadparm()
936 self
.creds
= credopts
.get_credentials(self
.lp
)
937 dns_conn
= DnsConnWrapper(server
, self
.lp
, self
.creds
)
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
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
,
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
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
):
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,
1010 self
.lp
= sambaopts
.get_loadparm()
1011 self
.creds
= credopts
.get_credentials(self
.lp
)
1012 dns_conn
= DnsConnWrapper(server
, self
.lp
, self
.creds
)
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
):
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
,
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,
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' %
1071 select_flags |
= dnsserver
.DNS_RPC_VIEW_AUTHORITY_DATA
1073 select_flags |
= dnsserver
.DNS_RPC_VIEW_CACHE_DATA
1075 select_flags |
= dnsserver
.DNS_RPC_VIEW_GLUE_DATA
1077 select_flags |
= dnsserver
.DNS_RPC_VIEW_ROOT_HINT_DATA
1079 select_flags |
= dnsserver
.DNS_RPC_VIEW_ADDITIONAL_DATA
1081 select_flags |
= dnsserver
.DNS_RPC_VIEW_NO_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
)
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,
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,
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
):
1141 For each type data contents are as follows:
1142 A ipv4_address_string
1143 AAAA ipv6_address_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
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,
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
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
)
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
)
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
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
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
,
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
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
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
,
1334 self
.outf
.write('Record deleted successfully\n')
1337 class cmd_cleanup_record(Command
):
1338 """Cleanup DNS records for a DNS host.
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
,
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."""
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()