3 # Unit tests for sites manipulation in samba
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
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.
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.
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
23 sys.path.insert(0, "bin/python")
26 from samba.tests.subunitrun import TestProgram, SubunitOptions
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
36 from samba.tests import delete_force
37 from samba.dcerpc import security
38 from ldb import SCOPE_SUBTREE, LdbError, ERR_INSUFFICIENT_ACCESS_RIGHTS
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))
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)
51 opts, args = parser.parse_args()
59 ldaphost = "ldap://%s" % host
63 lp = sambaopts.get_loadparm()
64 creds = credopts.get_credentials(lp)
71 class SitesBaseTests(samba.tests.TestCase):
74 super(SitesBaseTests, self).setUp()
75 self.ldb = SamDB(ldaphost, credentials=creds,
76 session_info=system_session(lp), lp=lp)
77 self.base_dn = self.ldb.domain_dn()
78 self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
79 self.configuration_dn = self.ldb.get_config_basedn().get_linearized()
81 def get_user_dn(self, name):
82 return "CN=%s,CN=Users,%s" % (name, self.base_dn)
86 class SimpleSitesTests(SitesBaseTests):
88 def test_create_and_delete(self):
89 """test creation and deletion of 1 site"""
91 sites.create_site(self.ldb, self.ldb.get_config_basedn(),
94 self.assertRaises(sites.SiteAlreadyExistsException,
95 sites.create_site, self.ldb,
96 self.ldb.get_config_basedn(),
99 sites.delete_site(self.ldb, self.ldb.get_config_basedn(),
102 self.assertRaises(sites.SiteNotFoundException,
103 sites.delete_site, self.ldb,
104 self.ldb.get_config_basedn(),
107 def test_delete_not_empty(self):
108 """test removal of 1 site with servers"""
110 self.assertRaises(sites.SiteServerNotEmptyException,
111 sites.delete_site, self.ldb,
112 self.ldb.get_config_basedn(),
113 "Default-First-Site-Name")
117 class SimpleSubnetTests(SitesBaseTests):
120 super(SimpleSubnetTests, self).setUp()
121 self.basedn = self.ldb.get_config_basedn()
122 self.sitename = "testsite"
123 self.sitename2 = "testsite2"
124 self.ldb.transaction_start()
125 sites.create_site(self.ldb, self.basedn, self.sitename)
126 sites.create_site(self.ldb, self.basedn, self.sitename2)
127 self.ldb.transaction_commit()
130 self.ldb.transaction_start()
131 sites.delete_site(self.ldb, self.basedn, self.sitename)
132 sites.delete_site(self.ldb, self.basedn, self.sitename2)
133 self.ldb.transaction_commit()
134 super(SimpleSubnetTests, self).tearDown()
136 def test_create_delete(self):
137 """Create a subnet and delete it again."""
138 basedn = self.ldb.get_config_basedn()
139 cidr = "10.11.12.0/24"
141 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
143 self.assertRaises(subnets.SubnetAlreadyExists,
144 subnets.create_subnet, self.ldb, basedn, cidr,
147 subnets.delete_subnet(self.ldb, basedn, cidr)
149 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
150 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
152 self.assertEqual(len(ret), 0, 'Failed to delete subnet %s' % cidr)
154 def test_create_shift_delete(self):
155 """Create a subnet, shift it to another site, then delete it."""
156 basedn = self.ldb.get_config_basedn()
157 cidr = "10.11.12.0/24"
159 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
161 subnets.set_subnet_site(self.ldb, basedn, cidr, self.sitename2)
163 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
164 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
166 sites = ret[0]['siteObject']
167 self.assertEqual(len(sites), 1)
168 self.assertEqual(sites[0],
169 'CN=testsite2,CN=Sites,%s' % self.ldb.get_config_basedn())
171 self.assertRaises(subnets.SubnetAlreadyExists,
172 subnets.create_subnet, self.ldb, basedn, cidr,
175 subnets.delete_subnet(self.ldb, basedn, cidr)
177 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
178 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
180 self.assertEqual(len(ret), 0, 'Failed to delete subnet %s' % cidr)
182 def test_delete_subnet_that_does_not_exist(self):
183 """Ensure we can't delete a site that isn't there."""
184 basedn = self.ldb.get_config_basedn()
185 cidr = "10.15.0.0/16"
187 self.assertRaises(subnets.SubnetNotFound,
188 subnets.delete_subnet, self.ldb, basedn, cidr)
190 def get_user_and_ldb(self, username, password, hostname=ldaphost):
191 """Get a connection for a temporarily user that will vanish as soon as
193 user = self.ldb.newuser(username, password)
194 creds_tmp = Credentials()
195 creds_tmp.set_username(username)
196 creds_tmp.set_password(password)
197 creds_tmp.set_domain(creds.get_domain())
198 creds_tmp.set_realm(creds.get_realm())
199 creds_tmp.set_workstation(creds.get_workstation())
200 creds_tmp.set_gensec_features(creds_tmp.get_gensec_features()
201 | gensec.FEATURE_SEAL)
202 creds_tmp.set_kerberos_state(DONT_USE_KERBEROS)
203 ldb_target = SamDB(url=hostname, credentials=creds_tmp, lp=lp)
204 self.addCleanup(delete_force, self.ldb, self.get_user_dn(username))
205 return (user, ldb_target)
207 def test_rename_delete_good_subnet_to_good_subnet_other_user(self):
208 """Make sure that we can't rename or delete subnets when we aren't
210 basedn = self.ldb.get_config_basedn()
211 cidr = "10.16.0.0/24"
212 new_cidr = "10.16.1.0/24"
213 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
214 user, non_admin_ldb = self.get_user_and_ldb("notadmin", "samba123@")
216 subnets.rename_subnet(non_admin_ldb, basedn, cidr, new_cidr)
217 except LdbError as e:
218 self.assertEqual(e.args[0], ERR_INSUFFICIENT_ACCESS_RIGHTS,
219 ("subnet rename by non-admin failed "
220 "in the wrong way: %s" % e))
222 self.fail("subnet rename by non-admin succeeded: %s" % e)
224 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
225 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
227 self.assertEqual(len(ret), 1, ('Subnet %s destroyed or renamed '
228 'by non-admin' % cidr))
230 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
231 expression=('(&(objectclass=subnet)(cn=%s))'
234 self.assertEqual(len(ret), 0,
235 'New subnet %s created by non-admin' % cidr)
238 subnets.delete_subnet(non_admin_ldb, basedn, cidr)
239 except LdbError as e:
240 self.assertEqual(e.args[0], ERR_INSUFFICIENT_ACCESS_RIGHTS,
241 ("subnet delete by non-admin failed "
242 "in the wrong way: %s" % e))
244 self.fail("subnet delete by non-admin succeeded: %s" % e)
246 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
247 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
249 self.assertEqual(len(ret), 1, 'Subnet %s deleted non-admin' % cidr)
251 subnets.delete_subnet(self.ldb, basedn, cidr)
253 def test_create_good_subnet_other_user(self):
254 """Make sure that we can't create subnets when we aren't admin."""
255 basedn = self.ldb.get_config_basedn()
256 cidr = "10.16.0.0/24"
257 user, non_admin_ldb = self.get_user_and_ldb("notadmin", "samba123@")
259 subnets.create_subnet(non_admin_ldb, basedn, cidr, self.sitename)
260 except LdbError as e:
261 self.assertEqual(e.args[0], ERR_INSUFFICIENT_ACCESS_RIGHTS,
262 ("subnet create by non-admin failed "
263 "in the wrong way: %s" % e))
265 subnets.delete_subnet(self.ldb, basedn, cidr)
266 self.fail("subnet create by non-admin succeeded: %s")
268 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
269 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
271 self.assertEqual(len(ret), 0, 'New subnet %s created by non-admin' % cidr)
273 def test_rename_good_subnet_to_good_subnet(self):
274 """Make sure that we can rename subnets"""
275 basedn = self.ldb.get_config_basedn()
276 cidr = "10.16.0.0/24"
277 new_cidr = "10.16.1.0/24"
279 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
281 subnets.rename_subnet(self.ldb, basedn, cidr, new_cidr)
283 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
284 expression='(&(objectclass=subnet)(cn=%s))' % new_cidr)
286 self.assertEqual(len(ret), 1, 'Failed to rename subnet %s' % cidr)
288 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
289 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
291 self.assertEqual(len(ret), 0, 'Failed to remove old subnet during rename %s' % cidr)
293 subnets.delete_subnet(self.ldb, basedn, new_cidr)
295 def test_rename_good_subnet_to_bad_subnet(self):
296 """Make sure that the CIDR checking runs during rename"""
297 basedn = self.ldb.get_config_basedn()
298 cidr = "10.17.0.0/24"
299 bad_cidr = "10.11.12.0/14"
301 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
303 self.assertRaises(subnets.SubnetInvalid, subnets.rename_subnet,
304 self.ldb, basedn, cidr, bad_cidr)
306 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
307 expression='(&(objectclass=subnet)(cn=%s))' % bad_cidr)
309 self.assertEqual(len(ret), 0, 'Failed to rename subnet %s' % cidr)
311 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
312 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
314 self.assertEqual(len(ret), 1, 'Failed to remove old subnet during rename %s' % cidr)
316 subnets.delete_subnet(self.ldb, basedn, cidr)
318 def test_create_bad_ranges(self):
319 """These CIDR ranges all have something wrong with them, and they
321 basedn = self.ldb.get_config_basedn()
336 # out of range address
352 # IPv6 insufficient zeros -- this could be a subtle one
353 # due to the vagaries of endianness in the 16 bit groups.
354 "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1100/119",
367 # An IPv4 address can't be exactly the bitmask (MS ADTS)
372 "255.255.255.255/32",
374 # The address can't have leading zeros (not RFC 4632, but MS ADTS)
378 "00000000000000000000000003.1.2.0/24",
384 # How about extraneous zeros later on
389 "100a:0bbb:0023::/48",
392 # Windows doesn't like the zero IPv4 address
394 # or the zero mask on IPv6
397 # various violations of RFC5952
407 # badly formed -- mostly the wrong arrangement of colons
412 "2001:3::110::3/118",
413 "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1111:0000/128",
416 # non-canonical representations (vs RFC 5952)
417 # "2001:0:c633:63::1:0/120" is correct
418 "2001:0:c633:63:0:0:1:0/120",
419 "2001::c633:63:0:0:1:0/120",
420 "2001:0:c633:63:0:0:1::/120",
422 # "10:0:0:42::/64" is correct
424 "10:0:0:42:0:0:0:0/64",
426 # "1::4:5:0:0:8/127" is correct
427 "1:0:0:4:5:0:0:8/127",
430 # "2001:db8:0:1:1:1:1:1/128" is correct
431 "2001:db8::1:1:1:1:1/128",
433 # IP4 embedded - rejected
437 # The next ones tinker indirectly with IPv4 embedding,
438 # where Windows has some odd behaviour.
440 # Samba's libreplace inet_ntop6 expects IPv4 embedding
441 # with addresses in these forms:
446 # these will be stringified with trailing dottted decimal, thus:
451 # and this will cause the address to be rejected by Samba,
452 # because it uses a inet_pton / inet_ntop round trip to
453 # ascertain correctness.
455 "::ffff:0:0/96", # this one fails on WIN2012r2
456 "::ffff:aaaa:a000/120",
468 "10.11.16.0/24\x00hidden bytes past a zero",
475 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
476 except subnets.SubnetInvalid:
477 print("%s fails properly" % (cidr,), file=sys.stderr)
480 # we are here because it succeeded when it shouldn't have.
481 print("CIDR %s fails to fail" % (cidr,), file=sys.stderr)
482 failures.append(cidr)
483 subnets.delete_subnet(self.ldb, basedn, cidr)
486 print("These bad subnet names were accepted:")
487 for cidr in failures:
491 def test_create_good_ranges(self):
492 """All of these CIDRs are good, and the subnet creation should
494 basedn = self.ldb.get_config_basedn()
510 "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1100/120",
511 "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:11f0/124",
512 "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:11fc/126",
513 # don't forget upper case
514 "FFFF:FFFF:FFFF:FFFF:ABCD:EfFF:FFFF:FFeF/128",
528 # this pattern of address suffix == mask is forbidden with
529 # IPv4 but OK for IPv6.
532 "ffff:ffff:ffc0::/42",
533 "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF/128",
534 # leading zeros are forbidden, but implicit IPv6 zeros
538 # taken to the logical conclusion, "::/0" should be OK, but no.
541 # Try some reserved ranges, which it might be reasonable
542 # to exclude, but which are not excluded in practice.
576 "2002:c000:200::/40",
579 "2002:c633:6400::/40",
580 "2002:cb00:7100::/40",
583 "2002:ffff:ffff::/48",
590 "2001:0:c000:200::/56",
593 "2001:0:c633:6400::/56",
594 "2001:0:cb00:7100::/56",
597 "2001:0:ffff:ffff::/64",
599 # non-RFC-5952 versions of these are tested in create_bad_ranges
600 "2001:0:c633:63::1:0/120",
603 "2001:db8:0:1:1:1:1:1/128",
605 # The "well-known prefix" 64::ff9b is another IPv4
606 # embedding scheme. Let's try that.
607 "64:ff9b::aaaa:aaaa/127",
609 "64:ff9b::ffff:2:3/128",
615 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
616 except subnets.SubnetInvalid as e:
618 failures.append(cidr)
621 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
622 expression=('(&(objectclass=subnet)(cn=%s))' %
626 print("%s was not created" % cidr)
627 failures.append(cidr)
629 subnets.delete_subnet(self.ldb, basedn, cidr)
632 print("These good subnet names were not accepted:")
633 for cidr in failures:
638 TestProgram(module=__name__, opts=subunitopts)