1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-12 09:18:10 +03:00
samba-mirror/python/samba/netcmd/dns.py
Douglas Bagnall 7c298ee89f samba-tool: dns update rejects malformed addresses
Because neither filling out the struct will not necessarily tell you
you got it wrong, and the RPC could succeed in setting an arbitrary
wrong address (typically, an IPv6 address would set an A record to
"255.255.255.255").

Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2021-07-05 04:16:34 +00:00

1298 lines
52 KiB
Python

# DNS management tool
#
# Copyright (C) Amitay Isaacs 2011-2012
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import samba.getopt as options
from samba import WERRORError
from samba import werror
from struct import pack
from socket import inet_ntop, inet_pton
from socket import AF_INET
from socket import AF_INET6
import struct
import time
import ldb
from samba.ndr import ndr_unpack, ndr_pack
import re
from samba import remove_dc, dsdb_dns
from samba.samdb import SamDB
from samba.auth import system_session
from samba.netcmd import (
Command,
CommandError,
Option,
SuperCommand,
)
from samba.dcerpc import dnsp, dnsserver
from samba.dnsserver import record_from_string, DNSParseError, flag_from_string
from samba.dnsserver import dns_record_match
def dns_connect(server, lp, creds):
if server.lower() == 'localhost':
server = '127.0.0.1'
binding_str = "ncacn_ip_tcp:%s[sign]" % server
try:
dns_conn = dnsserver.dnsserver(binding_str, lp, creds)
except RuntimeError as e:
raise CommandError('Connecting to DNS RPC server %s failed with %s' % (server, e))
return dns_conn
def bool_string(flag):
if flag == 0:
ret = 'FALSE'
elif flag == 1:
ret = 'TRUE'
else:
ret = 'UNKNOWN (0x%x)' % flag
return ret
def enum_string(module, enum_defs, value):
ret = None
for e in enum_defs:
if value == getattr(module, e):
ret = e
break
if not ret:
ret = 'UNKNOWN (0x%x)' % value
return ret
def bitmap_string(module, bitmap_defs, value):
ret = ''
for b in bitmap_defs:
if value & getattr(module, b):
ret += '%s ' % b
if not ret:
ret = 'NONE'
return ret
def boot_method_string(boot_method):
enum_defs = ['DNS_BOOT_METHOD_UNINITIALIZED', 'DNS_BOOT_METHOD_FILE',
'DNS_BOOT_METHOD_REGISTRY', 'DNS_BOOT_METHOD_DIRECTORY']
return enum_string(dnsserver, enum_defs, boot_method)
def name_check_flag_string(check_flag):
enum_defs = ['DNS_ALLOW_RFC_NAMES_ONLY', 'DNS_ALLOW_NONRFC_NAMES',
'DNS_ALLOW_MULTIBYTE_NAMES', 'DNS_ALLOW_ALL_NAMES']
return enum_string(dnsserver, enum_defs, check_flag)
def zone_type_string(zone_type):
enum_defs = ['DNS_ZONE_TYPE_CACHE', 'DNS_ZONE_TYPE_PRIMARY',
'DNS_ZONE_TYPE_SECONDARY', 'DNS_ZONE_TYPE_STUB',
'DNS_ZONE_TYPE_FORWARDER', 'DNS_ZONE_TYPE_SECONDARY_CACHE']
return enum_string(dnsp, enum_defs, zone_type)
def zone_update_string(zone_update):
enum_defs = ['DNS_ZONE_UPDATE_OFF', 'DNS_ZONE_UPDATE_UNSECURE',
'DNS_ZONE_UPDATE_SECURE']
return enum_string(dnsp, enum_defs, zone_update)
def zone_secondary_security_string(security):
enum_defs = ['DNS_ZONE_SECSECURE_NO_SECURITY', 'DNS_ZONE_SECSECURE_NS_ONLY',
'DNS_ZONE_SECSECURE_LIST_ONLY', 'DNS_ZONE_SECSECURE_NO_XFER']
return enum_string(dnsserver, enum_defs, security)
def zone_notify_level_string(notify_level):
enum_defs = ['DNS_ZONE_NOTIFY_OFF', 'DNS_ZONE_NOTIFY_ALL_SECONDARIES',
'DNS_ZONE_NOTIFY_LIST_ONLY']
return enum_string(dnsserver, enum_defs, notify_level)
def dp_flags_string(dp_flags):
bitmap_defs = ['DNS_DP_AUTOCREATED', 'DNS_DP_LEGACY', 'DNS_DP_DOMAIN_DEFAULT',
'DNS_DP_FOREST_DEFAULT', 'DNS_DP_ENLISTED', 'DNS_DP_DELETED']
return bitmap_string(dnsserver, bitmap_defs, dp_flags)
def zone_flags_string(flags):
bitmap_defs = ['DNS_RPC_ZONE_PAUSED', 'DNS_RPC_ZONE_SHUTDOWN',
'DNS_RPC_ZONE_REVERSE', 'DNS_RPC_ZONE_AUTOCREATED',
'DNS_RPC_ZONE_DSINTEGRATED', 'DNS_RPC_ZONE_AGING',
'DNS_RPC_ZONE_UPDATE_UNSECURE', 'DNS_RPC_ZONE_UPDATE_SECURE',
'DNS_RPC_ZONE_READONLY']
return bitmap_string(dnsserver, bitmap_defs, flags)
def ip4_array_string(array):
ret = []
if not array:
return ret
for i in range(array.AddrCount):
addr = inet_ntop(AF_INET, pack('I', array.AddrArray[i]))
ret.append(addr)
return ret
def dns_addr_array_string(array):
ret = []
if not array:
return ret
for i in range(array.AddrCount):
if array.AddrArray[i].MaxSa[0] == 0x02:
x = struct.pack('4B', *array.AddrArray[i].MaxSa[4:8])
addr = inet_ntop(AF_INET, x)
elif array.AddrArray[i].MaxSa[0] == 0x17:
x = struct.pack('16B', *array.AddrArray[i].MaxSa[8:24])
addr = inet_ntop(AF_INET6, x)
else:
addr = 'UNKNOWN'
ret.append(addr)
return ret
def dns_type_flag(rec_type):
try:
return flag_from_string(rec_type)
except DNSParseError as e:
raise CommandError(*e.args)
def dns_client_version(cli_version):
version = cli_version.upper()
if version == 'W2K':
client_version = dnsserver.DNS_CLIENT_VERSION_W2K
elif version == 'DOTNET':
client_version = dnsserver.DNS_CLIENT_VERSION_DOTNET
elif version == 'LONGHORN':
client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
else:
raise CommandError('Unknown client version %s' % cli_version)
return client_version
def print_serverinfo(outf, typeid, serverinfo):
outf.write(' dwVersion : 0x%x\n' % serverinfo.dwVersion)
outf.write(' fBootMethod : %s\n' % boot_method_string(serverinfo.fBootMethod))
outf.write(' fAdminConfigured : %s\n' % bool_string(serverinfo.fAdminConfigured))
outf.write(' fAllowUpdate : %s\n' % bool_string(serverinfo.fAllowUpdate))
outf.write(' fDsAvailable : %s\n' % bool_string(serverinfo.fDsAvailable))
outf.write(' pszServerName : %s\n' % serverinfo.pszServerName)
outf.write(' pszDsContainer : %s\n' % serverinfo.pszDsContainer)
if typeid != dnsserver.DNSSRV_TYPEID_SERVER_INFO:
outf.write(' aipServerAddrs : %s\n' %
ip4_array_string(serverinfo.aipServerAddrs))
outf.write(' aipListenAddrs : %s\n' %
ip4_array_string(serverinfo.aipListenAddrs))
outf.write(' aipForwarders : %s\n' %
ip4_array_string(serverinfo.aipForwarders))
else:
outf.write(' aipServerAddrs : %s\n' %
dns_addr_array_string(serverinfo.aipServerAddrs))
outf.write(' aipListenAddrs : %s\n' %
dns_addr_array_string(serverinfo.aipListenAddrs))
outf.write(' aipForwarders : %s\n' %
dns_addr_array_string(serverinfo.aipForwarders))
outf.write(' dwLogLevel : %d\n' % serverinfo.dwLogLevel)
outf.write(' dwDebugLevel : %d\n' % serverinfo.dwDebugLevel)
outf.write(' dwForwardTimeout : %d\n' % serverinfo.dwForwardTimeout)
outf.write(' dwRpcPrototol : 0x%x\n' % serverinfo.dwRpcProtocol)
outf.write(' dwNameCheckFlag : %s\n' % name_check_flag_string(serverinfo.dwNameCheckFlag))
outf.write(' cAddressAnswerLimit : %d\n' % serverinfo.cAddressAnswerLimit)
outf.write(' dwRecursionRetry : %d\n' % serverinfo.dwRecursionRetry)
outf.write(' dwRecursionTimeout : %d\n' % serverinfo.dwRecursionTimeout)
outf.write(' dwMaxCacheTtl : %d\n' % serverinfo.dwMaxCacheTtl)
outf.write(' dwDsPollingInterval : %d\n' % serverinfo.dwDsPollingInterval)
outf.write(' dwScavengingInterval : %d\n' % serverinfo.dwScavengingInterval)
outf.write(' dwDefaultRefreshInterval : %d\n' % serverinfo.dwDefaultRefreshInterval)
outf.write(' dwDefaultNoRefreshInterval : %d\n' % serverinfo.dwDefaultNoRefreshInterval)
outf.write(' fAutoReverseZones : %s\n' % bool_string(serverinfo.fAutoReverseZones))
outf.write(' fAutoCacheUpdate : %s\n' % bool_string(serverinfo.fAutoCacheUpdate))
outf.write(' fRecurseAfterForwarding : %s\n' % bool_string(serverinfo.fRecurseAfterForwarding))
outf.write(' fForwardDelegations : %s\n' % bool_string(serverinfo.fForwardDelegations))
outf.write(' fNoRecursion : %s\n' % bool_string(serverinfo.fNoRecursion))
outf.write(' fSecureResponses : %s\n' % bool_string(serverinfo.fSecureResponses))
outf.write(' fRoundRobin : %s\n' % bool_string(serverinfo.fRoundRobin))
outf.write(' fLocalNetPriority : %s\n' % bool_string(serverinfo.fLocalNetPriority))
outf.write(' fBindSecondaries : %s\n' % bool_string(serverinfo.fBindSecondaries))
outf.write(' fWriteAuthorityNs : %s\n' % bool_string(serverinfo.fWriteAuthorityNs))
outf.write(' fStrictFileParsing : %s\n' % bool_string(serverinfo.fStrictFileParsing))
outf.write(' fLooseWildcarding : %s\n' % bool_string(serverinfo.fLooseWildcarding))
outf.write(' fDefaultAgingState : %s\n' % bool_string(serverinfo.fDefaultAgingState))
if typeid != dnsserver.DNSSRV_TYPEID_SERVER_INFO_W2K:
outf.write(' dwRpcStructureVersion : 0x%x\n' % serverinfo.dwRpcStructureVersion)
outf.write(' aipLogFilter : %s\n' % dns_addr_array_string(serverinfo.aipLogFilter))
outf.write(' pwszLogFilePath : %s\n' % serverinfo.pwszLogFilePath)
outf.write(' pszDomainName : %s\n' % serverinfo.pszDomainName)
outf.write(' pszForestName : %s\n' % serverinfo.pszForestName)
outf.write(' pszDomainDirectoryPartition : %s\n' % serverinfo.pszDomainDirectoryPartition)
outf.write(' pszForestDirectoryPartition : %s\n' % serverinfo.pszForestDirectoryPartition)
outf.write(' dwLocalNetPriorityNetMask : 0x%x\n' % serverinfo.dwLocalNetPriorityNetMask)
outf.write(' dwLastScavengeTime : %d\n' % serverinfo.dwLastScavengeTime)
outf.write(' dwEventLogLevel : %d\n' % serverinfo.dwEventLogLevel)
outf.write(' dwLogFileMaxSize : %d\n' % serverinfo.dwLogFileMaxSize)
outf.write(' dwDsForestVersion : %d\n' % serverinfo.dwDsForestVersion)
outf.write(' dwDsDomainVersion : %d\n' % serverinfo.dwDsDomainVersion)
outf.write(' dwDsDsaVersion : %d\n' % serverinfo.dwDsDsaVersion)
if typeid == dnsserver.DNSSRV_TYPEID_SERVER_INFO:
outf.write(' fReadOnlyDC : %s\n' % bool_string(serverinfo.fReadOnlyDC))
def print_zoneinfo(outf, typeid, zoneinfo):
outf.write(' pszZoneName : %s\n' % zoneinfo.pszZoneName)
outf.write(' dwZoneType : %s\n' % zone_type_string(zoneinfo.dwZoneType))
outf.write(' fReverse : %s\n' % bool_string(zoneinfo.fReverse))
outf.write(' fAllowUpdate : %s\n' % zone_update_string(zoneinfo.fAllowUpdate))
outf.write(' fPaused : %s\n' % bool_string(zoneinfo.fPaused))
outf.write(' fShutdown : %s\n' % bool_string(zoneinfo.fShutdown))
outf.write(' fAutoCreated : %s\n' % bool_string(zoneinfo.fAutoCreated))
outf.write(' fUseDatabase : %s\n' % bool_string(zoneinfo.fUseDatabase))
outf.write(' pszDataFile : %s\n' % zoneinfo.pszDataFile)
if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
outf.write(' aipMasters : %s\n' %
ip4_array_string(zoneinfo.aipMasters))
else:
outf.write(' aipMasters : %s\n' %
dns_addr_array_string(zoneinfo.aipMasters))
outf.write(' fSecureSecondaries : %s\n' % zone_secondary_security_string(zoneinfo.fSecureSecondaries))
outf.write(' fNotifyLevel : %s\n' % zone_notify_level_string(zoneinfo.fNotifyLevel))
if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
outf.write(' aipSecondaries : %s\n' %
ip4_array_string(zoneinfo.aipSecondaries))
outf.write(' aipNotify : %s\n' %
ip4_array_string(zoneinfo.aipNotify))
else:
outf.write(' aipSecondaries : %s\n' %
dns_addr_array_string(zoneinfo.aipSecondaries))
outf.write(' aipNotify : %s\n' %
dns_addr_array_string(zoneinfo.aipNotify))
outf.write(' fUseWins : %s\n' % bool_string(zoneinfo.fUseWins))
outf.write(' fUseNbstat : %s\n' % bool_string(zoneinfo.fUseNbstat))
outf.write(' fAging : %s\n' % bool_string(zoneinfo.fAging))
outf.write(' dwNoRefreshInterval : %d\n' % zoneinfo.dwNoRefreshInterval)
outf.write(' dwRefreshInterval : %d\n' % zoneinfo.dwRefreshInterval)
outf.write(' dwAvailForScavengeTime : %d\n' % zoneinfo.dwAvailForScavengeTime)
if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
outf.write(' aipScavengeServers : %s\n' %
ip4_array_string(zoneinfo.aipScavengeServers))
else:
outf.write(' aipScavengeServers : %s\n' %
dns_addr_array_string(zoneinfo.aipScavengeServers))
if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO_W2K:
outf.write(' dwRpcStructureVersion : 0x%x\n' % zoneinfo.dwRpcStructureVersion)
outf.write(' dwForwarderTimeout : %d\n' % zoneinfo.dwForwarderTimeout)
outf.write(' fForwarderSlave : %d\n' % zoneinfo.fForwarderSlave)
if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
outf.write(' aipLocalMasters : %s\n' %
ip4_array_string(zoneinfo.aipLocalMasters))
else:
outf.write(' aipLocalMasters : %s\n' %
dns_addr_array_string(zoneinfo.aipLocalMasters))
outf.write(' dwDpFlags : %s\n' % dp_flags_string(zoneinfo.dwDpFlags))
outf.write(' pszDpFqdn : %s\n' % zoneinfo.pszDpFqdn)
outf.write(' pwszZoneDn : %s\n' % zoneinfo.pwszZoneDn)
outf.write(' dwLastSuccessfulSoaCheck : %d\n' % zoneinfo.dwLastSuccessfulSoaCheck)
outf.write(' dwLastSuccessfulXfr : %d\n' % zoneinfo.dwLastSuccessfulXfr)
if typeid == dnsserver.DNSSRV_TYPEID_ZONE_INFO:
outf.write(' fQueuedForBackgroundLoad : %s\n' % bool_string(zoneinfo.fQueuedForBackgroundLoad))
outf.write(' fBackgroundLoadInProgress : %s\n' % bool_string(zoneinfo.fBackgroundLoadInProgress))
outf.write(' fReadOnlyZone : %s\n' % bool_string(zoneinfo.fReadOnlyZone))
outf.write(' dwLastXfrAttempt : %d\n' % zoneinfo.dwLastXfrAttempt)
outf.write(' dwLastXfrResult : %d\n' % zoneinfo.dwLastXfrResult)
def print_zone(outf, typeid, zone):
outf.write(' pszZoneName : %s\n' % zone.pszZoneName)
outf.write(' Flags : %s\n' % zone_flags_string(zone.Flags))
outf.write(' ZoneType : %s\n' % zone_type_string(zone.ZoneType))
outf.write(' Version : %s\n' % zone.Version)
if typeid != dnsserver.DNSSRV_TYPEID_ZONE_W2K:
outf.write(' dwDpFlags : %s\n' % dp_flags_string(zone.dwDpFlags))
outf.write(' pszDpFqdn : %s\n' % zone.pszDpFqdn)
def print_enumzones(outf, typeid, zones):
outf.write(' %d zone(s) found\n' % zones.dwZoneCount)
for zone in zones.ZoneArray:
outf.write('\n')
print_zone(outf, typeid, zone)
def print_dns_record(outf, rec):
if rec.wType == dnsp.DNS_TYPE_A:
mesg = 'A: %s' % (rec.data)
elif rec.wType == dnsp.DNS_TYPE_AAAA:
mesg = 'AAAA: %s' % (rec.data)
elif rec.wType == dnsp.DNS_TYPE_PTR:
mesg = 'PTR: %s' % (rec.data.str)
elif rec.wType == dnsp.DNS_TYPE_NS:
mesg = 'NS: %s' % (rec.data.str)
elif rec.wType == dnsp.DNS_TYPE_CNAME:
mesg = 'CNAME: %s' % (rec.data.str)
elif rec.wType == dnsp.DNS_TYPE_SOA:
mesg = 'SOA: serial=%d, refresh=%d, retry=%d, expire=%d, minttl=%d, ns=%s, email=%s' % (
rec.data.dwSerialNo,
rec.data.dwRefresh,
rec.data.dwRetry,
rec.data.dwExpire,
rec.data.dwMinimumTtl,
rec.data.NamePrimaryServer.str,
rec.data.ZoneAdministratorEmail.str)
elif rec.wType == dnsp.DNS_TYPE_MX:
mesg = 'MX: %s (%d)' % (rec.data.nameExchange.str, rec.data.wPreference)
elif rec.wType == dnsp.DNS_TYPE_SRV:
mesg = 'SRV: %s (%d, %d, %d)' % (rec.data.nameTarget.str, rec.data.wPort,
rec.data.wPriority, rec.data.wWeight)
elif rec.wType == dnsp.DNS_TYPE_TXT:
slist = ['"%s"' % name.str for name in rec.data.str]
mesg = 'TXT: %s' % ','.join(slist)
else:
mesg = 'Unknown: '
outf.write(' %s (flags=%x, serial=%d, ttl=%d)\n' % (
mesg, rec.dwFlags, rec.dwSerial, rec.dwTtlSeconds))
def print_dnsrecords(outf, records):
for rec in records.rec:
outf.write(' Name=%s, Records=%d, Children=%d\n' % (
rec.dnsNodeName.str,
rec.wRecordCount,
rec.dwChildCount))
for dns_rec in rec.records:
print_dns_record(outf, dns_rec)
# Convert data into a dns record
def data_to_dns_record(record_type, data):
try:
rec = record_from_string(record_type, data)
except DNSParseError as e:
raise CommandError(*e.args) from None
return rec
class cmd_serverinfo(Command):
"""Query for Server information."""
synopsis = '%prog <server> [options]'
takes_args = ['server']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
takes_options = [
Option('--client-version', help='Client Version',
default='longhorn', metavar='w2k|dotnet|longhorn',
choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
]
def run(self, server, cli_ver, sambaopts=None, credopts=None,
versionopts=None):
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
client_version = dns_client_version(cli_ver)
typeid, res = dns_conn.DnssrvQuery2(client_version, 0, server,
None, 'ServerInfo')
print_serverinfo(self.outf, typeid, res)
def _add_integer_options(table, takes_options, integer_properties):
"""Generate options for cmd_zoneoptions"""
for k, doc, _min, _max in table:
o = '--' + k.lower()
opt = Option(o,
help=f"{doc} [{_min}-{_max}]",
type="int",
dest=k)
takes_options.append(opt)
integer_properties.append((k, _min, _max, o))
class cmd_zoneoptions(Command):
"""Change zone aging options."""
synopsis = '%prog <server> <zone> [options]'
takes_args = ['server', 'zone']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
takes_options = [
Option('--client-version', help='Client Version',
default='longhorn', metavar='w2k|dotnet|longhorn',
choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
Option('--mark-old-records-static', metavar="YYYY-MM-DD",
help="Make records older than this (YYYY-MM-DD) static"),
Option('--mark-records-static-regex', metavar="REGEXP",
help="Make records matching this regular expression static"),
Option('--mark-records-dynamic-regex', metavar="REGEXP",
help="Make records matching this regular expression dynamic"),
Option('-n', '--dry-run', action='store_true',
help="Don't change anything, say what would happen"),
]
integer_properties = []
# Any zone parameter that is stored as an integer (which is most of
# them) can be added to this table. The name should be the dnsp
# mixed case name, which will get munged into a lowercase name for
# the option. (e.g. "Aging" becomes "--aging").
#
# Note: just because we add a name here doesn't mean we will use
# it.
_add_integer_options([
# ( name, help-string, min, max )
('Aging', 'Enable record aging', 0, 1),
('NoRefreshInterval',
'Aging no refresh interval in hours (0: use default)',
0, 10 * 365 * 24),
('RefreshInterval',
'Aging refresh interval in hours (0: use default)',
0, 10 * 365 * 24),
],
takes_options,
integer_properties)
def run(self, server, zone, cli_ver, sambaopts=None, credopts=None,
versionopts=None, dry_run=False,
mark_old_records_static=None,
mark_records_static_regex=None,
mark_records_dynamic_regex=None,
**kwargs):
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
client_version = dns_client_version(cli_ver)
nap_type = dnsserver.DNSSRV_TYPEID_NAME_AND_PARAM
for k, _min, _max, o in self.integer_properties:
if kwargs.get(k) is None:
continue
v = kwargs[k]
if _min is not None and v < _min:
raise CommandError(f"{o} must be at least {_min}")
if _max is not None and v > _max:
raise CommandError(f"{o} can't exceed {_max}")
name_param = dnsserver.DNS_RPC_NAME_AND_PARAM()
name_param.dwParam = v
name_param.pszNodeName = k
if dry_run:
print(f"would set {k} to {v} for {zone}", file=self.outf)
continue
try:
dns_conn.DnssrvOperation2(client_version,
0,
server,
zone,
0,
'ResetDwordProperty',
nap_type,
name_param)
except WERRORError as e:
raise CommandError(f"Could not set {k} to {v}") from None
print(f"Set {k} to {v}", file=self.outf)
# We don't want to allow more than one of these --mark-*
# options at a time, as they are sensitive to ordering and
# the order is not documented.
n_mark_options = 0
for x in (mark_old_records_static,
mark_records_static_regex,
mark_records_dynamic_regex):
if x is not None:
n_mark_options += 1
if n_mark_options > 1:
raise CommandError("Multiple --mark-* options will not work\n")
if mark_old_records_static is not None:
self.mark_old_records_static(server, zone,
mark_old_records_static,
dry_run)
if mark_records_static_regex is not None:
self.mark_records_static_regex(server,
zone,
mark_records_static_regex,
dry_run)
if mark_records_dynamic_regex is not None:
self.mark_records_dynamic_regex(server,
zone,
mark_records_dynamic_regex,
dry_run)
def _get_dns_nodes(self, server, zone_name):
samdb = SamDB(url="ldap://%s" % server,
session_info=system_session(),
credentials=self.creds, lp=self.lp)
zone_dn = (f"DC={zone_name},CN=MicrosoftDNS,DC=DomainDNSZones,"
f"{samdb.get_default_basedn()}")
nodes = samdb.search(base=zone_dn,
scope=ldb.SCOPE_SUBTREE,
expression=("(&(objectClass=dnsNode)"
"(!(dNSTombstoned=TRUE)))"),
attrs=["dnsRecord", "name"])
return samdb, nodes
def mark_old_records_static(self, server, zone_name, date_string, dry_run):
try:
ts = time.strptime(date_string, "%Y-%m-%d")
t = time.mktime(ts)
except ValueError as e:
raise CommandError(f"Invalid date {date_string}: should be YYY-MM-DD")
threshold = dsdb_dns.unix_to_dns_timestamp(int(t))
samdb, nodes = self._get_dns_nodes(server, zone_name)
for node in nodes:
if "dnsRecord" not in node:
continue
values = list(node["dnsRecord"])
changes = 0
for i, v in enumerate(values):
rec = ndr_unpack(dnsp.DnssrvRpcRecord, v)
if rec.dwTimeStamp < threshold and rec.dwTimeStamp != 0:
rec.dwTimeStamp = 0
values[i] = ndr_pack(rec)
changes += 1
if changes == 0:
continue
name = node["name"][0].decode()
if dry_run:
print(f"would make {changes}/{len(values)} records static "
f"on {name}.{zone_name}.", file=self.outf)
continue
msg = ldb.Message.from_dict(samdb,
{'dn': node.dn,
'dnsRecord': values
},
ldb.FLAG_MOD_REPLACE)
samdb.modify(msg)
print(f"made {changes}/{len(values)} records static on "
f"{name}.{zone_name}.", file=self.outf)
def mark_records_static_regex(self, server, zone_name, regex, dry_run):
"""Make the records of nodes with matching names static.
"""
r = re.compile(regex)
samdb, nodes = self._get_dns_nodes(server, zone_name)
for node in nodes:
name = node["name"][0].decode()
if not r.search(name):
continue
if "dnsRecord" not in node:
continue
values = list(node["dnsRecord"])
if len(values) == 0:
continue
changes = 0
for i, v in enumerate(values):
rec = ndr_unpack(dnsp.DnssrvRpcRecord, v)
if rec.dwTimeStamp != 0:
rec.dwTimeStamp = 0
values[i] = ndr_pack(rec)
changes += 1
if changes == 0:
continue
if dry_run:
print(f"would make {changes}/{len(values)} records static "
f"on {name}.{zone_name}.", file=self.outf)
continue
msg = ldb.Message.from_dict(samdb,
{'dn': node.dn,
'dnsRecord': values
},
ldb.FLAG_MOD_REPLACE)
samdb.modify(msg)
print(f"made {changes}/{len(values)} records static on "
f"{name}.{zone_name}.", file=self.outf)
def mark_records_dynamic_regex(self, server, zone_name, regex, dry_run):
"""Make the records of nodes with matching names dynamic, with a
current timestamp. In this case we only adjust the A, AAAA,
and TXT records.
"""
r = re.compile(regex)
samdb, nodes = self._get_dns_nodes(server, zone_name)
now = time.time()
dns_timestamp = dsdb_dns.unix_to_dns_timestamp(int(now))
safe_wtypes = {
dnsp.DNS_TYPE_A,
dnsp.DNS_TYPE_AAAA,
dnsp.DNS_TYPE_TXT
}
for node in nodes:
name = node["name"][0].decode()
if not r.search(name):
continue
if "dnsRecord" not in node:
continue
values = list(node["dnsRecord"])
if len(values) == 0:
continue
changes = 0
for i, v in enumerate(values):
rec = ndr_unpack(dnsp.DnssrvRpcRecord, v)
if rec.wType in safe_wtypes and rec.dwTimeStamp == 0:
rec.dwTimeStamp = dns_timestamp
values[i] = ndr_pack(rec)
changes += 1
if changes == 0:
continue
if dry_run:
print(f"would make {changes}/{len(values)} records dynamic "
f"on {name}.{zone_name}.", file=self.outf)
continue
msg = ldb.Message.from_dict(samdb,
{'dn': node.dn,
'dnsRecord': values
},
ldb.FLAG_MOD_REPLACE)
samdb.modify(msg)
print(f"made {changes}/{len(values)} records dynamic on "
f"{name}.{zone_name}.", file=self.outf)
class cmd_zoneinfo(Command):
"""Query for zone information."""
synopsis = '%prog <server> <zone> [options]'
takes_args = ['server', 'zone']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
takes_options = [
Option('--client-version', help='Client Version',
default='longhorn', metavar='w2k|dotnet|longhorn',
choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
]
def run(self, server, zone, cli_ver, sambaopts=None, credopts=None,
versionopts=None):
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
client_version = dns_client_version(cli_ver)
typeid, res = dns_conn.DnssrvQuery2(client_version, 0, server, zone,
'ZoneInfo')
print_zoneinfo(self.outf, typeid, res)
class cmd_zonelist(Command):
"""Query for zones."""
synopsis = '%prog <server> [options]'
takes_args = ['server']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
takes_options = [
Option('--client-version', help='Client Version',
default='longhorn', metavar='w2k|dotnet|longhorn',
choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
Option('--primary', help='List primary zones (default)',
action='store_true', dest='primary'),
Option('--secondary', help='List secondary zones',
action='store_true', dest='secondary'),
Option('--cache', help='List cached zones',
action='store_true', dest='cache'),
Option('--auto', help='List automatically created zones',
action='store_true', dest='auto'),
Option('--forward', help='List forward zones',
action='store_true', dest='forward'),
Option('--reverse', help='List reverse zones',
action='store_true', dest='reverse'),
Option('--ds', help='List directory integrated zones',
action='store_true', dest='ds'),
Option('--non-ds', help='List non-directory zones',
action='store_true', dest='nonds')
]
def run(self, server, cli_ver, primary=False, secondary=False, cache=False,
auto=False, forward=False, reverse=False, ds=False, nonds=False,
sambaopts=None, credopts=None, versionopts=None):
request_filter = 0
if primary:
request_filter |= dnsserver.DNS_ZONE_REQUEST_PRIMARY
if secondary:
request_filter |= dnsserver.DNS_ZONE_REQUEST_SECONDARY
if cache:
request_filter |= dnsserver.DNS_ZONE_REQUEST_CACHE
if auto:
request_filter |= dnsserver.DNS_ZONE_REQUEST_AUTO
if forward:
request_filter |= dnsserver.DNS_ZONE_REQUEST_FORWARD
if reverse:
request_filter |= dnsserver.DNS_ZONE_REQUEST_REVERSE
if ds:
request_filter |= dnsserver.DNS_ZONE_REQUEST_DS
if nonds:
request_filter |= dnsserver.DNS_ZONE_REQUEST_NON_DS
if request_filter == 0:
request_filter = dnsserver.DNS_ZONE_REQUEST_PRIMARY
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
client_version = dns_client_version(cli_ver)
typeid, res = dns_conn.DnssrvComplexOperation2(client_version,
0, server, None,
'EnumZones',
dnsserver.DNSSRV_TYPEID_DWORD,
request_filter)
if client_version == dnsserver.DNS_CLIENT_VERSION_W2K:
typeid = dnsserver.DNSSRV_TYPEID_ZONE_W2K
else:
typeid = dnsserver.DNSSRV_TYPEID_ZONE
print_enumzones(self.outf, typeid, res)
class cmd_zonecreate(Command):
"""Create a zone."""
synopsis = '%prog <server> <zone> [options]'
takes_args = ['server', 'zone']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
takes_options = [
Option('--client-version', help='Client Version',
default='longhorn', metavar='w2k|dotnet|longhorn',
choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver')
]
def run(self, server, zone, cli_ver, sambaopts=None, credopts=None,
versionopts=None):
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
zone = zone.lower()
client_version = dns_client_version(cli_ver)
if client_version == dnsserver.DNS_CLIENT_VERSION_W2K:
typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE_W2K
zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_W2K()
zone_create_info.pszZoneName = zone
zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
zone_create_info.fAging = 0
zone_create_info.fDsIntegrated = 1
zone_create_info.fLoadExisting = 1
elif client_version == dnsserver.DNS_CLIENT_VERSION_DOTNET:
typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE_DOTNET
zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_DOTNET()
zone_create_info.pszZoneName = zone
zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
zone_create_info.fAging = 0
zone_create_info.fDsIntegrated = 1
zone_create_info.fLoadExisting = 1
zone_create_info.dwDpFlags = dnsserver.DNS_DP_DOMAIN_DEFAULT
else:
typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE
zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_LONGHORN()
zone_create_info.pszZoneName = zone
zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
zone_create_info.fAging = 0
zone_create_info.fDsIntegrated = 1
zone_create_info.fLoadExisting = 1
zone_create_info.dwDpFlags = dnsserver.DNS_DP_DOMAIN_DEFAULT
res = dns_conn.DnssrvOperation2(client_version, 0, server, None,
0, 'ZoneCreate', typeid,
zone_create_info)
typeid = dnsserver.DNSSRV_TYPEID_NAME_AND_PARAM
name_and_param = dnsserver.DNS_RPC_NAME_AND_PARAM()
name_and_param.pszNodeName = 'AllowUpdate'
name_and_param.dwParam = dnsp.DNS_ZONE_UPDATE_SECURE
try:
res = dns_conn.DnssrvOperation2(client_version, 0, server, zone,
0, 'ResetDwordProperty', typeid,
name_and_param)
except WERRORError as e:
if e.args[0] == werror.WERR_DNS_ERROR_ZONE_ALREADY_EXISTS:
self.outf.write('Zone already exists.')
raise e
self.outf.write('Zone %s created successfully\n' % zone)
class cmd_zonedelete(Command):
"""Delete a zone."""
synopsis = '%prog <server> <zone> [options]'
takes_args = ['server', 'zone']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
def run(self, server, zone, sambaopts=None, credopts=None,
versionopts=None):
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
zone = zone.lower()
try:
res = dns_conn.DnssrvOperation2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
0, server, zone, 0, 'DeleteZoneFromDs',
dnsserver.DNSSRV_TYPEID_NULL,
None)
except WERRORError as e:
if e.args[0] == werror.WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST:
raise CommandError('Zone does not exist and so could not be deleted.')
raise e
self.outf.write('Zone %s deleted successfully\n' % zone)
class cmd_query(Command):
"""Query a name."""
synopsis = ('%prog <server> <zone> <name> '
'<A|AAAA|PTR|CNAME|MX|NS|SOA|SRV|TXT|ALL> [options]')
takes_args = ['server', 'zone', 'name', 'rtype']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
takes_options = [
Option('--authority', help='Search authoritative records (default)',
action='store_true', dest='authority'),
Option('--cache', help='Search cached records',
action='store_true', dest='cache'),
Option('--glue', help='Search glue records',
action='store_true', dest='glue'),
Option('--root', help='Search root hints',
action='store_true', dest='root'),
Option('--additional', help='List additional records',
action='store_true', dest='additional'),
Option('--no-children', help='Do not list children',
action='store_true', dest='no_children'),
Option('--only-children', help='List only children',
action='store_true', dest='only_children')
]
def run(self, server, zone, name, rtype, authority=False, cache=False,
glue=False, root=False, additional=False, no_children=False,
only_children=False, sambaopts=None, credopts=None,
versionopts=None):
record_type = dns_type_flag(rtype)
if name.find('*') != -1:
self.outf.write('use "@" to dump entire domain, looking up %s\n' %
name)
select_flags = 0
if authority:
select_flags |= dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
if cache:
select_flags |= dnsserver.DNS_RPC_VIEW_CACHE_DATA
if glue:
select_flags |= dnsserver.DNS_RPC_VIEW_GLUE_DATA
if root:
select_flags |= dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA
if additional:
select_flags |= dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA
if no_children:
select_flags |= dnsserver.DNS_RPC_VIEW_NO_CHILDREN
if only_children:
select_flags |= dnsserver.DNS_RPC_VIEW_ONLY_CHILDREN
if select_flags == 0:
select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
if select_flags == dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA:
self.outf.write('Specify either --authority or --root along with --additional.\n')
self.outf.write('Assuming --authority.\n')
select_flags |= dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
try:
buflen, res = dns_conn.DnssrvEnumRecords2(
dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, zone, name,
None, record_type, select_flags, None, None)
except WERRORError as e:
if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
raise CommandError('Record or zone does not exist.')
raise e
print_dnsrecords(self.outf, res)
class cmd_roothints(Command):
"""Query root hints."""
synopsis = '%prog <server> [<name>] [options]'
takes_args = ['server', 'name?']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
def run(self, server, name='.', sambaopts=None, credopts=None,
versionopts=None):
record_type = dnsp.DNS_TYPE_NS
select_flags = (dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA |
dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA)
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
buflen, res = dns_conn.DnssrvEnumRecords2(
dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, '..RootHints',
name, None, record_type, select_flags, None, None)
print_dnsrecords(self.outf, res)
class cmd_add_record(Command):
"""Add a DNS record
For each type data contents are as follows:
A ipv4_address_string
AAAA ipv6_address_string
PTR fqdn_string
CNAME fqdn_string
NS fqdn_string
MX "fqdn_string preference"
SRV "fqdn_string port priority weight"
TXT "'string1' 'string2' ..."
"""
synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SRV|TXT> <data>'
takes_args = ['server', 'zone', 'name', 'rtype', 'data']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
def run(self, server, zone, name, rtype, data, sambaopts=None,
credopts=None, versionopts=None):
if rtype.upper() not in ('A', 'AAAA', 'PTR', 'CNAME', 'NS', 'MX', 'SRV', 'TXT'):
raise CommandError('Adding record of type %s is not supported' % rtype)
record_type = dns_type_flag(rtype)
rec = data_to_dns_record(record_type, data)
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
add_rec_buf.rec = rec
try:
dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
0, server, zone, name, add_rec_buf, None)
except WERRORError as e:
if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
raise CommandError('Zone does not exist; record could not be added. zone[%s] name[%s]' % (zone, name))
if e.args[0] == werror.WERR_DNS_ERROR_RECORD_ALREADY_EXISTS:
raise CommandError('Record already exist; record could not be added. zone[%s] name[%s]' % (zone, name))
raise e
self.outf.write('Record added successfully\n')
class cmd_update_record(Command):
"""Update a DNS record
For each type data contents are as follows:
A ipv4_address_string
AAAA ipv6_address_string
PTR fqdn_string
CNAME fqdn_string
NS fqdn_string
MX "fqdn_string preference"
SOA "fqdn_dns fqdn_email serial refresh retry expire minimumttl"
SRV "fqdn_string port priority weight"
TXT "'string1' 'string2' ..."
"""
synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SOA|SRV|TXT> <olddata> <newdata>'
takes_args = ['server', 'zone', 'name', 'rtype', 'olddata', 'newdata']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
def run(self, server, zone, name, rtype, olddata, newdata,
sambaopts=None, credopts=None, versionopts=None):
rtype = rtype.upper()
if rtype not in ('A', 'AAAA', 'PTR', 'CNAME', 'NS', 'MX', 'SOA', 'SRV', 'TXT'):
raise CommandError('Updating record of type %s is not supported' % rtype)
try:
if rtype == 'A':
inet_pton(AF_INET, newdata)
elif rtype == 'AAAA':
inet_pton(AF_INET6, newdata)
except OSError as e:
raise CommandError(f"bad data for {rtype}: {e!r}")
record_type = dns_type_flag(rtype)
rec = data_to_dns_record(record_type, newdata)
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
try:
rec_match = dns_record_match(dns_conn, server, zone, name, record_type,
olddata)
except DNSParseError as e:
raise CommandError(*e.args) from None
if not rec_match:
raise CommandError('Record or zone does not exist.')
# Copy properties from existing record to new record
rec.dwFlags = rec_match.dwFlags
rec.dwSerial = rec_match.dwSerial
rec.dwTtlSeconds = rec_match.dwTtlSeconds
rec.dwTimeStamp = rec_match.dwTimeStamp
add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
add_rec_buf.rec = rec
del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
del_rec_buf.rec = rec_match
try:
dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
0,
server,
zone,
name,
add_rec_buf,
del_rec_buf)
except WERRORError as e:
if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
raise CommandError('Zone does not exist; record could not be updated.')
raise e
self.outf.write('Record updated successfully\n')
class cmd_delete_record(Command):
"""Delete a DNS record
For each type data contents are as follows:
A ipv4_address_string
AAAA ipv6_address_string
PTR fqdn_string
CNAME fqdn_string
NS fqdn_string
MX "fqdn_string preference"
SRV "fqdn_string port priority weight"
TXT "'string1' 'string2' ..."
"""
synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SRV|TXT> <data>'
takes_args = ['server', 'zone', 'name', 'rtype', 'data']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
def run(self, server, zone, name, rtype, data, sambaopts=None, credopts=None, versionopts=None):
if rtype.upper() not in ('A', 'AAAA', 'PTR', 'CNAME', 'NS', 'MX', 'SRV', 'TXT'):
raise CommandError('Deleting record of type %s is not supported' % rtype)
record_type = dns_type_flag(rtype)
rec = data_to_dns_record(record_type, data)
self.lp = sambaopts.get_loadparm()
self.creds = credopts.get_credentials(self.lp)
dns_conn = dns_connect(server, self.lp, self.creds)
del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
del_rec_buf.rec = rec
try:
dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
0,
server,
zone,
name,
None,
del_rec_buf)
except WERRORError as e:
if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
raise CommandError('Zone does not exist; record could not be deleted. zone[%s] name[%s]' % (zone, name))
if e.args[0] == werror.WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST:
raise CommandError('Record does not exist; record could not be deleted. zone[%s] name[%s]' % (zone, name))
raise e
self.outf.write('Record deleted successfully\n')
class cmd_cleanup_record(Command):
"""Cleanup DNS records for a DNS host.
example:
samba-tool dns cleanup dc1 dc1.samdom.test.site -U USER%PASSWORD
NOTE: This command in many cases will only mark the `dNSTombstoned` attr
as `TRUE` on the DNS records. Querying will no longer return results but
there may still be some placeholder entries in the database.
"""
synopsis = '%prog <server> <dnshostname>'
takes_args = ['server', 'dnshostname']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
takes_options = [
Option("-v", "--verbose", help="Be verbose", action="store_true"),
Option("-q", "--quiet", help="Be quiet", action="store_true"),
]
def run(self, server, dnshostname, sambaopts=None, credopts=None,
versionopts=None, verbose=False, quiet=False):
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp)
logger = self.get_logger(verbose=verbose, quiet=quiet)
samdb = SamDB(url="ldap://%s" % server,
session_info=system_session(),
credentials=creds, lp=lp)
remove_dc.remove_dns_references(samdb, logger, dnshostname,
ignore_no_name=True)
class cmd_dns(SuperCommand):
"""Domain Name Service (DNS) management."""
subcommands = {}
subcommands['serverinfo'] = cmd_serverinfo()
subcommands['zoneoptions'] = cmd_zoneoptions()
subcommands['zoneinfo'] = cmd_zoneinfo()
subcommands['zonelist'] = cmd_zonelist()
subcommands['zonecreate'] = cmd_zonecreate()
subcommands['zonedelete'] = cmd_zonedelete()
subcommands['query'] = cmd_query()
subcommands['roothints'] = cmd_roothints()
subcommands['add'] = cmd_add_record()
subcommands['update'] = cmd_update_record()
subcommands['delete'] = cmd_delete_record()
subcommands['cleanup'] = cmd_cleanup_record()