1# DNS management tool
2#
3# Copyright (C) Amitay Isaacs 2011-2012
4#
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.
9#
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.
14#
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/>.
17#
18import logging
19
20import samba.getopt as options
21from samba import WERRORError
22from samba import werror
23from struct import pack
24from socket import inet_ntoa
25from socket import inet_ntop
26from socket import AF_INET
27from socket import AF_INET6
28import shlex
29import struct
30
31from samba import remove_dc
32from samba.samdb import SamDB
33from samba.auth import system_session
34
35from samba.netcmd import (
36    Command,
37    CommandError,
38    Option,
39    SuperCommand,
40)
41from samba.dcerpc import dnsp, dnsserver
42
43from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord
44
45
46def dns_connect(server, lp, creds):
47    if server.lower() == 'localhost':
48        server = '127.0.0.1'
49    binding_str = "ncacn_ip_tcp:%s[sign]" % server
50    try:
51        dns_conn = dnsserver.dnsserver(binding_str, lp, creds)
52    except RuntimeError as e:
53        raise CommandError('Connecting to DNS RPC server %s failed with %s' % (server, e))
54
55    return dns_conn
56
57
58def bool_string(flag):
59    if flag == 0:
60        ret = 'FALSE'
61    elif flag == 1:
62        ret = 'TRUE'
63    else:
64        ret = 'UNKNOWN (0x%x)' % flag
65    return ret
66
67
68def enum_string(module, enum_defs, value):
69    ret = None
70    for e in enum_defs:
71        if value == getattr(module, e):
72            ret = e
73            break
74    if not ret:
75        ret = 'UNKNOWN (0x%x)' % value
76    return ret
77
78
79def bitmap_string(module, bitmap_defs, value):
80    ret = ''
81    for b in bitmap_defs:
82        if value & getattr(module, b):
83            ret += '%s ' % b
84    if not ret:
85        ret = 'NONE'
86    return ret
87
88
89def boot_method_string(boot_method):
90    enum_defs = ['DNS_BOOT_METHOD_UNINITIALIZED', 'DNS_BOOT_METHOD_FILE',
91                 'DNS_BOOT_METHOD_REGISTRY', 'DNS_BOOT_METHOD_DIRECTORY']
92    return enum_string(dnsserver, enum_defs, boot_method)
93
94
95def name_check_flag_string(check_flag):
96    enum_defs = ['DNS_ALLOW_RFC_NAMES_ONLY', 'DNS_ALLOW_NONRFC_NAMES',
97                 'DNS_ALLOW_MULTIBYTE_NAMES', 'DNS_ALLOW_ALL_NAMES']
98    return enum_string(dnsserver, enum_defs, check_flag)
99
100
101def zone_type_string(zone_type):
102    enum_defs = ['DNS_ZONE_TYPE_CACHE', 'DNS_ZONE_TYPE_PRIMARY',
103                 'DNS_ZONE_TYPE_SECONDARY', 'DNS_ZONE_TYPE_STUB',
104                 'DNS_ZONE_TYPE_FORWARDER', 'DNS_ZONE_TYPE_SECONDARY_CACHE']
105    return enum_string(dnsp, enum_defs, zone_type)
106
107
108def zone_update_string(zone_update):
109    enum_defs = ['DNS_ZONE_UPDATE_OFF', 'DNS_ZONE_UPDATE_UNSECURE',
110                 'DNS_ZONE_UPDATE_SECURE']
111    return enum_string(dnsp, enum_defs, zone_update)
112
113
114def zone_secondary_security_string(security):
115    enum_defs = ['DNS_ZONE_SECSECURE_NO_SECURITY', 'DNS_ZONE_SECSECURE_NS_ONLY',
116                 'DNS_ZONE_SECSECURE_LIST_ONLY', 'DNS_ZONE_SECSECURE_NO_XFER']
117    return enum_string(dnsserver, enum_defs, security)
118
119
120def zone_notify_level_string(notify_level):
121    enum_defs = ['DNS_ZONE_NOTIFY_OFF', 'DNS_ZONE_NOTIFY_ALL_SECONDARIES',
122                 'DNS_ZONE_NOTIFY_LIST_ONLY']
123    return enum_string(dnsserver, enum_defs, notify_level)
124
125
126def dp_flags_string(dp_flags):
127    bitmap_defs = ['DNS_DP_AUTOCREATED', 'DNS_DP_LEGACY', 'DNS_DP_DOMAIN_DEFAULT',
128                   'DNS_DP_FOREST_DEFAULT', 'DNS_DP_ENLISTED', 'DNS_DP_DELETED']
129    return bitmap_string(dnsserver, bitmap_defs, dp_flags)
130
131
132def zone_flags_string(flags):
133    bitmap_defs = ['DNS_RPC_ZONE_PAUSED', 'DNS_RPC_ZONE_SHUTDOWN',
134                   'DNS_RPC_ZONE_REVERSE', 'DNS_RPC_ZONE_AUTOCREATED',
135                   'DNS_RPC_ZONE_DSINTEGRATED', 'DNS_RPC_ZONE_AGING',
136                   'DNS_RPC_ZONE_UPDATE_UNSECURE', 'DNS_RPC_ZONE_UPDATE_SECURE',
137                   'DNS_RPC_ZONE_READONLY']
138    return bitmap_string(dnsserver, bitmap_defs, flags)
139
140
141def ip4_array_string(array):
142    ret = []
143    if not array:
144        return ret
145    for i in range(array.AddrCount):
146        addr = inet_ntop(AF_INET, pack('I', array.AddrArray[i]))
147        ret.append(addr)
148    return ret
149
150
151def dns_addr_array_string(array):
152    ret = []
153    if not array:
154        return ret
155    for i in range(array.AddrCount):
156        if array.AddrArray[i].MaxSa[0] == 0x02:
157            x = struct.pack('4B', *array.AddrArray[i].MaxSa[4:8])
158            addr = inet_ntop(AF_INET, x)
159        elif array.AddrArray[i].MaxSa[0] == 0x17:
160            x = struct.pack('16B', *array.AddrArray[i].MaxSa[8:24])
161            addr = inet_ntop(AF_INET6, x)
162        else:
163            addr = 'UNKNOWN'
164        ret.append(addr)
165    return ret
166
167
168def dns_type_flag(rec_type):
169    rtype = rec_type.upper()
170    if rtype == 'A':
171        record_type = dnsp.DNS_TYPE_A
172    elif rtype == 'AAAA':
173        record_type = dnsp.DNS_TYPE_AAAA
174    elif rtype == 'PTR':
175        record_type = dnsp.DNS_TYPE_PTR
176    elif rtype == 'NS':
177        record_type = dnsp.DNS_TYPE_NS
178    elif rtype == 'CNAME':
179        record_type = dnsp.DNS_TYPE_CNAME
180    elif rtype == 'SOA':
181        record_type = dnsp.DNS_TYPE_SOA
182    elif rtype == 'MX':
183        record_type = dnsp.DNS_TYPE_MX
184    elif rtype == 'SRV':
185        record_type = dnsp.DNS_TYPE_SRV
186    elif rtype == 'TXT':
187        record_type = dnsp.DNS_TYPE_TXT
188    elif rtype == 'ALL':
189        record_type = dnsp.DNS_TYPE_ALL
190    else:
191        raise CommandError('Unknown type of DNS record %s' % rec_type)
192    return record_type
193
194
195def dns_client_version(cli_version):
196    version = cli_version.upper()
197    if version == 'W2K':
198        client_version = dnsserver.DNS_CLIENT_VERSION_W2K
199    elif version == 'DOTNET':
200        client_version = dnsserver.DNS_CLIENT_VERSION_DOTNET
201    elif version == 'LONGHORN':
202        client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
203    else:
204        raise CommandError('Unknown client version %s' % cli_version)
205    return client_version
206
207
208def print_serverinfo(outf, typeid, serverinfo):
209    outf.write('  dwVersion                   : 0x%x\n' % serverinfo.dwVersion)
210    outf.write('  fBootMethod                 : %s\n' % boot_method_string(serverinfo.fBootMethod))
211    outf.write('  fAdminConfigured            : %s\n' % bool_string(serverinfo.fAdminConfigured))
212    outf.write('  fAllowUpdate                : %s\n' % bool_string(serverinfo.fAllowUpdate))
213    outf.write('  fDsAvailable                : %s\n' % bool_string(serverinfo.fDsAvailable))
214    outf.write('  pszServerName               : %s\n' % serverinfo.pszServerName)
215    outf.write('  pszDsContainer              : %s\n' % serverinfo.pszDsContainer)
216
217    if typeid != dnsserver.DNSSRV_TYPEID_SERVER_INFO:
218        outf.write('  aipServerAddrs              : %s\n' %
219                   ip4_array_string(serverinfo.aipServerAddrs))
220        outf.write('  aipListenAddrs              : %s\n' %
221                   ip4_array_string(serverinfo.aipListenAddrs))
222        outf.write('  aipForwarders               : %s\n' %
223                   ip4_array_string(serverinfo.aipForwarders))
224    else:
225        outf.write('  aipServerAddrs              : %s\n' %
226                   dns_addr_array_string(serverinfo.aipServerAddrs))
227        outf.write('  aipListenAddrs              : %s\n' %
228                   dns_addr_array_string(serverinfo.aipListenAddrs))
229        outf.write('  aipForwarders               : %s\n' %
230                   dns_addr_array_string(serverinfo.aipForwarders))
231
232    outf.write('  dwLogLevel                  : %d\n' % serverinfo.dwLogLevel)
233    outf.write('  dwDebugLevel                : %d\n' % serverinfo.dwDebugLevel)
234    outf.write('  dwForwardTimeout            : %d\n' % serverinfo.dwForwardTimeout)
235    outf.write('  dwRpcPrototol               : 0x%x\n' % serverinfo.dwRpcProtocol)
236    outf.write('  dwNameCheckFlag             : %s\n' % name_check_flag_string(serverinfo.dwNameCheckFlag))
237    outf.write('  cAddressAnswerLimit         : %d\n' % serverinfo.cAddressAnswerLimit)
238    outf.write('  dwRecursionRetry            : %d\n' % serverinfo.dwRecursionRetry)
239    outf.write('  dwRecursionTimeout          : %d\n' % serverinfo.dwRecursionTimeout)
240    outf.write('  dwMaxCacheTtl               : %d\n' % serverinfo.dwMaxCacheTtl)
241    outf.write('  dwDsPollingInterval         : %d\n' % serverinfo.dwDsPollingInterval)
242    outf.write('  dwScavengingInterval        : %d\n' % serverinfo.dwScavengingInterval)
243    outf.write('  dwDefaultRefreshInterval    : %d\n' % serverinfo.dwDefaultRefreshInterval)
244    outf.write('  dwDefaultNoRefreshInterval  : %d\n' % serverinfo.dwDefaultNoRefreshInterval)
245    outf.write('  fAutoReverseZones           : %s\n' % bool_string(serverinfo.fAutoReverseZones))
246    outf.write('  fAutoCacheUpdate            : %s\n' % bool_string(serverinfo.fAutoCacheUpdate))
247    outf.write('  fRecurseAfterForwarding     : %s\n' % bool_string(serverinfo.fRecurseAfterForwarding))
248    outf.write('  fForwardDelegations         : %s\n' % bool_string(serverinfo.fForwardDelegations))
249    outf.write('  fNoRecursion                : %s\n' % bool_string(serverinfo.fNoRecursion))
250    outf.write('  fSecureResponses            : %s\n' % bool_string(serverinfo.fSecureResponses))
251    outf.write('  fRoundRobin                 : %s\n' % bool_string(serverinfo.fRoundRobin))
252    outf.write('  fLocalNetPriority           : %s\n' % bool_string(serverinfo.fLocalNetPriority))
253    outf.write('  fBindSecondaries            : %s\n' % bool_string(serverinfo.fBindSecondaries))
254    outf.write('  fWriteAuthorityNs           : %s\n' % bool_string(serverinfo.fWriteAuthorityNs))
255    outf.write('  fStrictFileParsing          : %s\n' % bool_string(serverinfo.fStrictFileParsing))
256    outf.write('  fLooseWildcarding           : %s\n' % bool_string(serverinfo.fLooseWildcarding))
257    outf.write('  fDefaultAgingState          : %s\n' % bool_string(serverinfo.fDefaultAgingState))
258
259    if typeid != dnsserver.DNSSRV_TYPEID_SERVER_INFO_W2K:
260        outf.write('  dwRpcStructureVersion       : 0x%x\n' % serverinfo.dwRpcStructureVersion)
261        outf.write('  aipLogFilter                : %s\n' % dns_addr_array_string(serverinfo.aipLogFilter))
262        outf.write('  pwszLogFilePath             : %s\n' % serverinfo.pwszLogFilePath)
263        outf.write('  pszDomainName               : %s\n' % serverinfo.pszDomainName)
264        outf.write('  pszForestName               : %s\n' % serverinfo.pszForestName)
265        outf.write('  pszDomainDirectoryPartition : %s\n' % serverinfo.pszDomainDirectoryPartition)
266        outf.write('  pszForestDirectoryPartition : %s\n' % serverinfo.pszForestDirectoryPartition)
267
268        outf.write('  dwLocalNetPriorityNetMask   : 0x%x\n' % serverinfo.dwLocalNetPriorityNetMask)
269        outf.write('  dwLastScavengeTime          : %d\n' % serverinfo.dwLastScavengeTime)
270        outf.write('  dwEventLogLevel             : %d\n' % serverinfo.dwEventLogLevel)
271        outf.write('  dwLogFileMaxSize            : %d\n' % serverinfo.dwLogFileMaxSize)
272        outf.write('  dwDsForestVersion           : %d\n' % serverinfo.dwDsForestVersion)
273        outf.write('  dwDsDomainVersion           : %d\n' % serverinfo.dwDsDomainVersion)
274        outf.write('  dwDsDsaVersion              : %d\n' % serverinfo.dwDsDsaVersion)
275
276    if typeid == dnsserver.DNSSRV_TYPEID_SERVER_INFO:
277        outf.write('  fReadOnlyDC                 : %s\n' % bool_string(serverinfo.fReadOnlyDC))
278
279
280def print_zoneinfo(outf, typeid, zoneinfo):
281    outf.write('  pszZoneName                 : %s\n' % zoneinfo.pszZoneName)
282    outf.write('  dwZoneType                  : %s\n' % zone_type_string(zoneinfo.dwZoneType))
283    outf.write('  fReverse                    : %s\n' % bool_string(zoneinfo.fReverse))
284    outf.write('  fAllowUpdate                : %s\n' % zone_update_string(zoneinfo.fAllowUpdate))
285    outf.write('  fPaused                     : %s\n' % bool_string(zoneinfo.fPaused))
286    outf.write('  fShutdown                   : %s\n' % bool_string(zoneinfo.fShutdown))
287    outf.write('  fAutoCreated                : %s\n' % bool_string(zoneinfo.fAutoCreated))
288    outf.write('  fUseDatabase                : %s\n' % bool_string(zoneinfo.fUseDatabase))
289    outf.write('  pszDataFile                 : %s\n' % zoneinfo.pszDataFile)
290    if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
291        outf.write('  aipMasters                  : %s\n' %
292                   ip4_array_string(zoneinfo.aipMasters))
293    else:
294        outf.write('  aipMasters                  : %s\n' %
295                   dns_addr_array_string(zoneinfo.aipMasters))
296    outf.write('  fSecureSecondaries          : %s\n' % zone_secondary_security_string(zoneinfo.fSecureSecondaries))
297    outf.write('  fNotifyLevel                : %s\n' % zone_notify_level_string(zoneinfo.fNotifyLevel))
298    if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
299        outf.write('  aipSecondaries              : %s\n' %
300                   ip4_array_string(zoneinfo.aipSecondaries))
301        outf.write('  aipNotify                   : %s\n' %
302                   ip4_array_string(zoneinfo.aipNotify))
303    else:
304        outf.write('  aipSecondaries              : %s\n' %
305                   dns_addr_array_string(zoneinfo.aipSecondaries))
306        outf.write('  aipNotify                   : %s\n' %
307                   dns_addr_array_string(zoneinfo.aipNotify))
308    outf.write('  fUseWins                    : %s\n' % bool_string(zoneinfo.fUseWins))
309    outf.write('  fUseNbstat                  : %s\n' % bool_string(zoneinfo.fUseNbstat))
310    outf.write('  fAging                      : %s\n' % bool_string(zoneinfo.fAging))
311    outf.write('  dwNoRefreshInterval         : %d\n' % zoneinfo.dwNoRefreshInterval)
312    outf.write('  dwRefreshInterval           : %d\n' % zoneinfo.dwRefreshInterval)
313    outf.write('  dwAvailForScavengeTime      : %d\n' % zoneinfo.dwAvailForScavengeTime)
314    if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
315        outf.write('  aipScavengeServers          : %s\n' %
316                   ip4_array_string(zoneinfo.aipScavengeServers))
317    else:
318        outf.write('  aipScavengeServers          : %s\n' %
319                   dns_addr_array_string(zoneinfo.aipScavengeServers))
320
321    if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO_W2K:
322        outf.write('  dwRpcStructureVersion       : 0x%x\n' % zoneinfo.dwRpcStructureVersion)
323        outf.write('  dwForwarderTimeout          : %d\n' % zoneinfo.dwForwarderTimeout)
324        outf.write('  fForwarderSlave             : %d\n' % zoneinfo.fForwarderSlave)
325        if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
326            outf.write('  aipLocalMasters             : %s\n' %
327                       ip4_array_string(zoneinfo.aipLocalMasters))
328        else:
329            outf.write('  aipLocalMasters             : %s\n' %
330                       dns_addr_array_string(zoneinfo.aipLocalMasters))
331        outf.write('  dwDpFlags                   : %s\n' % dp_flags_string(zoneinfo.dwDpFlags))
332        outf.write('  pszDpFqdn                   : %s\n' % zoneinfo.pszDpFqdn)
333        outf.write('  pwszZoneDn                  : %s\n' % zoneinfo.pwszZoneDn)
334        outf.write('  dwLastSuccessfulSoaCheck    : %d\n' % zoneinfo.dwLastSuccessfulSoaCheck)
335        outf.write('  dwLastSuccessfulXfr         : %d\n' % zoneinfo.dwLastSuccessfulXfr)
336
337    if typeid == dnsserver.DNSSRV_TYPEID_ZONE_INFO:
338        outf.write('  fQueuedForBackgroundLoad    : %s\n' % bool_string(zoneinfo.fQueuedForBackgroundLoad))
339        outf.write('  fBackgroundLoadInProgress   : %s\n' % bool_string(zoneinfo.fBackgroundLoadInProgress))
340        outf.write('  fReadOnlyZone               : %s\n' % bool_string(zoneinfo.fReadOnlyZone))
341        outf.write('  dwLastXfrAttempt            : %d\n' % zoneinfo.dwLastXfrAttempt)
342        outf.write('  dwLastXfrResult             : %d\n' % zoneinfo.dwLastXfrResult)
343
344
345def print_zone(outf, typeid, zone):
346    outf.write('  pszZoneName                 : %s\n' % zone.pszZoneName)
347    outf.write('  Flags                       : %s\n' % zone_flags_string(zone.Flags))
348    outf.write('  ZoneType                    : %s\n' % zone_type_string(zone.ZoneType))
349    outf.write('  Version                     : %s\n' % zone.Version)
350
351    if typeid != dnsserver.DNSSRV_TYPEID_ZONE_W2K:
352        outf.write('  dwDpFlags                   : %s\n' % dp_flags_string(zone.dwDpFlags))
353        outf.write('  pszDpFqdn                   : %s\n' % zone.pszDpFqdn)
354
355
356def print_enumzones(outf, typeid, zones):
357    outf.write('  %d zone(s) found\n' % zones.dwZoneCount)
358    for zone in zones.ZoneArray:
359        outf.write('\n')
360        print_zone(outf, typeid, zone)
361
362
363def print_dns_record(outf, rec):
364    if rec.wType == dnsp.DNS_TYPE_A:
365        mesg = 'A: %s' % (rec.data)
366    elif rec.wType == dnsp.DNS_TYPE_AAAA:
367        mesg = 'AAAA: %s' % (rec.data)
368    elif rec.wType == dnsp.DNS_TYPE_PTR:
369        mesg = 'PTR: %s' % (rec.data.str)
370    elif rec.wType == dnsp.DNS_TYPE_NS:
371        mesg = 'NS: %s' % (rec.data.str)
372    elif rec.wType == dnsp.DNS_TYPE_CNAME:
373        mesg = 'CNAME: %s' % (rec.data.str)
374    elif rec.wType == dnsp.DNS_TYPE_SOA:
375        mesg = 'SOA: serial=%d, refresh=%d, retry=%d, expire=%d, minttl=%d, ns=%s, email=%s' % (
376                    rec.data.dwSerialNo,
377                    rec.data.dwRefresh,
378                    rec.data.dwRetry,
379                    rec.data.dwExpire,
380                    rec.data.dwMinimumTtl,
381                    rec.data.NamePrimaryServer.str,
382                    rec.data.ZoneAdministratorEmail.str)
383    elif rec.wType == dnsp.DNS_TYPE_MX:
384        mesg = 'MX: %s (%d)' % (rec.data.nameExchange.str, rec.data.wPreference)
385    elif rec.wType == dnsp.DNS_TYPE_SRV:
386        mesg = 'SRV: %s (%d, %d, %d)' % (rec.data.nameTarget.str, rec.data.wPort,
387                                         rec.data.wPriority, rec.data.wWeight)
388    elif rec.wType == dnsp.DNS_TYPE_TXT:
389        slist = ['"%s"' % name.str for name in rec.data.str]
390        mesg = 'TXT: %s' % ','.join(slist)
391    else:
392        mesg = 'Unknown: '
393    outf.write('    %s (flags=%x, serial=%d, ttl=%d)\n' % (
394                mesg, rec.dwFlags, rec.dwSerial, rec.dwTtlSeconds))
395
396
397def print_dnsrecords(outf, records):
398    for rec in records.rec:
399        outf.write('  Name=%s, Records=%d, Children=%d\n' % (
400                    rec.dnsNodeName.str,
401                    rec.wRecordCount,
402                    rec.dwChildCount))
403        for dns_rec in rec.records:
404                print_dns_record(outf, dns_rec)
405
406
407# Convert data into a dns record
408def data_to_dns_record(record_type, data):
409    if record_type == dnsp.DNS_TYPE_A:
410        rec = ARecord(data)
411    elif record_type == dnsp.DNS_TYPE_AAAA:
412        rec = AAAARecord(data)
413    elif record_type == dnsp.DNS_TYPE_PTR:
414        rec = PTRRecord(data)
415    elif record_type == dnsp.DNS_TYPE_CNAME:
416        rec = CNameRecord(data)
417    elif record_type == dnsp.DNS_TYPE_NS:
418        rec = NSRecord(data)
419    elif record_type == dnsp.DNS_TYPE_MX:
420        tmp = data.split()
421        if len(tmp) != 2:
422            raise CommandError('Data requires 2 elements - mail_server, preference')
423        mail_server = tmp[0]
424        preference = int(tmp[1])
425        rec = MXRecord(mail_server, preference)
426    elif record_type == dnsp.DNS_TYPE_SRV:
427        tmp = data.split()
428        if len(tmp) != 4:
429            raise CommandError('Data requires 4 elements - server, port, priority, weight')
430        server = tmp[0]
431        port = int(tmp[1])
432        priority = int(tmp[2])
433        weight = int(tmp[3])
434        rec = SRVRecord(server, port, priority=priority, weight=weight)
435    elif record_type == dnsp.DNS_TYPE_SOA:
436        tmp = data.split()
437        if len(tmp) != 7:
438            raise CommandError('Data requires 7 elements - nameserver, email, serial, '
439                               'refresh, retry, expire, minimumttl')
440        nameserver = tmp[0]
441        email = tmp[1]
442        serial = int(tmp[2])
443        refresh = int(tmp[3])
444        retry = int(tmp[4])
445        expire = int(tmp[5])
446        minimum = int(tmp[6])
447        rec = SOARecord(nameserver, email, serial=serial, refresh=refresh,
448                        retry=retry, expire=expire, minimum=minimum)
449    elif record_type == dnsp.DNS_TYPE_TXT:
450        slist = shlex.split(data)
451        rec = TXTRecord(slist)
452    else:
453        raise CommandError('Unsupported record type')
454    return rec
455
456
457# Match dns name (of type DNS_RPC_NAME)
458def dns_name_equal(n1, n2):
459    return n1.str.rstrip('.').lower() == n2.str.rstrip('.').lower()
460
461
462# Match a dns record with specified data
463def dns_record_match(dns_conn, server, zone, name, record_type, data):
464    urec = data_to_dns_record(record_type, data)
465
466    select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
467
468    try:
469        buflen, res = dns_conn.DnssrvEnumRecords2(
470            dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, zone, name, None,
471            record_type, select_flags, None, None)
472    except WERRORError as e:
473        if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
474            # Either the zone doesn't exist, or there were no records.
475            # We can't differentiate the two.
476            return None
477        raise e
478
479    if not res or res.count == 0:
480        return None
481
482    for rec in res.rec[0].records:
483        if rec.wType != record_type:
484            continue
485
486        found = False
487        if record_type == dnsp.DNS_TYPE_A:
488            if rec.data == urec.data:
489                found = True
490        elif record_type == dnsp.DNS_TYPE_AAAA:
491            if rec.data == urec.data:
492                found = True
493        elif record_type == dnsp.DNS_TYPE_PTR:
494            if dns_name_equal(rec.data, urec.data):
495                found = True
496        elif record_type == dnsp.DNS_TYPE_CNAME:
497            if dns_name_equal(rec.data, urec.data):
498                found = True
499        elif record_type == dnsp.DNS_TYPE_NS:
500            if dns_name_equal(rec.data, urec.data):
501                found = True
502        elif record_type == dnsp.DNS_TYPE_MX:
503            if dns_name_equal(rec.data.nameExchange, urec.data.nameExchange) and \
504               rec.data.wPreference == urec.data.wPreference:
505                found = True
506        elif record_type == dnsp.DNS_TYPE_SRV:
507            if rec.data.wPriority == urec.data.wPriority and \
508               rec.data.wWeight == urec.data.wWeight and \
509               rec.data.wPort == urec.data.wPort and \
510               dns_name_equal(rec.data.nameTarget, urec.data.nameTarget):
511                found = True
512        elif record_type == dnsp.DNS_TYPE_SOA:
513            if rec.data.dwSerialNo == urec.data.dwSerialNo and \
514               rec.data.dwRefresh == urec.data.dwRefresh and \
515               rec.data.dwRetry == urec.data.dwRetry and \
516               rec.data.dwExpire == urec.data.dwExpire and \
517               rec.data.dwMinimumTtl == urec.data.dwMinimumTtl and \
518               dns_name_equal(rec.data.NamePrimaryServer,
519                              urec.data.NamePrimaryServer) and \
520               dns_name_equal(rec.data.ZoneAdministratorEmail,
521                              urec.data.ZoneAdministratorEmail):
522                found = True
523        elif record_type == dnsp.DNS_TYPE_TXT:
524            if rec.data.count == urec.data.count:
525                found = True
526                for i in range(rec.data.count):
527                    found = found and \
528                            (rec.data.str[i].str == urec.data.str[i].str)
529
530        if found:
531            return rec
532
533    return None
534
535
536class cmd_serverinfo(Command):
537    """Query for Server information."""
538
539    synopsis = '%prog <server> [options]'
540
541    takes_args = ['server']
542
543    takes_optiongroups = {
544        "sambaopts": options.SambaOptions,
545        "versionopts": options.VersionOptions,
546        "credopts": options.CredentialsOptions,
547    }
548
549    takes_options = [
550        Option('--client-version', help='Client Version',
551               default='longhorn', metavar='w2k|dotnet|longhorn',
552               choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
553    ]
554
555    def run(self, server, cli_ver, sambaopts=None, credopts=None,
556            versionopts=None):
557        self.lp = sambaopts.get_loadparm()
558        self.creds = credopts.get_credentials(self.lp)
559        dns_conn = dns_connect(server, self.lp, self.creds)
560
561        client_version = dns_client_version(cli_ver)
562
563        typeid, res = dns_conn.DnssrvQuery2(client_version, 0, server,
564                                            None, 'ServerInfo')
565        print_serverinfo(self.outf, typeid, res)
566
567
568class cmd_zoneinfo(Command):
569    """Query for zone information."""
570
571    synopsis = '%prog <server> <zone> [options]'
572
573    takes_args = ['server', 'zone']
574
575    takes_optiongroups = {
576        "sambaopts": options.SambaOptions,
577        "versionopts": options.VersionOptions,
578        "credopts": options.CredentialsOptions,
579    }
580
581    takes_options = [
582        Option('--client-version', help='Client Version',
583               default='longhorn', metavar='w2k|dotnet|longhorn',
584               choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
585    ]
586
587    def run(self, server, zone, cli_ver, sambaopts=None, credopts=None,
588            versionopts=None):
589        self.lp = sambaopts.get_loadparm()
590        self.creds = credopts.get_credentials(self.lp)
591        dns_conn = dns_connect(server, self.lp, self.creds)
592
593        client_version = dns_client_version(cli_ver)
594
595        typeid, res = dns_conn.DnssrvQuery2(client_version, 0, server, zone,
596                                            'ZoneInfo')
597        print_zoneinfo(self.outf, typeid, res)
598
599
600class cmd_zonelist(Command):
601    """Query for zones."""
602
603    synopsis = '%prog <server> [options]'
604
605    takes_args = ['server']
606
607    takes_optiongroups = {
608        "sambaopts": options.SambaOptions,
609        "versionopts": options.VersionOptions,
610        "credopts": options.CredentialsOptions,
611    }
612
613    takes_options = [
614        Option('--client-version', help='Client Version',
615               default='longhorn', metavar='w2k|dotnet|longhorn',
616               choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver'),
617        Option('--primary', help='List primary zones (default)',
618               action='store_true', dest='primary'),
619        Option('--secondary', help='List secondary zones',
620               action='store_true', dest='secondary'),
621        Option('--cache', help='List cached zones',
622               action='store_true', dest='cache'),
623        Option('--auto', help='List automatically created zones',
624               action='store_true', dest='auto'),
625        Option('--forward', help='List forward zones',
626               action='store_true', dest='forward'),
627        Option('--reverse', help='List reverse zones',
628               action='store_true', dest='reverse'),
629        Option('--ds', help='List directory integrated zones',
630               action='store_true', dest='ds'),
631        Option('--non-ds', help='List non-directory zones',
632               action='store_true', dest='nonds')
633    ]
634
635    def run(self, server, cli_ver, primary=False, secondary=False, cache=False,
636            auto=False, forward=False, reverse=False, ds=False, nonds=False,
637            sambaopts=None, credopts=None, versionopts=None):
638        request_filter = 0
639
640        if primary:
641            request_filter |= dnsserver.DNS_ZONE_REQUEST_PRIMARY
642        if secondary:
643            request_filter |= dnsserver.DNS_ZONE_REQUEST_SECONDARY
644        if cache:
645            request_filter |= dnsserver.DNS_ZONE_REQUEST_CACHE
646        if auto:
647            request_filter |= dnsserver.DNS_ZONE_REQUEST_AUTO
648        if forward:
649            request_filter |= dnsserver.DNS_ZONE_REQUEST_FORWARD
650        if reverse:
651            request_filter |= dnsserver.DNS_ZONE_REQUEST_REVERSE
652        if ds:
653            request_filter |= dnsserver.DNS_ZONE_REQUEST_DS
654        if nonds:
655            request_filter |= dnsserver.DNS_ZONE_REQUEST_NON_DS
656
657        if request_filter == 0:
658            request_filter = dnsserver.DNS_ZONE_REQUEST_PRIMARY
659
660        self.lp = sambaopts.get_loadparm()
661        self.creds = credopts.get_credentials(self.lp)
662        dns_conn = dns_connect(server, self.lp, self.creds)
663
664        client_version = dns_client_version(cli_ver)
665
666        typeid, res = dns_conn.DnssrvComplexOperation2(client_version,
667                                                       0, server, None,
668                                                       'EnumZones',
669                                                       dnsserver.DNSSRV_TYPEID_DWORD,
670                                                       request_filter)
671
672        if client_version == dnsserver.DNS_CLIENT_VERSION_W2K:
673            typeid = dnsserver.DNSSRV_TYPEID_ZONE_W2K
674        else:
675            typeid = dnsserver.DNSSRV_TYPEID_ZONE
676        print_enumzones(self.outf, typeid, res)
677
678
679class cmd_zonecreate(Command):
680    """Create a zone."""
681
682    synopsis = '%prog <server> <zone> [options]'
683
684    takes_args = ['server', 'zone']
685
686    takes_optiongroups = {
687        "sambaopts": options.SambaOptions,
688        "versionopts": options.VersionOptions,
689        "credopts": options.CredentialsOptions,
690    }
691
692    takes_options = [
693        Option('--client-version', help='Client Version',
694               default='longhorn', metavar='w2k|dotnet|longhorn',
695               choices=['w2k', 'dotnet', 'longhorn'], dest='cli_ver')
696    ]
697
698    def run(self, server, zone, cli_ver, sambaopts=None, credopts=None,
699            versionopts=None):
700
701        self.lp = sambaopts.get_loadparm()
702        self.creds = credopts.get_credentials(self.lp)
703        dns_conn = dns_connect(server, self.lp, self.creds)
704
705        zone = zone.lower()
706
707        client_version = dns_client_version(cli_ver)
708        if client_version == dnsserver.DNS_CLIENT_VERSION_W2K:
709            typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE_W2K
710            zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_W2K()
711            zone_create_info.pszZoneName = zone
712            zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
713            zone_create_info.fAging = 0
714            zone_create_info.fDsIntegrated = 1
715            zone_create_info.fLoadExisting = 1
716        elif client_version == dnsserver.DNS_CLIENT_VERSION_DOTNET:
717            typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE_DOTNET
718            zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_DOTNET()
719            zone_create_info.pszZoneName = zone
720            zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
721            zone_create_info.fAging = 0
722            zone_create_info.fDsIntegrated = 1
723            zone_create_info.fLoadExisting = 1
724            zone_create_info.dwDpFlags = dnsserver.DNS_DP_DOMAIN_DEFAULT
725        else:
726            typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE
727            zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_LONGHORN()
728            zone_create_info.pszZoneName = zone
729            zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
730            zone_create_info.fAging = 0
731            zone_create_info.fDsIntegrated = 1
732            zone_create_info.fLoadExisting = 1
733            zone_create_info.dwDpFlags = dnsserver.DNS_DP_DOMAIN_DEFAULT
734
735        res = dns_conn.DnssrvOperation2(client_version, 0, server, None,
736                                        0, 'ZoneCreate', typeid,
737                                        zone_create_info)
738
739        typeid = dnsserver.DNSSRV_TYPEID_NAME_AND_PARAM
740        name_and_param = dnsserver.DNS_RPC_NAME_AND_PARAM()
741        name_and_param.pszNodeName = 'AllowUpdate'
742        name_and_param.dwParam = dnsp.DNS_ZONE_UPDATE_SECURE
743
744        try:
745            res = dns_conn.DnssrvOperation2(client_version, 0, server, zone,
746                                            0, 'ResetDwordProperty', typeid,
747                                            name_and_param)
748        except WERRORError as e:
749            if e.args[0] == werror.WERR_DNS_ERROR_ZONE_ALREADY_EXISTS:
750                self.outf.write('Zone already exists.')
751            raise e
752
753        self.outf.write('Zone %s created successfully\n' % zone)
754
755
756class cmd_zonedelete(Command):
757    """Delete a zone."""
758
759    synopsis = '%prog <server> <zone> [options]'
760
761    takes_args = ['server', 'zone']
762
763    takes_optiongroups = {
764        "sambaopts": options.SambaOptions,
765        "versionopts": options.VersionOptions,
766        "credopts": options.CredentialsOptions,
767    }
768
769    def run(self, server, zone, sambaopts=None, credopts=None,
770            versionopts=None):
771
772        self.lp = sambaopts.get_loadparm()
773        self.creds = credopts.get_credentials(self.lp)
774        dns_conn = dns_connect(server, self.lp, self.creds)
775
776        zone = zone.lower()
777        try:
778            res = dns_conn.DnssrvOperation2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
779                                            0, server, zone, 0, 'DeleteZoneFromDs',
780                                            dnsserver.DNSSRV_TYPEID_NULL,
781                                            None)
782        except WERRORError as e:
783            if e.args[0] == werror.WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST:
784                raise CommandError('Zone does not exist and so could not be deleted.')
785            raise e
786
787        self.outf.write('Zone %s deleted successfully\n' % zone)
788
789
790class cmd_query(Command):
791    """Query a name."""
792
793    synopsis = '%prog <server> <zone> <name> <A|AAAA|CNAME|MX|NS|SOA|SRV|TXT|ALL> [options]'
794
795    takes_args = ['server', 'zone', 'name', 'rtype']
796
797    takes_optiongroups = {
798        "sambaopts": options.SambaOptions,
799        "versionopts": options.VersionOptions,
800        "credopts": options.CredentialsOptions,
801    }
802
803    takes_options = [
804        Option('--authority', help='Search authoritative records (default)',
805               action='store_true', dest='authority'),
806        Option('--cache', help='Search cached records',
807               action='store_true', dest='cache'),
808        Option('--glue', help='Search glue records',
809               action='store_true', dest='glue'),
810        Option('--root', help='Search root hints',
811               action='store_true', dest='root'),
812        Option('--additional', help='List additional records',
813               action='store_true', dest='additional'),
814        Option('--no-children', help='Do not list children',
815               action='store_true', dest='no_children'),
816        Option('--only-children', help='List only children',
817               action='store_true', dest='only_children')
818    ]
819
820    def run(self, server, zone, name, rtype, authority=False, cache=False,
821            glue=False, root=False, additional=False, no_children=False,
822            only_children=False, sambaopts=None, credopts=None,
823            versionopts=None):
824        record_type = dns_type_flag(rtype)
825
826        if name.find('*') != -1:
827            self.outf.write('use "@" to dump entire domain, looking up %s\n' %
828                            name)
829
830        select_flags = 0
831        if authority:
832            select_flags |= dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
833        if cache:
834            select_flags |= dnsserver.DNS_RPC_VIEW_CACHE_DATA
835        if glue:
836            select_flags |= dnsserver.DNS_RPC_VIEW_GLUE_DATA
837        if root:
838            select_flags |= dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA
839        if additional:
840            select_flags |= dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA
841        if no_children:
842            select_flags |= dnsserver.DNS_RPC_VIEW_NO_CHILDREN
843        if only_children:
844            select_flags |= dnsserver.DNS_RPC_VIEW_ONLY_CHILDREN
845
846        if select_flags == 0:
847            select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
848
849        if select_flags == dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA:
850            self.outf.write('Specify either --authority or --root along with --additional.\n')
851            self.outf.write('Assuming --authority.\n')
852            select_flags |= dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
853
854        self.lp = sambaopts.get_loadparm()
855        self.creds = credopts.get_credentials(self.lp)
856        dns_conn = dns_connect(server, self.lp, self.creds)
857
858        try:
859            buflen, res = dns_conn.DnssrvEnumRecords2(
860                dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, zone, name,
861                None, record_type, select_flags, None, None)
862        except WERRORError as e:
863            if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
864                raise CommandError('Record or zone does not exist.')
865            raise e
866
867        print_dnsrecords(self.outf, res)
868
869
870class cmd_roothints(Command):
871    """Query root hints."""
872
873    synopsis = '%prog <server> [<name>] [options]'
874
875    takes_args = ['server', 'name?']
876
877    takes_optiongroups = {
878        "sambaopts": options.SambaOptions,
879        "versionopts": options.VersionOptions,
880        "credopts": options.CredentialsOptions,
881    }
882
883    def run(self, server, name='.', sambaopts=None, credopts=None,
884            versionopts=None):
885        record_type = dnsp.DNS_TYPE_NS
886        select_flags = (dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA |
887                        dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA)
888
889        self.lp = sambaopts.get_loadparm()
890        self.creds = credopts.get_credentials(self.lp)
891        dns_conn = dns_connect(server, self.lp, self.creds)
892
893        buflen, res = dns_conn.DnssrvEnumRecords2(
894            dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, '..RootHints',
895            name, None, record_type, select_flags, None, None)
896        print_dnsrecords(self.outf, res)
897
898
899class cmd_add_record(Command):
900    """Add a DNS record
901
902       For each type data contents are as follows:
903         A      ipv4_address_string
904         AAAA   ipv6_address_string
905         PTR    fqdn_string
906         CNAME  fqdn_string
907         NS     fqdn_string
908         MX     "fqdn_string preference"
909         SRV    "fqdn_string port priority weight"
910         TXT    "'string1' 'string2' ..."
911    """
912
913    synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SRV|TXT> <data>'
914
915    takes_args = ['server', 'zone', 'name', 'rtype', 'data']
916
917    takes_optiongroups = {
918        "sambaopts": options.SambaOptions,
919        "versionopts": options.VersionOptions,
920        "credopts": options.CredentialsOptions,
921    }
922
923    def run(self, server, zone, name, rtype, data, sambaopts=None,
924            credopts=None, versionopts=None):
925
926        if rtype.upper() not in ('A', 'AAAA', 'PTR', 'CNAME', 'NS', 'MX', 'SRV', 'TXT'):
927            raise CommandError('Adding record of type %s is not supported' % rtype)
928
929        record_type = dns_type_flag(rtype)
930        rec = data_to_dns_record(record_type, data)
931
932        self.lp = sambaopts.get_loadparm()
933        self.creds = credopts.get_credentials(self.lp)
934        dns_conn = dns_connect(server, self.lp, self.creds)
935
936        add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
937        add_rec_buf.rec = rec
938
939        try:
940            dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
941                                         0, server, zone, name, add_rec_buf, None)
942        except WERRORError as e:
943            if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
944                raise CommandError('Zone does not exist; record could not be added.')
945            raise e
946
947        self.outf.write('Record added successfully\n')
948
949
950class cmd_update_record(Command):
951    """Update a DNS record
952
953       For each type data contents are as follows:
954         A      ipv4_address_string
955         AAAA   ipv6_address_string
956         PTR    fqdn_string
957         CNAME  fqdn_string
958         NS     fqdn_string
959         MX     "fqdn_string preference"
960         SOA    "fqdn_dns fqdn_email serial refresh retry expire minimumttl"
961         SRV    "fqdn_string port priority weight"
962         TXT    "'string1' 'string2' ..."
963    """
964
965    synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SOA|SRV|TXT> <olddata> <newdata>'
966
967    takes_args = ['server', 'zone', 'name', 'rtype', 'olddata', 'newdata']
968
969    takes_optiongroups = {
970        "sambaopts": options.SambaOptions,
971        "versionopts": options.VersionOptions,
972        "credopts": options.CredentialsOptions,
973    }
974
975    def run(self, server, zone, name, rtype, olddata, newdata,
976            sambaopts=None, credopts=None, versionopts=None):
977
978        if rtype.upper() not in ('A', 'AAAA', 'PTR', 'CNAME', 'NS', 'MX', 'SOA', 'SRV', 'TXT'):
979            raise CommandError('Updating record of type %s is not supported' % rtype)
980
981        record_type = dns_type_flag(rtype)
982        rec = data_to_dns_record(record_type, newdata)
983
984        self.lp = sambaopts.get_loadparm()
985        self.creds = credopts.get_credentials(self.lp)
986        dns_conn = dns_connect(server, self.lp, self.creds)
987
988        rec_match = dns_record_match(dns_conn, server, zone, name, record_type,
989                                     olddata)
990        if not rec_match:
991            raise CommandError('Record or zone does not exist.')
992
993        # Copy properties from existing record to new record
994        rec.dwFlags = rec_match.dwFlags
995        rec.dwSerial = rec_match.dwSerial
996        rec.dwTtlSeconds = rec_match.dwTtlSeconds
997        rec.dwTimeStamp = rec_match.dwTimeStamp
998
999        add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1000        add_rec_buf.rec = rec
1001
1002        del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1003        del_rec_buf.rec = rec_match
1004
1005        try:
1006            dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
1007                                         0,
1008                                         server,
1009                                         zone,
1010                                         name,
1011                                         add_rec_buf,
1012                                         del_rec_buf)
1013        except WERRORError as e:
1014            if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1015                raise CommandError('Zone does not exist; record could not be updated.')
1016            raise e
1017
1018        self.outf.write('Record updated successfully\n')
1019
1020
1021class cmd_delete_record(Command):
1022    """Delete a DNS record
1023
1024       For each type data contents are as follows:
1025         A      ipv4_address_string
1026         AAAA   ipv6_address_string
1027         PTR    fqdn_string
1028         CNAME  fqdn_string
1029         NS     fqdn_string
1030         MX     "fqdn_string preference"
1031         SRV    "fqdn_string port priority weight"
1032         TXT    "'string1' 'string2' ..."
1033    """
1034
1035    synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SRV|TXT> <data>'
1036
1037    takes_args = ['server', 'zone', 'name', 'rtype', 'data']
1038
1039    takes_optiongroups = {
1040        "sambaopts": options.SambaOptions,
1041        "versionopts": options.VersionOptions,
1042        "credopts": options.CredentialsOptions,
1043    }
1044
1045    def run(self, server, zone, name, rtype, data, sambaopts=None, credopts=None, versionopts=None):
1046
1047        if rtype.upper() not in ('A', 'AAAA', 'PTR', 'CNAME', 'NS', 'MX', 'SRV', 'TXT'):
1048            raise CommandError('Deleting record of type %s is not supported' % rtype)
1049
1050        record_type = dns_type_flag(rtype)
1051        rec = data_to_dns_record(record_type, data)
1052
1053        self.lp = sambaopts.get_loadparm()
1054        self.creds = credopts.get_credentials(self.lp)
1055        dns_conn = dns_connect(server, self.lp, self.creds)
1056
1057        del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1058        del_rec_buf.rec = rec
1059
1060        try:
1061            dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
1062                                         0,
1063                                         server,
1064                                         zone,
1065                                         name,
1066                                         None,
1067                                         del_rec_buf)
1068        except WERRORError as e:
1069            if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1070                raise CommandError('Zone does not exist; record could not be deleted.')
1071            raise e
1072
1073        self.outf.write('Record deleted successfully\n')
1074
1075
1076class cmd_cleanup_record(Command):
1077    """Cleanup DNS records for a DNS host.
1078
1079    example:
1080
1081        samba-tool dns cleanup dc1 dc1.samdom.test.site -U USER%PASSWORD
1082
1083    NOTE: This command in many cases will only mark the `dNSTombstoned` attr
1084    as `TRUE` on the DNS records. Querying will no longer return results but
1085    there may still be some placeholder entries in the database.
1086    """
1087
1088    synopsis = '%prog <server> <dnshostname>'
1089
1090    takes_args = ['server', 'dnshostname']
1091
1092    takes_optiongroups = {
1093        "sambaopts": options.SambaOptions,
1094        "versionopts": options.VersionOptions,
1095        "credopts": options.CredentialsOptions,
1096    }
1097
1098    takes_options = [
1099        Option("-v", "--verbose", help="Be verbose", action="store_true"),
1100        Option("-q", "--quiet", help="Be quiet", action="store_true"),
1101    ]
1102
1103    def run(self, server, dnshostname, sambaopts=None, credopts=None,
1104            versionopts=None, verbose=False, quiet=False):
1105        lp = sambaopts.get_loadparm()
1106        creds = credopts.get_credentials(lp)
1107
1108        logger = self.get_logger(verbose=verbose, quiet=quiet)
1109
1110        samdb = SamDB(url="ldap://%s" % server,
1111                      session_info=system_session(),
1112                      credentials=creds, lp=lp)
1113
1114        remove_dc.remove_dns_references(samdb, logger, dnshostname,
1115                                        ignore_no_name=True)
1116
1117
1118class cmd_dns(SuperCommand):
1119    """Domain Name Service (DNS) management."""
1120
1121    subcommands = {}
1122    subcommands['serverinfo'] = cmd_serverinfo()
1123    subcommands['zoneinfo'] = cmd_zoneinfo()
1124    subcommands['zonelist'] = cmd_zonelist()
1125    subcommands['zonecreate'] = cmd_zonecreate()
1126    subcommands['zonedelete'] = cmd_zonedelete()
1127    subcommands['query'] = cmd_query()
1128    subcommands['roothints'] = cmd_roothints()
1129    subcommands['add'] = cmd_add_record()
1130    subcommands['update'] = cmd_update_record()
1131    subcommands['delete'] = cmd_delete_record()
1132    subcommands['cleanup'] = cmd_cleanup_record()
1133