17d3c960bdb42a4fa8fb0f4075551b5f3a27abbb
[samba.git] / source4 / dsdb / tests / python / sites.py
1 #!/usr/bin/env python
2 #
3 # Unit tests for sites manipulation in samba
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
5 #
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 from __future__ import print_function
20
21 import optparse
22 import sys
23 sys.path.insert(0, "bin/python")
24 import samba
25
26 from samba.tests.subunitrun import TestProgram, SubunitOptions
27
28 import samba.getopt as options
29 from samba import sites
30 from samba import subnets
31 from samba.auth import system_session
32 from samba.samdb import SamDB
33 from samba import gensec
34 from samba.credentials import Credentials, DONT_USE_KERBEROS
35 import samba.tests
36 from samba.tests import delete_force
37 from samba.dcerpc import security
38 from ldb import SCOPE_SUBTREE, LdbError, ERR_INSUFFICIENT_ACCESS_RIGHTS
39
40 parser = optparse.OptionParser("sites.py [options] <host>")
41 sambaopts = options.SambaOptions(parser)
42 parser.add_option_group(sambaopts)
43 parser.add_option_group(options.VersionOptions(parser))
44
45 # use command line creds if available
46 credopts = options.CredentialsOptions(parser)
47 parser.add_option_group(credopts)
48 subunitopts = SubunitOptions(parser)
49 parser.add_option_group(subunitopts)
50
51 opts, args = parser.parse_args()
52
53 if len(args) < 1:
54     parser.print_usage()
55     sys.exit(1)
56
57 host = args[0]
58 if not "://" in host:
59     ldaphost = "ldap://%s" % host
60 else:
61     ldaphost = host
62
63 lp = sambaopts.get_loadparm()
64 creds = credopts.get_credentials(lp)
65
66 #
67 # Tests start here
68 #
69
70 class SitesBaseTests(samba.tests.TestCase):
71
72     def setUp(self):
73         super(SitesBaseTests, self).setUp()
74         self.ldb = SamDB(ldaphost, credentials=creds,
75                          session_info=system_session(lp), lp=lp)
76         self.base_dn = self.ldb.domain_dn()
77         self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
78         self.configuration_dn = self.ldb.get_config_basedn().get_linearized()
79
80     def get_user_dn(self, name):
81         return "CN=%s,CN=Users,%s" % (name, self.base_dn)
82
83
84 #tests on sites
85 class SimpleSitesTests(SitesBaseTests):
86
87     def test_create_and_delete(self):
88         """test creation and deletion of 1 site"""
89
90         sites.create_site(self.ldb, self.ldb.get_config_basedn(),
91                           "testsamba")
92
93         self.assertRaises(sites.SiteAlreadyExistsException,
94                           sites.create_site, self.ldb,
95                           self.ldb.get_config_basedn(),
96                           "testsamba")
97
98         sites.delete_site(self.ldb, self.ldb.get_config_basedn(),
99                           "testsamba")
100
101         self.assertRaises(sites.SiteNotFoundException,
102                           sites.delete_site, self.ldb,
103                           self.ldb.get_config_basedn(),
104                           "testsamba")
105
106     def test_delete_not_empty(self):
107         """test removal of 1 site with servers"""
108
109         self.assertRaises(sites.SiteServerNotEmptyException,
110                           sites.delete_site, self.ldb,
111                           self.ldb.get_config_basedn(),
112                           "Default-First-Site-Name")
113
114
115 # tests for subnets
116 class SimpleSubnetTests(SitesBaseTests):
117
118     def setUp(self):
119         super(SimpleSubnetTests, self).setUp()
120         self.basedn = self.ldb.get_config_basedn()
121         self.sitename = "testsite"
122         self.sitename2 = "testsite2"
123         self.ldb.transaction_start()
124         sites.create_site(self.ldb, self.basedn, self.sitename)
125         sites.create_site(self.ldb, self.basedn, self.sitename2)
126         self.ldb.transaction_commit()
127
128     def tearDown(self):
129         self.ldb.transaction_start()
130         sites.delete_site(self.ldb, self.basedn, self.sitename)
131         sites.delete_site(self.ldb, self.basedn, self.sitename2)
132         self.ldb.transaction_commit()
133         super(SimpleSubnetTests, self).tearDown()
134
135     def test_create_delete(self):
136         """Create a subnet and delete it again."""
137         basedn = self.ldb.get_config_basedn()
138         cidr = "10.11.12.0/24"
139
140         subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
141
142         self.assertRaises(subnets.SubnetAlreadyExists,
143                           subnets.create_subnet, self.ldb, basedn, cidr,
144                           self.sitename)
145
146         subnets.delete_subnet(self.ldb, basedn, cidr)
147
148         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
149                               expression='(&(objectclass=subnet)(cn=%s))' % cidr)
150
151         self.assertEqual(len(ret), 0, 'Failed to delete subnet %s' % cidr)
152
153     def test_create_shift_delete(self):
154         """Create a subnet, shift it to another site, then delete it."""
155         basedn = self.ldb.get_config_basedn()
156         cidr = "10.11.12.0/24"
157
158         subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
159
160         subnets.set_subnet_site(self.ldb, basedn, cidr, self.sitename2)
161
162         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
163                               expression='(&(objectclass=subnet)(cn=%s))' % cidr)
164
165         sites = ret[0]['siteObject']
166         self.assertEqual(len(sites), 1)
167         self.assertEqual(sites[0],
168                          'CN=testsite2,CN=Sites,%s' % self.ldb.get_config_basedn())
169
170         self.assertRaises(subnets.SubnetAlreadyExists,
171                           subnets.create_subnet, self.ldb, basedn, cidr,
172                           self.sitename)
173
174         subnets.delete_subnet(self.ldb, basedn, cidr)
175
176         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
177                               expression='(&(objectclass=subnet)(cn=%s))' % cidr)
178
179         self.assertEqual(len(ret), 0, 'Failed to delete subnet %s' % cidr)
180
181     def test_delete_subnet_that_does_not_exist(self):
182         """Ensure we can't delete a site that isn't there."""
183         basedn = self.ldb.get_config_basedn()
184         cidr = "10.15.0.0/16"
185
186         self.assertRaises(subnets.SubnetNotFound,
187                           subnets.delete_subnet, self.ldb, basedn, cidr)
188
189     def get_user_and_ldb(self, username, password, hostname=ldaphost):
190         """Get a connection for a temporarily user that will vanish as soon as
191         the test is over."""
192         user = self.ldb.newuser(username, password)
193         creds_tmp = Credentials()
194         creds_tmp.set_username(username)
195         creds_tmp.set_password(password)
196         creds_tmp.set_domain(creds.get_domain())
197         creds_tmp.set_realm(creds.get_realm())
198         creds_tmp.set_workstation(creds.get_workstation())
199         creds_tmp.set_gensec_features(creds_tmp.get_gensec_features()
200                                       | gensec.FEATURE_SEAL)
201         creds_tmp.set_kerberos_state(DONT_USE_KERBEROS)
202         ldb_target = SamDB(url=hostname, credentials=creds_tmp, lp=lp)
203         self.addCleanup(delete_force, self.ldb, self.get_user_dn(username))
204         return (user, ldb_target)
205
206     def test_rename_delete_good_subnet_to_good_subnet_other_user(self):
207         """Make sure that we can't rename or delete subnets when we aren't
208         admin."""
209         basedn = self.ldb.get_config_basedn()
210         cidr = "10.16.0.0/24"
211         new_cidr = "10.16.1.0/24"
212         subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
213         user, non_admin_ldb = self.get_user_and_ldb("notadmin", "samba123@")
214         try:
215             subnets.rename_subnet(non_admin_ldb, basedn, cidr, new_cidr)
216         except LdbError as e:
217             self.assertEqual(e.args[0], ERR_INSUFFICIENT_ACCESS_RIGHTS,
218                              ("subnet rename by non-admin failed "
219                               "in the wrong way: %s" % e))
220         else:
221             self.fail("subnet rename by non-admin succeeded: %s" % e)
222
223         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
224                               expression='(&(objectclass=subnet)(cn=%s))' % cidr)
225
226         self.assertEqual(len(ret), 1, ('Subnet %s destroyed or renamed '
227                                        'by non-admin' % cidr))
228
229         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
230                               expression=('(&(objectclass=subnet)(cn=%s))'
231                                           % new_cidr))
232
233         self.assertEqual(len(ret), 0,
234                          'New subnet %s created by non-admin' % cidr)
235
236         try:
237             subnets.delete_subnet(non_admin_ldb, basedn, cidr)
238         except LdbError as e:
239             self.assertEqual(e.args[0], ERR_INSUFFICIENT_ACCESS_RIGHTS,
240                              ("subnet delete by non-admin failed "
241                               "in the wrong way: %s" % e))
242         else:
243             self.fail("subnet delete by non-admin succeeded: %s" % e)
244
245         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
246                               expression='(&(objectclass=subnet)(cn=%s))' % cidr)
247
248         self.assertEqual(len(ret), 1, 'Subnet %s deleted non-admin' % cidr)
249
250         subnets.delete_subnet(self.ldb, basedn, cidr)
251
252     def test_create_good_subnet_other_user(self):
253         """Make sure that we can't create subnets when we aren't admin."""
254         basedn = self.ldb.get_config_basedn()
255         cidr = "10.16.0.0/24"
256         user, non_admin_ldb = self.get_user_and_ldb("notadmin", "samba123@")
257         try:
258             subnets.create_subnet(non_admin_ldb, basedn, cidr, self.sitename)
259         except LdbError as e:
260             self.assertEqual(e.args[0], ERR_INSUFFICIENT_ACCESS_RIGHTS,
261                              ("subnet create by non-admin failed "
262                               "in the wrong way: %s" % e))
263         else:
264             subnets.delete_subnet(self.ldb, basedn, cidr)
265             self.fail("subnet create by non-admin succeeded: %s")
266
267         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
268                               expression='(&(objectclass=subnet)(cn=%s))' % cidr)
269
270         self.assertEqual(len(ret), 0, 'New subnet %s created by non-admin' % cidr)
271
272     def test_rename_good_subnet_to_good_subnet(self):
273         """Make sure that we can rename subnets"""
274         basedn = self.ldb.get_config_basedn()
275         cidr = "10.16.0.0/24"
276         new_cidr = "10.16.1.0/24"
277
278         subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
279
280         subnets.rename_subnet(self.ldb, basedn, cidr, new_cidr)
281
282         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
283                               expression='(&(objectclass=subnet)(cn=%s))' % new_cidr)
284
285         self.assertEqual(len(ret), 1, 'Failed to rename subnet %s' % cidr)
286
287         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
288                               expression='(&(objectclass=subnet)(cn=%s))' % cidr)
289
290         self.assertEqual(len(ret), 0, 'Failed to remove old subnet during rename %s' % cidr)
291
292         subnets.delete_subnet(self.ldb, basedn, new_cidr)
293
294     def test_rename_good_subnet_to_bad_subnet(self):
295         """Make sure that the CIDR checking runs during rename"""
296         basedn = self.ldb.get_config_basedn()
297         cidr = "10.17.0.0/24"
298         bad_cidr = "10.11.12.0/14"
299
300         subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
301
302         self.assertRaises(subnets.SubnetInvalid, subnets.rename_subnet,
303                           self.ldb, basedn, cidr, bad_cidr)
304
305         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
306                               expression='(&(objectclass=subnet)(cn=%s))' % bad_cidr)
307
308         self.assertEqual(len(ret), 0, 'Failed to rename subnet %s' % cidr)
309
310         ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
311                               expression='(&(objectclass=subnet)(cn=%s))' % cidr)
312
313         self.assertEqual(len(ret), 1, 'Failed to remove old subnet during rename %s' % cidr)
314
315         subnets.delete_subnet(self.ldb, basedn, cidr)
316
317     def test_create_bad_ranges(self):
318         """These CIDR ranges all have something wrong with them, and they
319         should all fail."""
320         basedn = self.ldb.get_config_basedn()
321
322         cidrs = [
323             # IPv4
324             # insufficient zeros
325             "10.11.12.0/14",
326             "110.0.0.0/6",
327             "1.0.0.0/0",
328             "10.11.13.1/24",
329             "1.2.3.4/29",
330             "10.11.12.0/21",
331             # out of range mask
332             "110.0.0.0/33",
333             "110.0.0.0/-1",
334             "4.0.0.0/111",
335             # out of range address
336             "310.0.0.0/24",
337             "10.0.0.256/32",
338             "1.1.-20.0/24",
339             # badly formed
340             "1.0.0.0/1e",
341             "1.0.0.0/24.0",
342             "1.0.0.0/1/1",
343             "1.0.0.0",
344             "1.c.0.0/24",
345             "1.2.0.0.0/27",
346             "1.23.0/24",
347             "1.23.0.-7/24",
348             "1.-23.0.7/24",
349             "1.23.-0.7/24",
350             "1.23.0.0/0x10",
351             # IPv6 insufficient zeros -- this could be a subtle one
352             # due to the vagaries of endianness in the 16 bit groups.
353             "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1100/119",
354             "aaaa:bbbb::/31",
355             "a:b::/31",
356             "c000::/1",
357             "a::b00/119",
358             "1::1/127",
359             "1::2/126",
360             "1::100/119",
361             "1::8000/112",
362             # out of range mask
363             "a:b::/130",
364             "a:b::/-1",
365             "::/129",
366             # An IPv4 address can't be exactly the bitmask (MS ADTS)
367             "128.0.0.0/1",
368             "192.0.0.0/2",
369             "255.192.0.0/10",
370             "255.255.255.0/24",
371             "255.255.255.255/32",
372             "0.0.0.0/0",
373             # The address can't have leading zeros (not RFC 4632, but MS ADTS)
374             "00.1.2.0/24",
375             "003.1.2.0/24",
376             "022.1.0.0/16",
377             "00000000000000000000000003.1.2.0/24",
378             "09876::abfc/126",
379             "0aaaa:bbbb::/32",
380             "009876::abfc/126",
381             "000a:bbbb::/32",
382
383             # How about extraneous zeros later on
384             "3.01.2.0/24",
385             "3.1.2.00/24",
386             "22.001.0.0/16",
387             "3.01.02.0/24",
388             "100a:0bbb:0023::/48",
389             "100a::0023/128",
390
391             # Windows doesn't like the zero IPv4 address
392             "0.0.0.0/8",
393             # or the zero mask on IPv6
394             "::/0",
395
396             # various violations of RFC5952
397             "0:0:0:0:0:0:0:0/8",
398             "0::0/0",
399             "::0:0/48",
400             "::0:4/128",
401             "0::/8",
402             "0::4f/128",
403             "0::42:0:0:0:0/64",
404             "4f::0/48",
405
406             # badly formed -- mostly the wrong arrangement of colons
407             "a::b::0/120",
408             "a::abcdf:0/120",
409             "a::g:0/120",
410             "::0::3/48",
411             "2001:3::110::3/118",
412             "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1111:0000/128",
413             "a:::5:0/120",
414
415             # non-canonical representations (vs RFC 5952)
416             # "2001:0:c633:63::1:0/120"  is correct
417             "2001:0:c633:63:0:0:1:0/120",
418             "2001::c633:63:0:0:1:0/120",
419             "2001:0:c633:63:0:0:1::/120",
420
421             # "10:0:0:42::/64" is correct
422             "10::42:0:0:0:0/64",
423             "10:0:0:42:0:0:0:0/64",
424
425             # "1::4:5:0:0:8/127" is correct
426             "1:0:0:4:5:0:0:8/127",
427             "1:0:0:4:5::8/127",
428
429             # "2001:db8:0:1:1:1:1:1/128" is correct
430             "2001:db8::1:1:1:1:1/128",
431
432             # IP4 embedded - rejected
433             "a::10.0.0.0/120",
434             "a::10.9.8.7/128",
435
436             # The next ones tinker indirectly with IPv4 embedding,
437             # where Windows has some odd behaviour.
438             #
439             # Samba's libreplace inet_ntop6 expects IPv4 embedding
440             # with addresses in these forms:
441             #
442             #     ::wx:yz
443             #     ::FFFF:wx:yz
444             #
445             # these will be stringified with trailing dottted decimal, thus:
446             #
447             #     ::w.x.y.z
448             #     ::ffff:w.x.y.z
449             #
450             # and this will cause the address to be rejected by Samba,
451             # because it uses a inet_pton / inet_ntop round trip to
452             # ascertain correctness.
453
454             "::ffff:0:0/96",  # this one fails on WIN2012r2
455             "::ffff:aaaa:a000/120",
456             "::ffff:10:0/120",
457             "::ffff:2:300/120",
458             "::3:0/120",
459             "::2:30/124",
460             "::ffff:2:30/124",
461
462             # completely wrong
463             None,
464             "bob",
465             3.1415,
466             False,
467             "10.11.16.0/24\x00hidden bytes past a zero",
468             self,
469         ]
470
471         failures = []
472         for cidr in cidrs:
473             try:
474                 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
475             except subnets.SubnetInvalid:
476                 print("%s fails properly" % (cidr,), file=sys.stderr)
477                 continue
478
479             # we are here because it succeeded when it shouldn't have.
480             print("CIDR %s fails to fail" % (cidr,), file=sys.stderr)
481             failures.append(cidr)
482             subnets.delete_subnet(self.ldb, basedn, cidr)
483
484         if failures:
485             print("These bad subnet names were accepted:")
486             for cidr in failures:
487                 print("    %s" % cidr)
488             self.fail()
489
490     def test_create_good_ranges(self):
491         """All of these CIDRs are good, and the subnet creation should
492         succeed."""
493         basedn = self.ldb.get_config_basedn()
494
495         cidrs = [
496             # IPv4
497             "10.11.12.0/24",
498             "10.11.12.0/23",
499             "10.11.12.0/25",
500             "110.0.0.0/7",
501             "1.0.0.0/32",
502             "10.11.13.0/32",
503             "10.11.13.1/32",
504             "99.0.97.0/24",
505             "1.2.3.4/30",
506             "10.11.12.0/22",
507             "0.12.13.0/24",
508             # IPv6
509             "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1100/120",
510             "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:11f0/124",
511             "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:11fc/126",
512             # don't forget upper case
513             "FFFF:FFFF:FFFF:FFFF:ABCD:EfFF:FFFF:FFeF/128",
514             "9876::ab00/120",
515             "9876::abf0/124",
516             "9876::abfc/126",
517             "aaaa:bbbb::/32",
518             "aaaa:bbba::/31",
519             "aaaa:ba00::/23",
520             "aaaa:bb00::/24",
521             "aaaa:bb00::/77",
522             "::/48",
523             "a:b::/32",
524             "c000::/2",
525             "a::b00/120",
526             "1::2/127",
527             # this pattern of address suffix == mask is forbidden with
528             # IPv4 but OK for IPv6.
529             "8000::/1",
530             "c000::/2",
531             "ffff:ffff:ffc0::/42",
532             "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF/128",
533             # leading zeros are forbidden, but implicit IPv6 zeros
534             # (via "::") are OK.
535             "::1000/116",
536             "::8000/113",
537             # taken to the logical conclusion, "::/0" should be OK, but no.
538             "::/48",
539
540             # Try some reserved ranges, which it might be reasonable
541             # to exclude, but which are not excluded in practice.
542             "129.0.0.0/16",
543             "129.255.0.0/16",
544             "100.64.0.0/10",
545             "127.0.0.0/8",
546             "127.0.0.0/24",
547             "169.254.0.0/16",
548             "169.254.1.0/24",
549             "192.0.0.0/24",
550             "192.0.2.0/24",
551             "198.18.0.0/15",
552             "198.51.100.0/24",
553             "203.0.113.0/24",
554             "224.0.0.0/4",
555             "130.129.0.0/16",
556             "130.255.0.0/16",
557             "192.12.0.0/24",
558             "223.255.255.0/24",
559             "240.255.255.0/24",
560             "224.0.0.0/8",
561             "::/96",
562             "100::/64",
563             "2001:10::/28",
564             "fec0::/10",
565             "ff00::/8",
566             "::1/128",
567             "2001:db8::/32",
568             "2001:10::/28",
569             "2002::/24",
570             "2002:a00::/24",
571             "2002:7f00::/24",
572             "2002:a9fe::/32",
573             "2002:ac10::/28",
574             "2002:c000::/40",
575             "2002:c000:200::/40",
576             "2002:c0a8::/32",
577             "2002:c612::/31",
578             "2002:c633:6400::/40",
579             "2002:cb00:7100::/40",
580             "2002:e000::/20",
581             "2002:f000::/20",
582             "2002:ffff:ffff::/48",
583             "2001::/40",
584             "2001:0:a00::/40",
585             "2001:0:7f00::/40",
586             "2001:0:a9fe::/48",
587             "2001:0:ac10::/44",
588             "2001:0:c000::/56",
589             "2001:0:c000:200::/56",
590             "2001:0:c0a8::/48",
591             "2001:0:c612::/47",
592             "2001:0:c633:6400::/56",
593             "2001:0:cb00:7100::/56",
594             "2001:0:e000::/36",
595             "2001:0:f000::/36",
596             "2001:0:ffff:ffff::/64",
597
598             # non-RFC-5952 versions of these are tested in create_bad_ranges
599             "2001:0:c633:63::1:0/120",
600             "10:0:0:42::/64",
601             "1::4:5:0:0:8/127",
602             "2001:db8:0:1:1:1:1:1/128",
603
604             # The "well-known prefix" 64::ff9b is another IPv4
605             # embedding scheme. Let's try that.
606             "64:ff9b::aaaa:aaaa/127",
607             "64:ff9b::/120",
608             "64:ff9b::ffff:2:3/128",
609         ]
610         failures = []
611
612         for cidr in cidrs:
613             try:
614                 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
615             except subnets.SubnetInvalid as e:
616                 print(e)
617                 failures.append(cidr)
618                 continue
619
620             ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
621                                   expression=('(&(objectclass=subnet)(cn=%s))' %
622                                               cidr))
623
624             if len(ret) != 1:
625                 print("%s was not created" % cidr)
626                 failures.append(cidr)
627                 continue
628             subnets.delete_subnet(self.ldb, basedn, cidr)
629
630         if failures:
631             print("These good subnet names were not accepted:")
632             for cidr in failures:
633                 print("    %s" % cidr)
634             self.fail()
635
636
637
638 TestProgram(module=__name__, opts=subunitopts)