1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Andrew Bartlett <abartlet@catalyst.net.nz>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 from samba
.ndr
import ndr_unpack
, ndr_pack
23 from samba
.dcerpc
import dnsp
24 from samba
.tests
.samba_tool
.base
import SambaToolCmdTest
26 from samba
import dsdb_dns
29 class DnsCmdTestCase(SambaToolCmdTest
):
33 self
.dburl
= "ldap://%s" % os
.environ
["SERVER"]
34 self
.creds_string
= "-U%s%%%s" % (os
.environ
["DC_USERNAME"],
35 os
.environ
["DC_PASSWORD"])
37 self
.samdb
= self
.getSamDB("-H", self
.dburl
, self
.creds_string
)
38 self
.config_dn
= str(self
.samdb
.get_config_basedn())
40 self
.testip
= "192.168.0.193"
41 self
.testip2
= "192.168.0.194"
43 self
.addCleanup(self
.deleteZone
)
46 # Note: SOA types don't work (and shouldn't), as we only have one zone per DNS record.
48 good_dns
= ["SAMDOM.EXAMPLE.COM",
50 "%sEXAMPLE.COM" % ("1." * 100),
60 "SAMDOM..EXAMPLE.COM"]
62 good_mx
= ["SAMDOM.EXAMPLE.COM 65530",
63 "SAMDOM.EXAMPLE.COM 0"]
64 bad_mx
= ["SAMDOM.EXAMPLE.COM -1",
67 "SAMDOM.EXAMPLE.COM 1 1",
68 "SAMDOM.EXAMPLE.COM SAMDOM.EXAMPLE.COM"]
70 good_srv
= ["SAMDOM.EXAMPLE.COM 65530 65530 65530",
71 "SAMDOM.EXAMPLE.COM 1 1 1"]
72 bad_srv
= ["SAMDOM.EXAMPLE.COM 0 65536 0",
73 "SAMDOM.EXAMPLE.COM 0 0 65536",
74 "SAMDOM.EXAMPLE.COM 65536 0 0"]
76 for bad_dn
in bad_dns
:
77 bad_mx
.append("%s 1" % bad_dn
)
78 bad_srv
.append("%s 0 0 0" % bad_dn
)
79 for good_dn
in good_dns
:
80 good_mx
.append("%s 1" % good_dn
)
81 good_srv
.append("%s 0 0 0" % good_dn
)
84 "A":["192.168.0.1", "255.255.255.255"],
85 "AAAA":["1234:5678:9ABC:DEF0:0000:0000:0000:0000",
86 "0000:0000:0000:0000:0000:0000:0000:0000",
87 "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0",
89 "1234:5678:9ABC:DEF0::",
91 "1234::5678:9ABC:0000:0000:0000:0000",
100 "TXT": ["text", "", "@#!", "\n"]
104 "A":["192.168.0.500",
105 "255.255.255.255/32"],
106 "AAAA":["GGGG:1234:5678:9ABC:0000:0000:0000:0000",
107 "0000:0000:0000:0000:0000:0000:0000:0000/1",
108 "AAAA:AAAA:AAAA:AAAA:G000:0000:0000:1234",
109 "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0:1234",
110 "1234:5678:9ABC:DEF0:1234:5678:9ABC",
125 result
, out
, err
= self
.runsubcmd("dns",
127 os
.environ
["SERVER"],
130 self
.assertCmdSuccess(result
, out
, err
)
132 def deleteZone(self
):
133 result
, out
, err
= self
.runsubcmd("dns",
135 os
.environ
["SERVER"],
138 self
.assertCmdSuccess(result
, out
, err
)
140 def get_all_records(self
, zone_name
):
141 zone_dn
= (f
"DC={zone_name},CN=MicrosoftDNS,DC=DomainDNSZones,"
142 f
"{self.samdb.get_default_basedn()}")
144 expression
= "(&(objectClass=dnsNode)(!(dNSTombstoned=TRUE)))"
146 nodes
= self
.samdb
.search(base
=zone_dn
, scope
=ldb
.SCOPE_SUBTREE
,
147 expression
=expression
,
148 attrs
=["dnsRecord", "name"])
152 name
= node
["name"][0].decode()
153 record_map
[name
] = list(node
["dnsRecord"])
157 def get_record_from_db(self
, zone_name
, record_name
):
158 zones
= self
.samdb
.search(base
="DC=DomainDnsZones,%s"
159 % self
.samdb
.get_default_basedn(),
160 scope
=ldb
.SCOPE_SUBTREE
,
161 expression
="(objectClass=dnsZone)",
165 if zone_name
in str(zone
.dn
):
169 records
= self
.samdb
.search(base
=zone_dn
, scope
=ldb
.SCOPE_SUBTREE
,
170 expression
="(objectClass=dnsNode)",
173 for old_packed_record
in records
:
174 if record_name
in str(old_packed_record
.dn
):
175 return (old_packed_record
.dn
,
176 ndr_unpack(dnsp
.DnssrvRpcRecord
,
177 old_packed_record
["dnsRecord"][0]))
179 def test_rank_none(self
):
180 record_str
= "192.168.50.50"
181 record_type_str
= "A"
183 result
, out
, err
= self
.runsubcmd("dns", "add", os
.environ
["SERVER"],
184 self
.zone
, "testrecord", record_type_str
,
185 record_str
, self
.creds_string
)
186 self
.assertCmdSuccess(result
, out
, err
,
187 "Failed to add record '%s' with type %s."
188 % (record_str
, record_type_str
))
190 dn
, record
= self
.get_record_from_db(self
.zone
, "testrecord")
191 record
.rank
= 0 # DNS_RANK_NONE
192 res
= self
.samdb
.dns_replace_by_dn(dn
, [record
])
194 self
.fail("Unable to update dns record to have DNS_RANK_NONE.")
198 # The record should still exist
199 result
, out
, err
= self
.runsubcmd("dns", "query", os
.environ
["SERVER"],
200 self
.zone
, "testrecord", record_type_str
,
203 self
.assertCmdSuccess(result
, out
, err
,
204 "Failed to query for a record"
205 "which had DNS_RANK_NONE.")
206 self
.assertTrue("testrecord" in out
and record_str
in out
,
207 "Query for a record which had DNS_RANK_NONE"
208 "succeeded but produced no resulting records.")
209 except AssertionError:
210 # Windows produces no resulting records
213 # We should not be able to add a duplicate
214 result
, out
, err
= self
.runsubcmd("dns", "add", os
.environ
["SERVER"],
215 self
.zone
, "testrecord", record_type_str
,
216 record_str
, self
.creds_string
)
218 self
.assertCmdFail(result
, "Successfully added duplicate record"
219 "of one which had DNS_RANK_NONE.")
220 except AssertionError as e
:
223 # We should be able to delete it
224 result
, out
, err
= self
.runsubcmd("dns", "delete", os
.environ
["SERVER"],
225 self
.zone
, "testrecord", record_type_str
,
226 record_str
, self
.creds_string
)
228 self
.assertCmdSuccess(result
, out
, err
, "Failed to delete record"
229 "which had DNS_RANK_NONE.")
230 except AssertionError as e
:
233 # Now the record should not exist
234 result
, out
, err
= self
.runsubcmd("dns", "query", os
.environ
["SERVER"],
235 self
.zone
, "testrecord",
236 record_type_str
, self
.creds_string
)
238 self
.assertCmdFail(result
, "Successfully queried for deleted record"
239 "which had DNS_RANK_NONE.")
240 except AssertionError as e
:
244 err_str
= "Failed appropriate behaviour with DNS_RANK_NONE:"
246 err_str
= err_str
+ "\n" + str(error
)
247 raise AssertionError(err_str
)
249 def test_accept_valid_commands(self
):
251 For all good records, attempt to add, query and delete them.
255 for dnstype
in self
.good_records
:
256 for record
in self
.good_records
[dnstype
]:
258 result
, out
, err
= self
.runsubcmd("dns", "add",
259 os
.environ
["SERVER"],
260 self
.zone
, "testrecord",
263 self
.assertCmdSuccess(result
, out
, err
, "Failed to add"
264 "record %s with type %s."
267 result
, out
, err
= self
.runsubcmd("dns", "query",
268 os
.environ
["SERVER"],
269 self
.zone
, "testrecord",
272 self
.assertCmdSuccess(result
, out
, err
, "Failed to query"
273 "record %s with qualifier %s."
276 result
, out
, err
= self
.runsubcmd("dns", "delete",
277 os
.environ
["SERVER"],
278 self
.zone
, "testrecord",
281 self
.assertCmdSuccess(result
, out
, err
, "Failed to remove"
282 "record %s with type %s."
284 except AssertionError as e
:
285 num_failures
= num_failures
+ 1
286 failure_msgs
.append(e
)
289 for msg
in failure_msgs
:
291 self
.fail("Failed to accept valid commands. %d total failures."
292 "Errors above." % num_failures
)
294 def test_reject_invalid_commands(self
):
296 For all bad records, attempt to add them and update to them,
297 making sure that both operations fail.
302 # Add invalid records and make sure they fail to be added
303 for dnstype
in self
.bad_records
:
304 for record
in self
.bad_records
[dnstype
]:
306 result
, out
, err
= self
.runsubcmd("dns", "add",
307 os
.environ
["SERVER"],
308 self
.zone
, "testrecord",
311 self
.assertCmdFail(result
, "Successfully added invalid"
312 "record '%s' of type '%s'."
314 except AssertionError as e
:
315 num_failures
= num_failures
+ 1
316 failure_msgs
.append(e
)
319 result
, out
, err
= self
.runsubcmd("dns", "delete",
320 os
.environ
["SERVER"],
321 self
.zone
, "testrecord",
324 self
.assertCmdFail(result
, "Successfully deleted invalid"
325 "record '%s' of type '%s' which"
326 "shouldn't exist." % (record
, dnstype
))
327 except AssertionError as e
:
328 num_failures
= num_failures
+ 1
329 failure_msgs
.append(e
)
332 # Update valid records to invalid ones and make sure they
334 for dnstype
in self
.bad_records
:
335 for bad_record
in self
.bad_records
[dnstype
]:
336 good_record
= self
.good_records
[dnstype
][0]
339 result
, out
, err
= self
.runsubcmd("dns", "add",
340 os
.environ
["SERVER"],
341 self
.zone
, "testrecord",
342 dnstype
, good_record
,
344 self
.assertCmdSuccess(result
, out
, err
, "Failed to add "
345 "record '%s' with type %s."
348 result
, out
, err
= self
.runsubcmd("dns", "update",
349 os
.environ
["SERVER"],
350 self
.zone
, "testrecord",
351 dnstype
, good_record
,
354 self
.assertCmdFail(result
, "Successfully updated valid "
355 "record '%s' of type '%s' to invalid "
356 "record '%s' of the same type."
357 % (good_record
, dnstype
, bad_record
))
359 result
, out
, err
= self
.runsubcmd("dns", "delete",
360 os
.environ
["SERVER"],
361 self
.zone
, "testrecord",
362 dnstype
, good_record
,
364 self
.assertCmdSuccess(result
, out
, err
, "Could not delete "
365 "valid record '%s' of type '%s'."
366 % (good_record
, dnstype
))
367 except AssertionError as e
:
368 num_failures
= num_failures
+ 1
369 failure_msgs
.append(e
)
373 for msg
in failure_msgs
:
375 self
.fail("Failed to reject invalid commands. %d total failures. "
376 "Errors above." % num_failures
)
378 def test_update_invalid_type(self
):
379 """Make sure that a record can't be updated to another type leaving
380 the data the same, where that data would be incompatible with
381 the new type. This is not always enforced at the C level.
383 We don't try with all types, because many types are compatible
384 in their representations (e.g. A records could be TXT or CNAME
385 records; PTR record values are exactly the same as CNAME
388 dnstypes
= ('A', 'AAAA', 'SRV')
389 for dnstype1
in dnstypes
:
390 record1
= self
.good_records
[dnstype1
][0]
391 result
, out
, err
= self
.runsubcmd("dns", "add",
392 os
.environ
["SERVER"],
393 self
.zone
, "testrecord",
396 self
.assertCmdSuccess(result
, out
, err
, "Failed to add "
397 "record %s with type %s."
398 % (record1
, dnstype1
))
400 for dnstype2
in dnstypes
:
401 if dnstype1
== dnstype2
:
404 record2
= self
.good_records
[dnstype2
][0]
406 # Check both ways: Give the current type and try to update,
407 # and give the new type and try to update.
408 result
, out
, err
= self
.runsubcmd("dns", "update",
409 os
.environ
["SERVER"],
410 self
.zone
, "testrecord",
412 record2
, self
.creds_string
)
413 self
.assertCmdFail(result
, "Successfully updated record '%s' "
414 "to '%s', even though the latter is of "
415 "type '%s' where '%s' was expected."
416 % (record1
, record2
, dnstype2
, dnstype1
))
418 result
, out
, err
= self
.runsubcmd("dns", "update",
419 os
.environ
["SERVER"],
420 self
.zone
, "testrecord",
421 dnstype2
, record1
, record2
,
423 self
.assertCmdFail(result
, "Successfully updated record "
424 "'%s' to '%s', even though the former "
425 "is of type '%s' where '%s' was expected."
426 % (record1
, record2
, dnstype1
, dnstype2
))
428 def test_update_valid_type(self
):
429 for dnstype
in self
.good_records
:
430 for record
in self
.good_records
[dnstype
]:
431 result
, out
, err
= self
.runsubcmd("dns", "add",
432 os
.environ
["SERVER"],
433 self
.zone
, "testrecord",
436 self
.assertCmdSuccess(result
, out
, err
, "Failed to add "
437 "record %s with type %s."
440 if record
== '.' and dnstype
!= 'TXT':
441 # This will fail because the update finds a match
442 # for "." that is actually "" (in
443 # dns_record_match()), then uses the "" record in
444 # a call to dns_to_dnsp_convert() which calls
445 # dns_name_check() which rejects "" as a bad DNS
446 # name. Maybe FIXME, maybe not.
449 # Update the record to be the same.
450 result
, out
, err
= self
.runsubcmd("dns", "update",
451 os
.environ
["SERVER"],
452 self
.zone
, "testrecord",
453 dnstype
, record
, record
,
455 self
.assertCmdSuccess(result
, out
, err
,
456 "Could not update record "
457 "'%s' to be exactly the same." % record
)
459 result
, out
, err
= self
.runsubcmd("dns", "delete",
460 os
.environ
["SERVER"],
461 self
.zone
, "testrecord",
464 self
.assertCmdSuccess(result
, out
, err
, "Could not delete "
465 "valid record '%s' of type '%s'."
468 for record
in self
.good_records
["SRV"]:
469 result
, out
, err
= self
.runsubcmd("dns", "add",
470 os
.environ
["SERVER"],
471 self
.zone
, "testrecord",
474 self
.assertCmdSuccess(result
, out
, err
, "Failed to add "
475 "record %s with type 'SRV'." % record
)
477 split
= record
.split()
478 new_bit
= str(int(split
[3]) + 1)
479 new_record
= '%s %s %s %s' % (split
[0], split
[1], split
[2], new_bit
)
481 result
, out
, err
= self
.runsubcmd("dns", "update",
482 os
.environ
["SERVER"],
483 self
.zone
, "testrecord",
485 new_record
, self
.creds_string
)
486 self
.assertCmdSuccess(result
, out
, err
, "Failed to update record "
487 "'%s' of type '%s' to '%s'."
488 % (record
, "SRV", new_record
))
490 result
, out
, err
= self
.runsubcmd("dns", "query",
491 os
.environ
["SERVER"],
492 self
.zone
, "testrecord",
493 "SRV", self
.creds_string
)
494 self
.assertCmdSuccess(result
, out
, err
, "Failed to query for "
495 "record '%s' of type '%s'."
496 % (new_record
, "SRV"))
498 result
, out
, err
= self
.runsubcmd("dns", "delete",
499 os
.environ
["SERVER"],
500 self
.zone
, "testrecord",
503 self
.assertCmdSuccess(result
, out
, err
, "Could not delete "
504 "valid record '%s' of type '%s'."
505 % (new_record
, "SRV"))
507 # Since 'dns update' takes the current value as a parameter, make sure
508 # we can't enter the wrong current value for a given record.
509 for dnstype
in self
.good_records
:
510 if len(self
.good_records
[dnstype
]) < 3:
511 continue # Not enough records of this type to do this test
513 used_record
= self
.good_records
[dnstype
][0]
514 unused_record
= self
.good_records
[dnstype
][1]
515 new_record
= self
.good_records
[dnstype
][2]
517 result
, out
, err
= self
.runsubcmd("dns", "add",
518 os
.environ
["SERVER"],
519 self
.zone
, "testrecord",
520 dnstype
, used_record
,
522 self
.assertCmdSuccess(result
, out
, err
, "Failed to add record %s "
523 "with type %s." % (used_record
, dnstype
))
525 result
, out
, err
= self
.runsubcmd("dns", "update",
526 os
.environ
["SERVER"],
527 self
.zone
, "testrecord",
528 dnstype
, unused_record
,
531 self
.assertCmdFail(result
, "Successfully updated record '%s' "
532 "from '%s' to '%s', even though the given "
533 "source record is incorrect."
534 % (used_record
, unused_record
, new_record
))
536 def test_invalid_types(self
):
537 result
, out
, err
= self
.runsubcmd("dns", "add",
538 os
.environ
["SERVER"],
539 self
.zone
, "testrecord",
542 self
.assertCmdFail(result
, "Successfully added record of type SOA, "
543 "when this type should not be available.")
544 self
.assertTrue("type SOA is not supported" in err
,
545 "Invalid error message '%s' when attempting to "
546 "add record of type SOA." % err
)
548 def test_add_overlapping_different_type(self
):
550 Make sure that we can add an entry with the same name as an existing one but a different type.
554 for dnstype1
in self
.good_records
:
555 record1
= self
.good_records
[dnstype1
][0]
556 for dnstype2
in self
.good_records
:
557 # Only do some subset of dns types, otherwise it takes a long time.
562 if dnstype1
== dnstype2
:
565 record2
= self
.good_records
[dnstype2
][0]
567 result
, out
, err
= self
.runsubcmd("dns", "add",
568 os
.environ
["SERVER"],
569 self
.zone
, "testrecord",
572 self
.assertCmdSuccess(result
, out
, err
, "Failed to add record "
573 "'%s' of type '%s'." % (record1
, dnstype1
))
575 result
, out
, err
= self
.runsubcmd("dns", "add",
576 os
.environ
["SERVER"],
577 self
.zone
, "testrecord",
580 self
.assertCmdSuccess(result
, out
, err
, "Failed to add record "
581 "'%s' of type '%s' when a record '%s' "
582 "of type '%s' with the same name exists."
583 % (record1
, dnstype1
, record2
, dnstype2
))
585 result
, out
, err
= self
.runsubcmd("dns", "query",
586 os
.environ
["SERVER"],
587 self
.zone
, "testrecord",
588 dnstype1
, self
.creds_string
)
589 self
.assertCmdSuccess(result
, out
, err
, "Failed to query for "
590 "record '%s' of type '%s' when a new "
591 "record '%s' of type '%s' with the same "
593 % (record1
, dnstype1
, record2
, dnstype2
))
595 result
, out
, err
= self
.runsubcmd("dns", "query",
596 os
.environ
["SERVER"],
597 self
.zone
, "testrecord",
598 dnstype2
, self
.creds_string
)
599 self
.assertCmdSuccess(result
, out
, err
, "Failed to query "
600 "record '%s' of type '%s' which should "
601 "have been added with the same name as "
602 "record '%s' of type '%s'."
603 % (record2
, dnstype2
, record1
, dnstype1
))
605 result
, out
, err
= self
.runsubcmd("dns", "delete",
606 os
.environ
["SERVER"],
607 self
.zone
, "testrecord",
610 self
.assertCmdSuccess(result
, out
, err
, "Failed to delete "
611 "record '%s' of type '%s'."
612 % (record1
, dnstype1
))
614 result
, out
, err
= self
.runsubcmd("dns", "delete",
615 os
.environ
["SERVER"],
616 self
.zone
, "testrecord",
619 self
.assertCmdSuccess(result
, out
, err
, "Failed to delete "
620 "record '%s' of type '%s'."
621 % (record2
, dnstype2
))
623 def test_query_deleted_record(self
):
624 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
625 "testrecord", "A", self
.testip
, self
.creds_string
)
626 self
.runsubcmd("dns", "delete", os
.environ
["SERVER"], self
.zone
,
627 "testrecord", "A", self
.testip
, self
.creds_string
)
629 result
, out
, err
= self
.runsubcmd("dns", "query",
630 os
.environ
["SERVER"],
631 self
.zone
, "testrecord",
632 "A", self
.creds_string
)
633 self
.assertCmdFail(result
)
635 def test_add_duplicate_record(self
):
636 for record_type
in self
.good_records
:
637 result
, out
, err
= self
.runsubcmd("dns", "add",
638 os
.environ
["SERVER"],
639 self
.zone
, "testrecord",
641 self
.good_records
[record_type
][0],
643 self
.assertCmdSuccess(result
, out
, err
)
644 result
, out
, err
= self
.runsubcmd("dns", "add",
645 os
.environ
["SERVER"],
646 self
.zone
, "testrecord",
648 self
.good_records
[record_type
][0],
650 self
.assertCmdFail(result
)
651 result
, out
, err
= self
.runsubcmd("dns", "query",
652 os
.environ
["SERVER"],
653 self
.zone
, "testrecord",
654 record_type
, self
.creds_string
)
655 self
.assertCmdSuccess(result
, out
, err
)
656 result
, out
, err
= self
.runsubcmd("dns", "delete",
657 os
.environ
["SERVER"],
658 self
.zone
, "testrecord",
660 self
.good_records
[record_type
][0],
662 self
.assertCmdSuccess(result
, out
, err
)
664 def test_remove_deleted_record(self
):
665 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
666 "testrecord", "A", self
.testip
, self
.creds_string
)
667 self
.runsubcmd("dns", "delete", os
.environ
["SERVER"], self
.zone
,
668 "testrecord", "A", self
.testip
, self
.creds_string
)
670 # Attempting to delete a record that has already been deleted or has never existed should fail
671 result
, out
, err
= self
.runsubcmd("dns", "delete",
672 os
.environ
["SERVER"],
673 self
.zone
, "testrecord",
674 "A", self
.testip
, self
.creds_string
)
675 self
.assertCmdFail(result
)
676 result
, out
, err
= self
.runsubcmd("dns", "query",
677 os
.environ
["SERVER"],
678 self
.zone
, "testrecord",
679 "A", self
.creds_string
)
680 self
.assertCmdFail(result
)
681 result
, out
, err
= self
.runsubcmd("dns", "delete",
682 os
.environ
["SERVER"],
683 self
.zone
, "testrecord2",
684 "A", self
.testip
, self
.creds_string
)
685 self
.assertCmdFail(result
)
687 def test_cleanup_record(self
):
689 Test dns cleanup command is working fine.
693 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
694 'testa', "A", self
.testip
, self
.creds_string
)
696 # the above A record points to this host
697 dnshostname
= '{0}.{1}'.format('testa', self
.zone
.lower())
699 # add a CNAME record points to above host
700 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
701 'testcname', "CNAME", dnshostname
, self
.creds_string
)
704 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
705 'testns', "NS", dnshostname
, self
.creds_string
)
707 # add a PTR record points to above host
708 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
709 'testptr', "PTR", dnshostname
, self
.creds_string
)
711 # add a SRV record points to above host
712 srv_record
= "{0} 65530 65530 65530".format(dnshostname
)
713 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
714 'testsrv', "SRV", srv_record
, self
.creds_string
)
716 # cleanup record for this dns host
717 self
.runsubcmd("dns", "cleanup", os
.environ
["SERVER"],
718 dnshostname
, self
.creds_string
)
720 # all records should be marked as dNSTombstoned
721 for record_name
in ['testa', 'testcname', 'testns', 'testptr', 'testsrv']:
723 records
= self
.samdb
.search(
724 base
="DC=DomainDnsZones,{0}".format(self
.samdb
.get_default_basedn()),
725 scope
=ldb
.SCOPE_SUBTREE
,
726 expression
="(&(objectClass=dnsNode)(name={0}))".format(record_name
),
727 attrs
=["dNSTombstoned"])
729 self
.assertEqual(len(records
), 1)
730 for record
in records
:
731 self
.assertEqual(str(record
['dNSTombstoned']), 'TRUE')
733 def test_cleanup_record_no_A_record(self
):
735 Test dns cleanup command works with no A record.
739 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
740 'notesta', "A", self
.testip
, self
.creds_string
)
742 # the above A record points to this host
743 dnshostname
= '{0}.{1}'.format('testa', self
.zone
.lower())
745 # add a CNAME record points to above host
746 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
747 'notestcname', "CNAME", dnshostname
, self
.creds_string
)
750 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
751 'notestns', "NS", dnshostname
, self
.creds_string
)
753 # add a PTR record points to above host
754 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
755 'notestptr', "PTR", dnshostname
, self
.creds_string
)
757 # add a SRV record points to above host
758 srv_record
= "{0} 65530 65530 65530".format(dnshostname
)
759 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
760 'notestsrv', "SRV", srv_record
, self
.creds_string
)
762 # Remove the initial A record (leading to hanging references)
763 self
.runsubcmd("dns", "delete", os
.environ
["SERVER"], self
.zone
,
764 'notesta', "A", self
.testip
, self
.creds_string
)
766 # cleanup record for this dns host
767 self
.runsubcmd("dns", "cleanup", os
.environ
["SERVER"],
768 dnshostname
, self
.creds_string
)
770 # all records should be marked as dNSTombstoned
771 for record_name
in ['notestcname', 'notestns', 'notestptr', 'notestsrv']:
773 records
= self
.samdb
.search(
774 base
="DC=DomainDnsZones,{0}".format(self
.samdb
.get_default_basedn()),
775 scope
=ldb
.SCOPE_SUBTREE
,
776 expression
="(&(objectClass=dnsNode)(name={0}))".format(record_name
),
777 attrs
=["dNSTombstoned"])
779 self
.assertEqual(len(records
), 1)
780 for record
in records
:
781 self
.assertEqual(str(record
['dNSTombstoned']), 'TRUE')
783 def test_cleanup_multi_srv_record(self
):
785 Test dns cleanup command for multi-valued SRV record.
788 - Add 2 A records host1 and host2
789 - Add a SRV record srv1 and points to both host1 and host2
790 - Run cleanup command for host1
791 - Check records for srv1, data for host1 should be gone and host2 is kept.
794 hosts
= ['host1', 'host2'] # A record names
799 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
800 host
, "A", self
.testip
, self
.creds_string
)
802 # the above A record points to this host
803 dnshostname
= '{0}.{1}'.format(host
, self
.zone
.lower())
805 # add a SRV record points to above host
806 srv_record
= "{0} 65530 65530 65530".format(dnshostname
)
807 self
.runsubcmd("dns", "add", os
.environ
["SERVER"], self
.zone
,
808 srv_name
, "SRV", srv_record
, self
.creds_string
)
810 records
= self
.samdb
.search(
811 base
="DC=DomainDnsZones,{0}".format(self
.samdb
.get_default_basedn()),
812 scope
=ldb
.SCOPE_SUBTREE
,
813 expression
="(&(objectClass=dnsNode)(name={0}))".format(srv_name
),
815 # should have 2 records here
816 self
.assertEqual(len(records
[0]['dnsRecord']), 2)
818 # cleanup record for dns host1
819 dnshostname1
= 'host1.{0}'.format(self
.zone
.lower())
820 self
.runsubcmd("dns", "cleanup", os
.environ
["SERVER"],
821 dnshostname1
, self
.creds_string
)
823 records
= self
.samdb
.search(
824 base
="DC=DomainDnsZones,{0}".format(self
.samdb
.get_default_basedn()),
825 scope
=ldb
.SCOPE_SUBTREE
,
826 expression
="(&(objectClass=dnsNode)(name={0}))".format(srv_name
),
827 attrs
=['dnsRecord', 'dNSTombstoned'])
829 # dnsRecord for host1 should be deleted
830 self
.assertEqual(len(records
[0]['dnsRecord']), 1)
833 dns_record_bin
= records
[0]['dnsRecord'][0]
834 dns_record_obj
= ndr_unpack(dnsp
.DnssrvRpcRecord
, dns_record_bin
)
836 # dnsRecord for host2 is still there and is the only one
837 dnshostname2
= 'host2.{0}'.format(self
.zone
.lower())
838 self
.assertEqual(dns_record_obj
.data
.nameTarget
, dnshostname2
)
840 # assert that the record isn't spuriously tombstoned
841 self
.assertTrue('dNSTombstoned' not in records
[0] or
842 str(records
[0]['dNSTombstoned']) == 'FALSE')
844 def test_dns_wildcards(self
):
846 Ensure that DNS wild card entries can be added deleted and queried
850 records
= [("*.", "MISS", "A", "1.1.1.1"),
851 ("*.SAMDOM", "MISS.SAMDOM", "A", "1.1.1.2")]
852 for (name
, miss
, dnstype
, record
) in records
:
854 result
, out
, err
= self
.runsubcmd("dns", "add",
855 os
.environ
["SERVER"],
859 self
.assertCmdSuccess(
863 ("Failed to add record %s (%s) with type %s."
864 % (name
, record
, dnstype
)))
866 result
, out
, err
= self
.runsubcmd("dns", "query",
867 os
.environ
["SERVER"],
871 self
.assertCmdSuccess(
875 ("Failed to query record %s with qualifier %s."
876 % (record
, dnstype
)))
878 # dns tool does not perform dns wildcard search if the name
880 result
, out
, err
= self
.runsubcmd("dns", "query",
881 os
.environ
["SERVER"],
887 ("Failed to query record %s with qualifier %s."
888 % (record
, dnstype
)))
890 result
, out
, err
= self
.runsubcmd("dns", "delete",
891 os
.environ
["SERVER"],
895 self
.assertCmdSuccess(
899 ("Failed to remove record %s with type %s."
900 % (record
, dnstype
)))
901 except AssertionError as e
:
902 num_failures
= num_failures
+ 1
903 failure_msgs
.append(e
)
906 for msg
in failure_msgs
:
908 self
.fail("Failed to accept valid commands. %d total failures."
909 "Errors above." % num_failures
)
911 def test_serverinfo(self
):
912 for v
in ['w2k', 'dotnet', 'longhorn']:
913 result
, out
, err
= self
.runsubcmd("dns",
915 "--client-version", v
,
916 os
.environ
["SERVER"],
918 self
.assertCmdSuccess(result
,
921 "Failed to print serverinfo with "
922 "client version %s" % v
)
923 self
.assertTrue(out
!= '')
925 def test_zoneinfo(self
):
926 result
, out
, err
= self
.runsubcmd("dns",
928 os
.environ
["SERVER"],
931 self
.assertCmdSuccess(result
,
934 "Failed to print zoneinfo")
935 self
.assertTrue(out
!= '')
937 def test_zoneoptions_aging(self
):
938 for options
, vals
, error
in (
939 (['--aging=1'], {'fAging': 'TRUE'}, False),
940 (['--aging=0'], {'fAging': 'FALSE'}, False),
941 (['--aging=-1'], {'fAging': 'FALSE'}, True),
942 (['--aging=2'], {}, True),
943 (['--aging=2', '--norefreshinterval=1'], {}, True),
944 (['--aging=1', '--norefreshinterval=1'],
945 {'fAging': 'TRUE', 'dwNoRefreshInterval': '1'}, False),
946 (['--aging=1', '--norefreshinterval=0'],
947 {'fAging': 'TRUE', 'dwNoRefreshInterval': '0'}, False),
948 (['--aging=0', '--norefreshinterval=99', '--refreshinterval=99'],
950 'dwNoRefreshInterval': '99',
951 'dwRefreshInterval': '99'}, False),
952 (['--aging=0', '--norefreshinterval=-99', '--refreshinterval=99'],
954 (['--refreshinterval=9999999'], {}, True),
955 (['--norefreshinterval=9999999'], {}, True),
957 result
, out
, err
= self
.runsubcmd("dns",
959 os
.environ
["SERVER"],
964 self
.assertCmdFail(result
, "zoneoptions should fail")
966 self
.assertCmdSuccess(result
,
969 "zoneoptions shouldn't fail")
972 info_r
, info_out
, info_err
= self
.runsubcmd("dns",
974 os
.environ
["SERVER"],
978 self
.assertCmdSuccess(info_r
,
981 "zoneinfo shouldn't fail after zoneoptions")
983 info
= {k
: v
for k
, v
in re
.findall(r
'^\s*(\w+)\s*:\s*(\w+)\s*$',
986 for k
, v
in vals
.items():
987 self
.assertIn(k
, info
)
988 self
.assertEqual(v
, info
[k
])
991 def ldap_add_node_with_records(self
, name
, records
):
992 dn
= (f
"DC={name},DC={self.zone},CN=MicrosoftDNS,DC=DomainDNSZones,"
993 f
"{self.samdb.get_default_basedn()}")
997 rec
= dnsp
.DnssrvRpcRecord()
998 rec
.wType
= r
.get('wType', dnsp
.DNS_TYPE_A
)
999 rec
.rank
= dnsp
.DNS_RANK_ZONE
1000 rec
.dwTtlSeconds
= 900
1001 rec
.dwTimeStamp
= r
.get('dwTimeStamp', 0)
1002 rec
.data
= r
.get('data', '10.10.10.10')
1003 dns_records
.append(ndr_pack(rec
))
1005 msg
= ldb
.Message
.from_dict(self
.samdb
,
1007 "objectClass": ["top", "dnsNode"],
1008 'dnsRecord': dns_records
1012 def get_timestamp_map(self
):
1013 re_wtypes
= (dnsp
.DNS_TYPE_A
,
1018 now
= dsdb_dns
.unix_to_dns_timestamp(int(t
))
1020 records
= self
.get_all_records(self
.zone
)
1022 for k
, recs
in records
.items():
1026 r
= ndr_unpack(dnsp
.DnssrvRpcRecord
, rec
)
1027 timestamp
= r
.dwTimeStamp
1028 if abs(timestamp
- now
) < 3:
1029 timestamp
= 'nowish'
1031 if r
.wType
in re_wtypes
:
1032 m
.append(('R', timestamp
))
1034 m
.append(('-', timestamp
))
1039 def test_zoneoptions_mark_records(self
):
1040 self
.maxDiff
= 10000
1041 # We need a number of records to work with, so we'll use part
1042 # of our known good records list, using three different names
1043 # to test the regex. All these records will be static.
1044 for dnstype
in self
.good_records
:
1045 for record
in self
.good_records
[dnstype
][:2]:
1046 self
.runsubcmd("dns", "add",
1047 os
.environ
["SERVER"],
1048 self
.zone
, "frobitz",
1051 self
.runsubcmd("dns", "add",
1052 os
.environ
["SERVER"],
1053 self
.zone
, "weergly",
1056 self
.runsubcmd("dns", "add",
1057 os
.environ
["SERVER"],
1058 self
.zone
, "snizle",
1062 # and we also want some that aren't static, and some mixed
1063 # static/dynamic records.
1064 # timestamps are in hours since 1601; now ~= 3.7 million
1065 for ts
in (0, 100, 10 ** 6, 10 ** 7):
1067 self
.ldap_add_node_with_records(name
, [{"dwTimeStamp": ts
}])
1070 for ts
in (0, 100, 10 ** 6, 10 ** 7):
1071 addr
= f
'10.{(ts >> 16) & 255}.{(ts >> 8) & 255}.{ts & 255}'
1072 recs
.append({"dwTimeStamp": ts
, "data": addr
})
1074 self
.ldap_add_node_with_records("ts-multi", recs
)
1076 # get the state of ALL records.
1077 # then we make assertions about the diffs, keeping track of
1078 # the current state.
1080 tsmap
= self
.get_timestamp_map()
1084 for options
, diff
, output_substrings
, error
in (
1085 # --mark-old-records-static
1086 # --mark-records-static-regex
1087 # --mark-records-dynamic-regex
1089 ['--mark-old-records-static=1971-13-04'],
1095 # using --dry-run, should be no change, but output.
1096 ['--mark-old-records-static=1971-03-04', '--dry-run'],
1099 "would make 1/1 records static on ts-1000000.zone.",
1100 "would make 1/1 records static on ts-100.zone.",
1101 "would make 2/4 records static on ts-multi.zone.",
1106 # timestamps < ~ 3.25 million are now static
1107 ['--mark-old-records-static=1971-03-04'],
1109 'ts-100': [('R', 0)],
1110 'ts-1000000': [('R', 0)],
1111 'ts-multi': [('R', 0), ('R', 0), ('R', 0), ('R', 10000000)]
1114 "made 1/1 records static on ts-1000000.zone.",
1115 "made 1/1 records static on ts-100.zone.",
1116 "made 2/4 records static on ts-multi.zone.",
1121 # no change, old records already static
1122 ['--mark-old-records-static=1972-03-04'],
1128 # no change, samba-tool added records already static
1129 ['--mark-records-static-regex=sniz'],
1135 # snizle has 2 A, 2 AAAA, 10 fancy, and 2 TXT records, in
1137 # the A, AAAA, and TXT records should be dynamic
1138 ['--mark-records-dynamic-regex=sniz'],
1139 {'snizle': [('R', 'nowish'),
1156 ['made 6/16 records dynamic on snizle.zone.'],
1160 # This regex should catch snizle, weergly, and ts-*
1161 # but we're doing dry-run so no change
1162 ['--mark-records-dynamic-regex=[sw]', '-n'],
1164 ['would make 3/4 records dynamic on ts-multi.zone.',
1165 'would make 1/1 records dynamic on ts-0.zone.',
1166 'would make 1/1 records dynamic on ts-1000000.zone.',
1167 'would make 6/16 records dynamic on weergly.zone.',
1168 'would make 1/1 records dynamic on ts-100.zone.'
1173 # This regex should catch snizle and frobitz
1174 # but snizle has already been changed.
1175 ['--mark-records-dynamic-regex=z'],
1176 {'frobitz': [('R', 'nowish'),
1193 ['made 6/16 records dynamic on frobitz.zone.'],
1197 # This regex should catch snizle, frobitz, and
1198 # ts-multi. Note that the 1e7 ts-multi record is
1199 # already dynamic and doesn't change.
1200 ['--mark-records-dynamic-regex=[i]'],
1201 {'ts-multi': [('R', 'nowish'),
1206 ['made 3/4 records dynamic on ts-multi.zone.'],
1210 # matches no records
1211 ['--mark-records-dynamic-regex=^aloooooo[qw]+'],
1217 # This should be an error, as only one --mark-*
1218 # argument is allowed at a time
1219 ['--mark-records-dynamic-regex=.',
1220 '--mark-records-static-regex=.',
1227 # This should also be an error
1228 ['--mark-old-records-static=1997-07-07',
1229 '--mark-records-static-regex=.',
1236 # This should not be an error. --aging and refresh
1237 # options can be mixed with --mark ones.
1238 ['--mark-old-records-static=1997-07-07',
1246 # This regex should catch weergly, but all the
1247 # records are already static,
1248 ['--mark-records-static-regex=wee'],
1254 # Make frobitz static again.
1255 ['--mark-records-static-regex=obi'],
1256 {'frobitz': [('R', 0),
1273 ['made 6/16 records static on frobitz.zone.'],
1277 # would make almost everything static, but --dry-run
1278 ['--mark-old-records-static=2222-03-04', '--dry-run'],
1281 'would make 6/16 records static on snizle.zone.',
1282 'would make 3/4 records static on ts-multi.zone.'
1287 # make everything static
1288 ['--mark-records-static-regex=.'],
1289 {'snizle': [('R', 0),
1305 'ts-10000000': [('R', 0)],
1306 'ts-multi': [('R', 0), ('R', 0), ('R', 0), ('R', 0)]
1309 'made 4/4 records static on ts-multi.zone.',
1310 'made 1/1 records static on ts-10000000.zone.',
1311 'made 6/16 records static on snizle.zone.',
1316 # make everything dynamic that can be
1317 ['--mark-records-dynamic-regex=.'],
1318 {'frobitz': [('R', 'nowish'),
1334 'snizle': [('R', 'nowish'),
1350 'ts-0': [('R', 'nowish')],
1351 'ts-100': [('R', 'nowish')],
1352 'ts-1000000': [('R', 'nowish')],
1353 'ts-10000000': [('R', 'nowish')],
1354 'ts-multi': [('R', 'nowish'),
1358 'weergly': [('R', 'nowish'),
1376 'made 4/4 records dynamic on ts-multi.zone.',
1377 'made 6/16 records dynamic on snizle.zone.',
1378 'made 1/1 records dynamic on ts-0.zone.',
1379 'made 1/1 records dynamic on ts-1000000.zone.',
1380 'made 1/1 records dynamic on ts-10000000.zone.',
1381 'made 1/1 records dynamic on ts-100.zone.',
1382 'made 6/16 records dynamic on frobitz.zone.',
1383 'made 6/16 records dynamic on weergly.zone.',
1388 result
, out
, err
= self
.runsubcmd("dns",
1390 os
.environ
["SERVER"],
1395 self
.assertCmdFail(result
, f
"zoneoptions should fail ({error})")
1397 self
.assertCmdSuccess(result
,
1400 "zoneoptions shouldn't fail")
1402 new_tsmap
= self
.get_timestamp_map()
1405 self
.assertEqual(sorted(new_tsmap
), sorted(tsmap
))
1408 if tsmap
[k
] != new_tsmap
[k
]:
1409 changes
[k
] = new_tsmap
[k
]
1411 self
.assertEqual(diff
, changes
)
1413 for s
in output_substrings
:
1414 self
.assertIn(s
, out
)
1417 def test_zonecreate_dns_domain_directory_partition(self
):
1418 zone
= "test-dns-domain-dp-zone"
1419 dns_dp_opt
= "--dns-directory-partition=domain"
1421 result
, out
, err
= self
.runsubcmd("dns",
1423 os
.environ
["SERVER"],
1427 self
.assertCmdSuccess(result
,
1430 "Failed to create zone with "
1431 "--dns-directory-partition option")
1432 self
.assertTrue('Zone %s created successfully' % zone
in out
,
1433 "Unexpected output: %s")
1435 result
, out
, err
= self
.runsubcmd("dns",
1437 os
.environ
["SERVER"],
1440 self
.assertCmdSuccess(result
, out
, err
)
1441 self
.assertTrue("DNS_DP_DOMAIN_DEFAULT" in out
,
1442 "Missing DNS_DP_DOMAIN_DEFAULT flag")
1444 result
, out
, err
= self
.runsubcmd("dns",
1446 os
.environ
["SERVER"],
1449 self
.assertCmdSuccess(result
, out
, err
,
1450 "Failed to delete zone in domain DNS directory "
1452 result
, out
, err
= self
.runsubcmd("dns",
1454 os
.environ
["SERVER"],
1456 self
.assertCmdSuccess(result
, out
, err
,
1457 "Failed to delete zone in domain DNS directory "
1459 self
.assertTrue(zone
not in out
,
1460 "Deleted zone still exists")
1462 def test_zonecreate_dns_forest_directory_partition(self
):
1463 zone
= "test-dns-forest-dp-zone"
1464 dns_dp_opt
= "--dns-directory-partition=forest"
1466 result
, out
, err
= self
.runsubcmd("dns",
1468 os
.environ
["SERVER"],
1472 self
.assertCmdSuccess(result
,
1475 "Failed to create zone with "
1476 "--dns-directory-partition option")
1477 self
.assertTrue('Zone %s created successfully' % zone
in out
,
1478 "Unexpected output: %s")
1480 result
, out
, err
= self
.runsubcmd("dns",
1482 os
.environ
["SERVER"],
1485 self
.assertCmdSuccess(result
, out
, err
)
1486 self
.assertTrue("DNS_DP_FOREST_DEFAULT" in out
,
1487 "Missing DNS_DP_FOREST_DEFAULT flag")
1489 result
, out
, err
= self
.runsubcmd("dns",
1491 os
.environ
["SERVER"],
1494 self
.assertCmdSuccess(result
, out
, err
,
1495 "Failed to delete zone in forest DNS directory "
1498 result
, out
, err
= self
.runsubcmd("dns",
1500 os
.environ
["SERVER"],
1502 self
.assertCmdSuccess(result
, out
, err
,
1503 "Failed to delete zone in forest DNS directory "
1505 self
.assertTrue(zone
not in out
,
1506 "Deleted zone still exists")