samba-tool: Added dns command for DNS management
[ddiss/samba.git] / source4 / scripting / python / samba / netcmd / dns.py
1 #!/usr/bin/env python
2 #
3 # DNS management tool
4 #
5 # Copyright (C) Amitay Isaacs 2011
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20
21 import os
22 import samba.getopt as options
23 import ldb
24 from struct import pack
25 from socket import inet_ntoa
26
27 from samba.netcmd import (
28     Command,
29     CommandError,
30     Option,
31     SuperCommand,
32     )
33 from samba.dcerpc import dnsp, dnsserver
34 from samba.ndr import ndr_print
35
36
37 def dns_connect(server, lp, creds):
38     binding_str = "ncacn_ip_tcp:%s[sign]" % server
39     dns_conn = dnsserver.dnsserver(binding_str, lp, creds)
40     return dns_conn
41
42 def bool_string(flag):
43     if flag == 0:
44         ret = 'FALSE'
45     elif flag == 1:
46         ret = 'TRUE'
47     else:
48         ret = 'UNKNOWN (0x%x)' % flag
49     return ret
50
51 def enum_string(module, enum_defs, value):
52     ret = None
53     for e in enum_defs:
54         if value == getattr(module, e):
55             ret = e
56             break
57     if not ret:
58         ret = 'UNKNOWN (0x%x)' % value
59     return ret
60
61 def bitmap_string(module, bitmap_defs, value):
62     ret = ''
63     for b in bitmap_defs:
64         if value & getattr(module, b):
65             ret += '%s ' % b
66     if not ret:
67         ret = 'NONE'
68     return ret
69
70 def boot_method_string(boot_method):
71     enum_defs = [ 'DNS_BOOT_METHOD_UNINITIALIZED', 'DNS_BOOT_METHOD_FILE',
72                     'DNS_BOOT_METHOD_REGISTRY', 'DNS_BOOT_METHOD_DIRECTORY' ]
73     return enum_string(dnsserver, enum_defs, boot_method)
74
75 def name_check_flag_string(check_flag):
76     enum_defs = [ 'DNS_ALLOW_RFC_NAMES_ONLY', 'DNS_ALLOW_NONRFC_NAMES',
77                     'DNS_ALLOW_MULTIBYTE_NAMES', 'DNS_ALLOW_ALL_NAMES' ]
78     return enum_string(dnsserver, enum_defs, check_flag)
79
80 def zone_type_string(zone_type):
81     enum_defs = [ 'DNS_ZONE_TYPE_CACHE', 'DNS_ZONE_TYPE_PRIMARY',
82                     'DNS_ZONE_TYPE_SECONDARY', 'DNS_ZONE_TYPE_STUB',
83                     'DNS_ZONE_TYPE_FORWARDER', 'DNS_ZONE_TYPE_SECONDARY_CACHE' ]
84     return enum_string(dnsp, enum_defs, zone_type)
85
86 def zone_update_string(zone_update):
87     enum_defs = [ 'DNS_ZONE_UPDATE_OFF', 'DNS_ZONE_UPDATE_SECURE',
88                     'DNS_ZONE_UPDATE_SECURE' ]
89     return enum_string(dnsp, enum_defs, zone_update)
90
91 def zone_secondary_security_string(security):
92     enum_defs = [ 'DNS_ZONE_SECSECURE_NO_SECURITY', 'DNS_ZONE_SECSECURE_NS_ONLY',
93                     'DNS_ZONE_SECSECURE_LIST_ONLY', 'DNS_ZONE_SECSECURE_NO_XFER' ]
94     return enum_string(dnsserver, enum_defs, security)
95
96 def zone_notify_level_string(notify_level):
97     enum_defs = [ 'DNS_ZONE_NOTIFY_OFF', 'DNS_ZONE_NOTIFY_ALL_SECONDARIES',
98                     'DNS_ZONE_NOTIFY_LIST_ONLY' ]
99     return enum_string(dnsserver, enum_defs, notify_level)
100
101 def dp_flags_string(dp_flags):
102     bitmap_defs = [ 'DNS_DP_AUTOCREATED', 'DNS_DP_LEGACY', 'DNS_DP_DOMAIN_DEFAULT',
103                 'DNS_DP_FOREST_DEFAULT', 'DNS_DP_ENLISTED', 'DNS_DP_DELETED' ]
104     return bitmap_string(dnsserver, bitmap_defs, dp_flags)
105
106 def zone_flags_string(flags):
107     bitmap_defs = [ 'DNS_RPC_ZONE_PAUSED', 'DNS_RPC_ZONE_SHUTDOWN',
108                     'DNS_RPC_ZONE_REVERSE', 'DNS_RPC_ZONE_AUTOCREATED',
109                     'DNS_RPC_ZONE_DSINTEGRATED', 'DNS_RPC_ZONE_AGING',
110                     'DNS_RPC_ZONE_UPDATE_UNSECURE', 'DNS_RPC_ZONE_UPDATE_SECURE',
111                     'DNS_RPC_ZONE_READONLY']
112     return bitmap_string(dnsserver, bitmap_defs, flags)
113
114 def ip4_array_string(array):
115     ret = []
116     if not array:
117         return ret
118     for i in xrange(array.AddrCount):
119         addr = '%s' % inet_ntoa(pack('i', array.AddrArray[i]))
120         ret.append(addr)
121     return ret
122
123 def dns_addr_array_string(array):
124     ret = []
125     if not array:
126         return ret
127     for i in xrange(array.AddrCount):
128         if array.AddrArray[i].MaxSa[0] == 0x02:
129             addr = '%d.%d.%d.%d (%d)' % \
130                 tuple(array.AddrArray[i].MaxSa[4:8] + [array.AddrArray[i].MaxSa[3]])
131         elif array.AddrArray[i].MaxSa[0] == 0x17:
132             addr = '%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x (%d)' % \
133                 tuple(array.AddrArray[i].MaxSa[4:20] + [array.AddrArray[i].MaxSa[3]])
134         else:
135             addr = 'UNKNOWN'
136         ret.append(addr)
137     return ret
138
139 def dns_type_flag(rec_type):
140     rtype = rec_type.upper()
141     if rtype == 'A':
142         record_type = dnsp.DNS_TYPE_A
143     elif rtype == 'NS':
144         record_type = dnsp.DNS_TYPE_NS
145     elif rtype == 'CNAME':
146         record_type = dnsp.DNS_TYPE_CNAME
147     elif rtype == 'SOA':
148         record_type = dnsp.DNS_TYPE_SOA
149     elif rtype == 'MX':
150         record_type = dnsp.DNS_TYPE_MX
151     elif rtype == 'SRV':
152         record_type = dnsp.DNS_TYPE_SRV
153     elif rtype == 'ALL':
154         record_type = dnsp.DNS_TYPE_ALL
155     else:
156         raise CommandError('Unknown type of DNS record %s' % rec_type)
157     return record_type
158
159 def dns_client_version(cli_version):
160     version = cli_version.upper()
161     if version == 'W2K':
162         client_version = dnsserver.DNS_CLIENT_VERSION_W2K
163     elif version == 'DOTNET':
164         client_version = dnsserver.DNS_CLIENT_VERSION_DOTNET
165     elif version == 'LONGHORN':
166         client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
167     else:
168         raise CommandError('Unknown client version %s' % cli_version)
169     return client_version
170
171 def print_serverinfo(outf, typeid, serverinfo):
172     outf.write('  dwVersion                   : 0x%x\n' % serverinfo.dwVersion)
173     outf.write('  fBootMethod                 : %s\n' % boot_method_string(serverinfo.fBootMethod))
174     outf.write('  fAdminConfigured            : %s\n' % bool_string(serverinfo.fAdminConfigured))
175     outf.write('  fAllowUpdate                : %s\n' % bool_string(serverinfo.fAllowUpdate))
176     outf.write('  fDsAvailable                : %s\n' % bool_string(serverinfo.fDsAvailable))
177     outf.write('  pszServerName               : %s\n' % serverinfo.pszServerName)
178     outf.write('  pszDsContainer              : %s\n' % serverinfo.pszDsContainer)
179
180     if typeid != dnsserver.DNSSRV_TYPEID_SERVER_INFO:
181         outf.write('  aipServerAddrs              : %s\n' %
182                     ip4_array_string(serverinfo.aipServerAddrs))
183         outf.write('  aipListenAddrs              : %s\n' %
184                     ip4_array_string(serverinfo.aipListenAddrs))
185         outf.write('  aipForwarders               : %s\n' %
186                     ip4_array_string(serverinfo.aipForwarders))
187     else:
188         outf.write('  aipServerAddrs              : %s\n' %
189                     dns_addr_array_string(serverinfo.aipServerAddrs))
190         outf.write('  aipListenAddrs              : %s\n' %
191                     dns_addr_array_string(serverinfo.aipListenAddrs))
192         outf.write('  aipForwarders               : %s\n' %
193                     dns_addr_array_string(serverinfo.aipForwarders))
194
195     outf.write('  dwLogLevel                  : %d\n' % serverinfo.dwLogLevel)
196     outf.write('  dwDebugLevel                : %d\n' % serverinfo.dwDebugLevel)
197     outf.write('  dwForwardTimeout            : %d\n' % serverinfo.dwForwardTimeout)
198     outf.write('  dwRpcPrototol               : 0x%x\n' % serverinfo.dwRpcProtocol)
199     outf.write('  dwNameCheckFlag             : %s\n' % name_check_flag_string(serverinfo.dwNameCheckFlag))
200     outf.write('  cAddressAnswerLimit         : %d\n' % serverinfo.cAddressAnswerLimit)
201     outf.write('  dwRecursionRetry            : %d\n' % serverinfo.dwRecursionRetry)
202     outf.write('  dwRecursionTimeout          : %d\n' % serverinfo.dwRecursionTimeout)
203     outf.write('  dwMaxCacheTtl               : %d\n' % serverinfo.dwMaxCacheTtl)
204     outf.write('  dwDsPollingInterval         : %d\n' % serverinfo.dwDsPollingInterval)
205     outf.write('  dwScavengingInterval        : %d\n' % serverinfo.dwScavengingInterval)
206     outf.write('  dwDefaultRefreshInterval    : %d\n' % serverinfo.dwDefaultRefreshInterval)
207     outf.write('  dwDefaultNoRefreshInterval  : %d\n' % serverinfo.dwDefaultNoRefreshInterval)
208     outf.write('  fAutoReverseZones           : %s\n' % bool_string(serverinfo.fAutoReverseZones))
209     outf.write('  fAutoCacheUpdate            : %s\n' % bool_string(serverinfo.fAutoCacheUpdate))
210     outf.write('  fRecurseAfterForwarding     : %s\n' % bool_string(serverinfo.fRecurseAfterForwarding))
211     outf.write('  fForwardDelegations         : %s\n' % bool_string(serverinfo.fForwardDelegations))
212     outf.write('  fNoRecursion                : %s\n' % bool_string(serverinfo.fNoRecursion))
213     outf.write('  fSecureResponses            : %s\n' % bool_string(serverinfo.fSecureResponses))
214     outf.write('  fRoundRobin                 : %s\n' % bool_string(serverinfo.fRoundRobin))
215     outf.write('  fLocalNetPriority           : %s\n' % bool_string(serverinfo.fLocalNetPriority))
216     outf.write('  fBindSecondaries            : %s\n' % bool_string(serverinfo.fBindSecondaries))
217     outf.write('  fWriteAuthorityNs           : %s\n' % bool_string(serverinfo.fWriteAuthorityNs))
218     outf.write('  fStrictFileParsing          : %s\n' % bool_string(serverinfo.fStrictFileParsing))
219     outf.write('  fLooseWildcarding           : %s\n' % bool_string(serverinfo.fLooseWildcarding))
220     outf.write('  fDefaultAgingState          : %s\n' % bool_string(serverinfo.fDefaultAgingState))
221
222     if typeid != dnsserver.DNSSRV_TYPEID_SERVER_INFO_W2K:
223         outf.write('  dwRpcStructureVersion       : 0x%x\n' % serverinfo.dwRpcStructureVersion)
224         outf.write('  aipLogFilter                : %s\n' % dns_addr_array_string(serverinfo.aipLogFilter))
225         outf.write('  pwszLogFilePath             : %s\n' % serverinfo.pwszLogFilePath)
226         outf.write('  pszDomainName               : %s\n' % serverinfo.pszDomainName)
227         outf.write('  pszForestName               : %s\n' % serverinfo.pszForestName)
228         outf.write('  pszDomainDirectoryPartition : %s\n' % serverinfo.pszDomainDirectoryPartition)
229         outf.write('  pszForestDirectoryPartition : %s\n' % serverinfo.pszForestDirectoryPartition)
230
231         outf.write('  dwLocalNetPriorityNetMask   : 0x%x\n' % serverinfo.dwLocalNetPriorityNetMask)
232         outf.write('  dwLastScavengeTime          : %d\n' % serverinfo.dwLastScavengeTime)
233         outf.write('  dwEventLogLevel             : %d\n' % serverinfo.dwEventLogLevel)
234         outf.write('  dwLogFileMaxSize            : %d\n' % serverinfo.dwLogFileMaxSize)
235         outf.write('  dwDsForestVersion           : %d\n' % serverinfo.dwDsForestVersion)
236         outf.write('  dwDsDomainVersion           : %d\n' % serverinfo.dwDsDomainVersion)
237         outf.write('  dwDsDsaVersion              : %d\n' % serverinfo.dwDsDsaVersion)
238
239     if typeid == dnsserver.DNSSRV_TYPEID_SERVER_INFO:
240         outf.write('  fReadOnlyDC                 : %s\n' % bool_string(serverinfo.fReadOnlyDC))
241
242
243 def print_zoneinfo(outf, typeid, zoneinfo):
244     outf.write('  pszZoneName                 : %s\n' % zoneinfo.pszZoneName)
245     outf.write('  dwZoneType                  : %s\n' % zone_type_string(zoneinfo.dwZoneType))
246     outf.write('  fReverse                    : %s\n' % bool_string(zoneinfo.fReverse))
247     outf.write('  fAllowUpdate                : %s\n' % zone_update_string(zoneinfo.fAllowUpdate))
248     outf.write('  fPaused                     : %s\n' % bool_string(zoneinfo.fPaused))
249     outf.write('  fShutdown                   : %s\n' % bool_string(zoneinfo.fShutdown))
250     outf.write('  fAutoCreated                : %s\n' % bool_string(zoneinfo.fAutoCreated))
251     outf.write('  fUseDatabase                : %s\n' % bool_string(zoneinfo.fUseDatabase))
252     outf.write('  pszDataFile                 : %s\n' % zoneinfo.pszDataFile)
253     if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
254         outf.write('  aipMasters                  : %s\n' %
255                     ip4_array_string(zoneinfo.aipMasters))
256     else:
257         outf.write('  aipMasters                  : %s\n' %
258                     dns_addr_array_string(zoneinfo.aipMasters))
259     outf.write('  fSecureSecondaries          : %s\n' % zone_secondary_security_string(zoneinfo.fSecureSecondaries))
260     outf.write('  fNotifyLevel                : %s\n' % zone_notify_level_string(zoneinfo.fNotifyLevel))
261     if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
262         outf.write('  aipSecondaries              : %s\n' %
263                     ip4_array_string(zoneinfo.aipSecondaries))
264         outf.write('  aipNotify                   : %s\n' %
265                     ip4_array_string(zoneinfo.aipNotify))
266     else:
267         outf.write('  aipSecondaries              : %s\n' %
268                     dns_addr_array_string(zoneinfo.aipSecondaries))
269         outf.write('  aipNotify                   : %s\n' %
270                     dns_addr_array_string(zoneinfo.aipNotify))
271     outf.write('  fUseWins                    : %s\n' % bool_string(zoneinfo.fUseWins))
272     outf.write('  fUseNbstat                  : %s\n' % bool_string(zoneinfo.fUseNbstat))
273     outf.write('  fAging                      : %s\n' % bool_string(zoneinfo.fAging))
274     outf.write('  dwNoRefreshInterval         : %d\n' % zoneinfo.dwNoRefreshInterval)
275     outf.write('  dwRefreshInterval           : %d\n' % zoneinfo.dwRefreshInterval)
276     outf.write('  dwAvailForScavengeTime      : %d\n' % zoneinfo.dwAvailForScavengeTime)
277     if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
278         outf.write('  aipScavengeServers          : %s\n' %
279                     ip4_array_string(zoneinfo.aipScavengeServers))
280     else:
281         outf.write('  aipScavengeServers          : %s\n' %
282                     dns_addr_array_string(zoneinfo.aipScavengeServers))
283
284     if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO_W2K:
285         outf.write('  dwRpcStructureVersion       : 0x%x\n' % zoneinfo.dwRpcStructureVersion)
286         outf.write('  dwForwarderTimeout          : %d\n' % zoneinfo.dwForwarderTimeout)
287         outf.write('  fForwarderSlave             : %d\n' % zoneinfo.fForwarderSlave)
288         if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
289             outf.write('  aipLocalMasters             : %s\n' %
290                         ip4_array_string(zoneinfo.aipLocalMasters))
291         else:
292             outf.write('  aipLocalMasters             : %s\n' %
293                         dns_addr_array_string(zoneinfo.aipLocalMasters))
294         outf.write('  dwDpFlags                   : %s\n' % dp_flags_string(zoneinfo.dwDpFlags))
295         outf.write('  pszDpFqdn                   : %s\n' % zoneinfo.pszDpFqdn)
296         outf.write('  pwszZoneDn                  : %s\n' % zoneinfo.pwszZoneDn)
297         outf.write('  dwLastSuccessfulSoaCheck    : %d\n' % zoneinfo.dwLastSuccessfulSoaCheck)
298         outf.write('  dwLastSuccessfulXfr         : %d\n' % zoneinfo.dwLastSuccessfulXfr)
299
300     if typeid == dnsserver.DNSSRV_TYPEID_ZONE_INFO:
301         outf.write('  fQueuedForBackgroundLoad    : %s\n' % bool_string(zoneinfo.fQueuedForBackgroundLoad))
302         outf.write('  fBackgroundLoadInProgress   : %s\n' % bool_string(zoneinfo.fBackgroundLoadInProgress))
303         outf.write('  fReadOnlyZone               : %s\n' % bool_string(zoneinfo.fReadOnlyZone))
304         outf.write('  dwLastXfrAttempt            : %d\n' % zoneinfo.dwLastXfrAttempt)
305         outf.write('  dwLastXfrResult             : %d\n' % zoneinfo.dwLastXfrResult)
306
307
308 def print_zone(outf, typeid, zone):
309     outf.write('  pszZoneName                 : %s\n' % zone.pszZoneName)
310     outf.write('  Flags                       : %s\n' % zone_flags_string(zone.Flags))
311     outf.write('  ZoneType                    : %s\n' % zone_type_string(zone.ZoneType))
312     outf.write('  Version                     : %s\n' % zone.Version)
313
314     if typeid != dnsserver.DNSSRV_TYPEID_ZONE_W2K:
315         outf.write('  dwDpFlags                   : %s\n' % dp_flags_string(zone.dwDpFlags))
316         outf.write('  pszDpFqdn                   : %s\n' % zone.pszDpFqdn)
317
318
319 def print_enumzones(outf, typeid, zones):
320     outf.write('  %d zone(s) found\n' % zones.dwZoneCount)
321     for zone in zones.ZoneArray:
322         outf.write('\n')
323         print_zone(outf, typeid, zone)
324
325
326 def print_dns_record(outf, rec):
327     mesg = 'Unknown: '
328     if rec.wType == dnsp.DNS_TYPE_A:
329         mesg = 'A: %s' % (rec.data)
330     elif rec.wType == dnsp.DNS_TYPE_NS:
331         mesg = 'NS: %s' % (rec.data.str)
332     elif rec.wType == dnsp.DNS_TYPE_CNAME:
333         mesg = 'CNAME: %s' % (rec.data.str)
334     elif rec.wType == dnsp.DNS_TYPE_SOA:
335         mesg = 'SOA: serial=%d, refresh=%d, retry=%d, expire=%d, ns=%s, email=%s' % (
336                     rec.data.dwSerialNo,
337                     rec.data.dwRefresh,
338                     rec.data.dwRetry,
339                     rec.data.dwExpire,
340                     rec.data.NamePrimaryServer.str,
341                     rec.data.ZoneAdministratorEmail.str)
342     elif rec.wType == dnsp.DNS_TYPE_MX:
343         mesg = 'MX: %s' % (rec.data.str)
344     elif rec.wType == dnsp.DNS_TYPE_SRV:
345         mesg = 'SRV: %s (%d)' % (rec.data.nameTarget.str, rec.data.wPort)
346     outf.write('    %s (flags=%x, serial=%d, ttl=%d)\n' % (
347                 mesg, rec.dwFlags, rec.dwSerial, rec.dwTtlSeconds))
348
349
350 def print_dnsrecords(outf, records):
351     for rec in records.rec:
352         outf.write('  Name=%s, Records=%d, Children=%d\n' % (
353                     rec.dnsNodeName.str,
354                     rec.wRecordCount,
355                     rec.dwChildCount))
356         for dns_rec in rec.records:
357                 print_dns_record(outf, dns_rec)
358
359
360 class ARecord(dnsserver.DNS_RPC_RECORD):
361     def __init__(self, ip_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
362                     node_flag=0):
363         super(ARecord, self).__init__()
364         self.wType = dnsp.DNS_TYPE_A
365         self.dwFlags = rank | node_flag
366         self.dwSerial = serial
367         self.dwTtlSeconds = ttl
368         self.data = ip_addr
369
370 class AAAARecord(dnsserver.DNS_RPC_RECORD):
371     def __init__(self, ip6_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
372                     node_flag=0):
373         super(AAAARecord, self).__init__()
374         self.wType = dnsp.DNS_TYPE_AAAA
375         self.dwFlags = rank | node_flag
376         self.dwSerial = serial
377         self.dwTtlSeconds = ttl
378         self.data = ip6_addr
379
380 class CNameRecord(dnsserver.DNS_RPC_RECORD):
381     def __init__(self, cname, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
382                     node_flag=0):
383         super(CNameRecord, self).__init__()
384         self.wType = dnsp.DNS_TYPE_CNAME
385         self.dwFlags = rank | node_flag
386         self.dwSerial = serial
387         self.dwTtlSeconds = ttl
388         cname_name = dnsserver.DNS_RPC_NAME()
389         cname_name.str = cname
390         cname_name.len = len(cname)
391         self.data = cname_name
392
393 class NSRecord(dnsserver.DNS_RPC_RECORD):
394     def __init__(self, dns_server, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
395                     node_flag=0):
396         super(NSRecord, self).__init__()
397         self.wType = dnsp.DNS_TYPE_NS
398         self.dwFlags = rank | node_flag
399         self.dwSerial = serial
400         self.dwTtlSeconds = ttl
401         ns = dnsserver.DNS_RPC_NAME()
402         ns.str = dns_server
403         ns.len = len(dns_server)
404         self.data = ns
405
406 class SOARecord(dnsserver.DNS_RPC_RECORD):
407     def __init__(self, mname, rname, serial=1, refresh=900, retry=600,
408                  expire=86400, minimum=3600, ttl=3600, rank=dnsp.DNS_RANK_ZONE,
409                  node_flag=dnsp.DNS_RPC_FLAG_AUTH_ZONE_ROOT):
410         super(SOARecord, self).__init__()
411         self.wType = dnsp.DNS_TYPE_SOA
412         self.dwFlags = rank | node_flag
413         self.dwSerial = serial
414         self.dwTtlSeconds = ttl
415         soa = dnsserver.DNS_RPC_RECORD_SOA()
416         soa.dwSerialNo = serial
417         soa.dwRefresh = refresh
418         soa.dwRetry = retry
419         soa.dwExpire = expire
420         soa.NamePrimaryServer.str = mname
421         soa.NamePrimaryServer.len = len(mname)
422         soa.ZoneAdministratorEmail.str = rname
423         soa.ZoneAdministratorEmail.len = len(rname)
424         self.data = soa
425
426 class SRVRecord(dnsserver.DNS_RPC_RECORD):
427     def __init__(self, target, port, priority=0, weight=100, serial=1, ttl=900,
428                 rank=dnsp.DNS_RANK_ZONE, node_flag=0):
429         super(SRVRecord, self).__init__()
430         self.wType = dnsp.DNS_TYPE_SRV
431         self.dwFlags = rank | node_flag
432         self.dwSerial = serial
433         self.dwTtlSeconds = ttl
434         srv = dnsserver.DNS_RPC_RECORD_SRV()
435         srv.wPriority = priority
436         srv.wWeight = weight
437         srv.wPort = port
438         srv.nameTarget.str = target
439         srv.nameTarget.len = len(target)
440         self.data = srv
441
442 def dns_record_match(dns_conn, server, zone, name, record_type, data):
443     select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
444
445     try:
446         buflen, res = dns_conn.DnssrvEnumRecords2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
447                                                     0,
448                                                     server,
449                                                     zone,
450                                                     name,
451                                                     None,
452                                                     record_type,
453                                                     select_flags,
454                                                     None,
455                                                     None)
456     except RuntimeError, e:
457         return None
458
459     rec_match = None
460     if res and res.count > 0:
461         recs = res.rec[0]
462         for rec in recs.records:
463             if rec.wType == record_type:
464                 rec_match = rec
465                 break
466
467     if rec_match:
468         found = False
469         if record_type == dnsp.DNS_TYPE_A:
470             if rec_match.data == data:
471                 found = True
472         elif record_type == dnsp.DNS_TYPE_AAAA:
473             if rec_match.data == data:
474                 found = True
475         elif record_type == dnsp.DNS_TYPE_CNAME:
476             if rec_match.data == data:
477                 found = True
478         elif record_type == dnsp.DNS_TYPE_NS:
479             if rec_match.data == data:
480                 found = True
481
482         if found:
483             return rec_match
484
485     return None
486
487
488 class cmd_serverinfo(Command):
489     """Query for Server information"""
490
491     synopsis = '%prog <server> [options]'
492
493     takes_args = [ 'server' ]
494
495     takes_options = [
496         Option('--client-version', help='Client Version', default='longhorn',
497                 choices=['w2k','dotnet','longhorn'], dest='cli_ver'),
498     ]
499
500     def run(self, server, cli_ver, sambaopts=None, credopts=None, versionopts=None):
501         self.lp = sambaopts.get_loadparm()
502         self.creds = credopts.get_credentials(self.lp)
503         dns_conn = dns_connect(server, self.lp, self.creds)
504
505         client_version = dns_client_version(cli_ver)
506
507         typeid, res = dns_conn.DnssrvQuery2(client_version,
508                                             0,
509                                             server,
510                                             None,
511                                             'ServerInfo')
512         print_serverinfo(self.outf, typeid, res)
513
514
515 class cmd_zoneinfo(Command):
516     """Query for zone information"""
517
518     synopsis = '%prog <server> <zone> [options]'
519
520     takes_args = [ 'server', 'zone' ]
521
522     takes_options = [
523         Option('--client-version', help='Client Version', default='longhorn',
524                 choices=['w2k','dotnet','longhorn'], dest='cli_ver'),
525     ]
526
527     def run(self, server, zone, cli_ver, sambaopts=None, credopts=None, versionopts=None):
528         self.lp = sambaopts.get_loadparm()
529         self.creds = credopts.get_credentials(self.lp)
530         dns_conn = dns_connect(server, self.lp, self.creds)
531
532         client_version = dns_client_version(cli_ver)
533
534         typeid, res = dns_conn.DnssrvQuery2(client_version,
535                                             0,
536                                             server,
537                                             zone,
538                                             'ZoneInfo')
539         print_zoneinfo(self.outf, typeid, res)
540
541
542 class cmd_zonelist(Command):
543     """Query for zones"""
544
545     synopsis = '%prog <server> [options]'
546
547     takes_args = [ 'server' ]
548
549     takes_options = [
550         Option('--client-version', help='Client Version', default='longhorn',
551                 choices=['w2k','dotnet','longhorn'], dest='cli_ver'),
552         Option('--primary', help='List primary zones (default)',
553                 action='store_true', dest='primary'),
554         Option('--secondary', help='List secondary zones',
555                 action='store_true', dest='secondary'),
556         Option('--cache', help='List cached zones',
557                 action='store_true', dest='cache'),
558         Option('--auto', help='List automatically created zones',
559                 action='store_true', dest='auto'),
560         Option('--forward', help='List forward zones',
561                 action='store_true', dest='forward'),
562         Option('--reverse', help='List reverse zones',
563                 action='store_true', dest='reverse'),
564         Option('--ds', help='List directory integrated zones',
565                 action='store_true', dest='ds'),
566         Option('--non-ds', help='List non-directory zones',
567                 action='store_true', dest='nonds')
568     ]
569
570     def run(self, server, cli_ver, primary=False, secondary=False, cache=False,
571                 auto=False, forward=False, reverse=False, ds=False, nonds=False,
572                 sambaopts=None, credopts=None, versionopts=None):
573         request_filter = 0
574
575         if primary:
576             request_filter |= dnsserver.DNS_ZONE_REQUEST_PRIMARY
577         if secondary:
578             request_filter |= dnsserver.DNS_ZONE_REQUEST_SECONDARY
579         if cache:
580             request_filter |= dnsserver.DNS_ZONE_REQUEST_CACHE
581         if auto:
582             request_filter |= dnsserver.DNS_ZONE_REQUEST_AUTO
583         if forward:
584             request_filter |= dnsserver.DNS_ZONE_REQUEST_FORWARD
585         if reverse:
586             request_filter |= dnsserver.DNS_ZONE_REQUEST_REVERSE
587         if ds:
588             request_filter |= dnsserver.DNS_ZONE_REQUEST_DS
589         if nonds:
590             request_filter |= dnsserver.DNS_ZONE_REQUEST_NON_DS
591
592         if request_filter == 0:
593             request_filter = dnsserver.DNS_ZONE_REQUEST_PRIMARY
594
595         self.lp = sambaopts.get_loadparm()
596         self.creds = credopts.get_credentials(self.lp)
597         dns_conn = dns_connect(server, self.lp, self.creds)
598
599         client_version = dns_client_version(cli_ver)
600
601         typeid, res = dns_conn.DnssrvComplexOperation2(client_version,
602                                                         0,
603                                                         server,
604                                                         None,
605                                                         'EnumZones',
606                                                         dnsserver.DNSSRV_TYPEID_DWORD,
607                                                         request_filter)
608
609         if client_version == dnsserver.DNS_CLIENT_VERSION_W2K:
610             typeid = dnsserver.DNSSRV_TYPEID_ZONE_W2K
611         else:
612             typeid = dnsserver.DNSSRV_TYPEID_ZONE
613         print_enumzones(self.outf, typeid, res)
614
615
616 class cmd_query(Command):
617     """Query a name"""
618
619     synopsis = '%prog <server> <zone> <name> <type> [options]'
620
621     takes_args = [ 'server', 'zone', 'name', 'rtype' ]
622
623     takes_options = [
624         Option('--authority', help='Search authoritative records (default)',
625                 action='store_true', dest='authority'),
626         Option('--cache', help='Search cached records',
627                 action='store_true', dest='cache'),
628         Option('--glue', help='Search glue records',
629                 action='store_true', dest='glue'),
630         Option('--root', help='Search root hints',
631                 action='store_true', dest='root'),
632         Option('--additional', help='List additional records',
633                 action='store_true', dest='additional'),
634         Option('--no-children', help='Do not list children',
635                 action='store_true', dest='no_children'),
636         Option('--only-children', help='List only children',
637                 action='store_true', dest='only_children')
638     ]
639
640     def run(self, server, zone, name, rtype, authority=False, cache=False, glue=False,
641                 root=False, additional=False, no_children=False, only_children=False,
642                 sambaopts=None, credopts=None, versionopts=None):
643         record_type = dns_type_flag(rtype)
644
645         select_flags = 0
646         if authority:
647             select_flags |= dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
648         if cache:
649             select_flags |= dnsserver.DNS_RPC_VIEW_CACHE_DATA
650         if glue:
651             select_flags |= dnsserver.DNS_RPC_VIEW_GLUE_DATA
652         if root:
653             select_flags |= dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA
654         if additional:
655             select_flags |= dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA
656         if no_children:
657             select_flags |= dnsserver.DNS_RPC_VIEW_NO_CHILDREN
658         if only_children:
659             select_flags |= dnsserver.DNS_RPC_VIEW_ONLY_CHILDREN
660
661         if select_flags == 0:
662             select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
663
664         self.lp = sambaopts.get_loadparm()
665         self.creds = credopts.get_credentials(self.lp)
666         dns_conn = dns_connect(server, self.lp, self.creds)
667
668         buflen, res = dns_conn.DnssrvEnumRecords2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
669                                                     0,
670                                                     server,
671                                                     zone,
672                                                     name,
673                                                     None,
674                                                     record_type,
675                                                     select_flags,
676                                                     None,
677                                                     None)
678         print_dnsrecords(self.outf, res)
679
680
681 class cmd_roothints(Command):
682     """Query root hints"""
683
684     synopsis = '%prog <server> [<name>] [options]'
685
686     takes_args = [ 'server', 'name?' ]
687
688     def run(self, server, name='.', sambaopts=None, credopts=None, versionopts=None):
689         record_type = dnsp.DNS_TYPE_NS
690         select_flags = (dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA |
691                         dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA)
692
693         self.lp = sambaopts.get_loadparm()
694         self.creds = credopts.get_credentials(self.lp)
695         dns_conn = dns_connect(server, self.lp, self.creds)
696
697         buflen, res = dns_conn.DnssrvEnumRecords2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
698                                                     0,
699                                                     server,
700                                                     '..RootHints',
701                                                     name,
702                                                     None,
703                                                     record_type,
704                                                     select_flags,
705                                                     None,
706                                                     None)
707         print_dnsrecords(self.outf, res)
708
709
710 class cmd_add_record(Command):
711     """Add a DNS record"""
712
713     synopsis = '%prog <server> <zone> <name> <A|AAAA|CNAME|NS> <data>'
714
715     takes_args = [ 'server', 'zone', 'name', 'rtype', 'data' ]
716
717     def run(self, server, zone, name, rtype, data, sambaopts=None, credopts=None, versionopts=None):
718
719         record_type = dns_type_flag(rtype)
720
721         if record_type == dnsp.DNS_TYPE_A:
722             rec = ARecord(data)
723         elif record_type == dnsp.DNS_TYPE_AAAA:
724             rec = AAAARecord(data)
725         elif record_type == dnsp.DNS_TYPE_CNAME:
726             rec = CNameRecord(data)
727         elif record_type == dnsp.DNS_TYPE_NS:
728             rec = NSRecord(data)
729         else:
730             raise CommandError('Adding record of type %s is not supported' % rtype)
731
732         self.lp = sambaopts.get_loadparm()
733         self.creds = credopts.get_credentials(self.lp)
734         dns_conn = dns_connect(server, self.lp, self.creds)
735
736         rec_match = dns_record_match(dns_conn, server, zone, name, record_type, data)
737         if rec_match is not None:
738             raise CommandError('Record already exists')
739
740         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
741         add_rec_buf.rec = rec
742
743         dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
744                                         0,
745                                         server,
746                                         zone,
747                                         name,
748                                         add_rec_buf,
749                                         None)
750         self.outf.write('Record added succefully\n')
751
752
753 class cmd_update_record(Command):
754     """Update a DNS record"""
755
756     synopsis = '%prog <server> <zone> <name> <A|AAAA|CNAME|NS> <olddata> <newdata>'
757
758     takes_args = [ 'server', 'zone', 'name', 'rtype', 'olddata', 'newdata' ]
759
760     def run(self, server, zone, name, rtype, olddata, newdata,
761                 sambaopts=None, credopts=None, versionopts=None):
762
763         record_type = dns_type_flag(rtype)
764         if record_type == dnsp.DNS_TYPE_A:
765             rec = ARecord(newdata)
766         elif record_type == dnsp.DNS_TYPE_AAAA:
767             rec = AAAARecord(newdata)
768         elif record_type == dnsp.DNS_TYPE_CNAME:
769             rec = CNameRecord(newdata)
770         elif record_type == dnsp.DNS_TYPE_NS:
771             rec = NSRecord(newdata)
772         else:
773             raise CommandError('Updating record of type %s is not supported' % rtype)
774
775         self.lp = sambaopts.get_loadparm()
776         self.creds = credopts.get_credentials(self.lp)
777         dns_conn = dns_connect(server, self.lp, self.creds)
778
779         rec_match = dns_record_match(dns_conn, server, zone, name, record_type, olddata)
780         if not rec_match:
781             raise CommandError('Record does not exist')
782
783         # Copy properties from existing record to new record
784         rec.dwFlags = rec_match.dwFlags
785         rec.dwSerial = rec_match.dwSerial
786         rec.dwTtlSeconds = rec_match.dwTtlSeconds
787         rec.dwTimeStamp = rec_match.dwTimeStamp
788
789         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
790         add_rec_buf.rec = rec
791
792         del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
793         del_rec_buf.rec = rec_match
794
795         dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
796                                         0,
797                                         server,
798                                         zone,
799                                         name,
800                                         add_rec_buf,
801                                         del_rec_buf)
802         self.outf.write('Record updated succefully\n')
803
804
805 class cmd_delete_record(Command):
806     """Delete a DNS record"""
807
808     synopsis = '%prog <server> <zone> <name> <A|AAAA|CNAME|NS> <data>'
809
810     takes_args = [ 'server', 'zone', 'name', 'rtype', 'data' ]
811
812     def run(self, server, zone, name, rtype, data, sambaopts=None, credopts=None, versionopts=None):
813
814         record_type = dns_type_flag(rtype)
815
816         if record_type == dnsp.DNS_TYPE_A:
817             rec = ARecord(data)
818         elif record_type == dnsp.DNS_TYPE_AAAA:
819             rec = AAAARecord(data)
820         elif record_type == dnsp.DNS_TYPE_CNAME:
821             rec = CNameRecord(data)
822         elif record_type == dnsp.DNS_TYPE_NS:
823             rec = NSRecord(data)
824         else:
825             raise CommandError('Deleting record of type %s is not supported' % rtype)
826
827         self.lp = sambaopts.get_loadparm()
828         self.creds = credopts.get_credentials(self.lp)
829         dns_conn = dns_connect(server, self.lp, self.creds)
830
831         rec_match = dns_record_match(dns_conn, server, zone, name, record_type, data)
832         if not rec_match:
833             raise CommandError('Record does not exist')
834
835         del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
836         del_rec_buf.rec = rec_match
837
838         dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
839                                         0,
840                                         server,
841                                         zone,
842                                         name,
843                                         None,
844                                         del_rec_buf)
845         self.outf.write('Record deleted succefully\n')
846
847
848 class cmd_dns(SuperCommand):
849     """Domain Name Service (DNS) management"""
850
851     subcommands = {}
852     subcommands['serverinfo'] = cmd_serverinfo()
853     subcommands['zoneinfo'] = cmd_zoneinfo()
854     subcommands['zonelist'] = cmd_zonelist()
855     subcommands['query'] = cmd_query()
856     subcommands['roothints'] = cmd_roothints()
857     subcommands['add'] = cmd_add_record()
858     subcommands['update'] = cmd_update_record()
859     subcommands['delete'] = cmd_delete_record()