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)
70 class SitesBaseTests(samba.tests.TestCase):
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()
80 def get_user_dn(self, name):
81 return "CN=%s,CN=Users,%s" % (name, self.base_dn)
85 class SimpleSitesTests(SitesBaseTests):
87 def test_create_and_delete(self):
88 """test creation and deletion of 1 site"""
90 sites.create_site(self.ldb, self.ldb.get_config_basedn(),
93 self.assertRaises(sites.SiteAlreadyExistsException,
94 sites.create_site, self.ldb,
95 self.ldb.get_config_basedn(),
98 sites.delete_site(self.ldb, self.ldb.get_config_basedn(),
101 self.assertRaises(sites.SiteNotFoundException,
102 sites.delete_site, self.ldb,
103 self.ldb.get_config_basedn(),
106 def test_delete_not_empty(self):
107 """test removal of 1 site with servers"""
109 self.assertRaises(sites.SiteServerNotEmptyException,
110 sites.delete_site, self.ldb,
111 self.ldb.get_config_basedn(),
112 "Default-First-Site-Name")
116 class SimpleSubnetTests(SitesBaseTests):
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()
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()
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"
140 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
142 self.assertRaises(subnets.SubnetAlreadyExists,
143 subnets.create_subnet, self.ldb, basedn, cidr,
146 subnets.delete_subnet(self.ldb, basedn, cidr)
148 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
149 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
151 self.assertEqual(len(ret), 0, 'Failed to delete subnet %s' % cidr)
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"
158 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
160 subnets.set_subnet_site(self.ldb, basedn, cidr, self.sitename2)
162 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
163 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
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())
170 self.assertRaises(subnets.SubnetAlreadyExists,
171 subnets.create_subnet, self.ldb, basedn, cidr,
174 subnets.delete_subnet(self.ldb, basedn, cidr)
176 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
177 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
179 self.assertEqual(len(ret), 0, 'Failed to delete subnet %s' % cidr)
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"
186 self.assertRaises(subnets.SubnetNotFound,
187 subnets.delete_subnet, self.ldb, basedn, cidr)
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
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)
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
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@")
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))
221 self.fail("subnet rename by non-admin succeeded: %s" % e)
223 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
224 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
226 self.assertEqual(len(ret), 1, ('Subnet %s destroyed or renamed '
227 'by non-admin' % cidr))
229 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
230 expression=('(&(objectclass=subnet)(cn=%s))'
233 self.assertEqual(len(ret), 0,
234 'New subnet %s created by non-admin' % cidr)
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))
243 self.fail("subnet delete by non-admin succeeded: %s" % e)
245 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
246 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
248 self.assertEqual(len(ret), 1, 'Subnet %s deleted non-admin' % cidr)
250 subnets.delete_subnet(self.ldb, basedn, cidr)
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@")
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))
264 subnets.delete_subnet(self.ldb, basedn, cidr)
265 self.fail("subnet create by non-admin succeeded: %s")
267 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
268 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
270 self.assertEqual(len(ret), 0, 'New subnet %s created by non-admin' % cidr)
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"
278 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
280 subnets.rename_subnet(self.ldb, basedn, cidr, new_cidr)
282 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
283 expression='(&(objectclass=subnet)(cn=%s))' % new_cidr)
285 self.assertEqual(len(ret), 1, 'Failed to rename subnet %s' % cidr)
287 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
288 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
290 self.assertEqual(len(ret), 0, 'Failed to remove old subnet during rename %s' % cidr)
292 subnets.delete_subnet(self.ldb, basedn, new_cidr)
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"
300 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
302 self.assertRaises(subnets.SubnetInvalid, subnets.rename_subnet,
303 self.ldb, basedn, cidr, bad_cidr)
305 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
306 expression='(&(objectclass=subnet)(cn=%s))' % bad_cidr)
308 self.assertEqual(len(ret), 0, 'Failed to rename subnet %s' % cidr)
310 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
311 expression='(&(objectclass=subnet)(cn=%s))' % cidr)
313 self.assertEqual(len(ret), 1, 'Failed to remove old subnet during rename %s' % cidr)
315 subnets.delete_subnet(self.ldb, basedn, cidr)
317 def test_create_bad_ranges(self):
318 """These CIDR ranges all have something wrong with them, and they
320 basedn = self.ldb.get_config_basedn()
335 # out of range address
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",
366 # An IPv4 address can't be exactly the bitmask (MS ADTS)
371 "255.255.255.255/32",
373 # The address can't have leading zeros (not RFC 4632, but MS ADTS)
377 "00000000000000000000000003.1.2.0/24",
383 # How about extraneous zeros later on
388 "100a:0bbb:0023::/48",
391 # Windows doesn't like the zero IPv4 address
393 # or the zero mask on IPv6
396 # various violations of RFC5952
406 # badly formed -- mostly the wrong arrangement of colons
411 "2001:3::110::3/118",
412 "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1111:0000/128",
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",
421 # "10:0:0:42::/64" is correct
423 "10:0:0:42:0:0:0:0/64",
425 # "1::4:5:0:0:8/127" is correct
426 "1:0:0:4:5:0:0:8/127",
429 # "2001:db8:0:1:1:1:1:1/128" is correct
430 "2001:db8::1:1:1:1:1/128",
432 # IP4 embedded - rejected
436 # The next ones tinker indirectly with IPv4 embedding,
437 # where Windows has some odd behaviour.
439 # Samba's libreplace inet_ntop6 expects IPv4 embedding
440 # with addresses in these forms:
445 # these will be stringified with trailing dottted decimal, thus:
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.
454 "::ffff:0:0/96", # this one fails on WIN2012r2
455 "::ffff:aaaa:a000/120",
467 "10.11.16.0/24\x00hidden bytes past a zero",
474 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
475 except subnets.SubnetInvalid:
476 print("%s fails properly" % (cidr,), file=sys.stderr)
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)
485 print("These bad subnet names were accepted:")
486 for cidr in failures:
490 def test_create_good_ranges(self):
491 """All of these CIDRs are good, and the subnet creation should
493 basedn = self.ldb.get_config_basedn()
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",
527 # this pattern of address suffix == mask is forbidden with
528 # IPv4 but OK for IPv6.
531 "ffff:ffff:ffc0::/42",
532 "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF/128",
533 # leading zeros are forbidden, but implicit IPv6 zeros
537 # taken to the logical conclusion, "::/0" should be OK, but no.
540 # Try some reserved ranges, which it might be reasonable
541 # to exclude, but which are not excluded in practice.
575 "2002:c000:200::/40",
578 "2002:c633:6400::/40",
579 "2002:cb00:7100::/40",
582 "2002:ffff:ffff::/48",
589 "2001:0:c000:200::/56",
592 "2001:0:c633:6400::/56",
593 "2001:0:cb00:7100::/56",
596 "2001:0:ffff:ffff::/64",
598 # non-RFC-5952 versions of these are tested in create_bad_ranges
599 "2001:0:c633:63::1:0/120",
602 "2001:db8:0:1:1:1:1:1/128",
604 # The "well-known prefix" 64::ff9b is another IPv4
605 # embedding scheme. Let's try that.
606 "64:ff9b::aaaa:aaaa/127",
608 "64:ff9b::ffff:2:3/128",
614 subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
615 except subnets.SubnetInvalid as e:
617 failures.append(cidr)
620 ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
621 expression=('(&(objectclass=subnet)(cn=%s))' %
625 print("%s was not created" % cidr)
626 failures.append(cidr)
628 subnets.delete_subnet(self.ldb, basedn, cidr)
631 print("These good subnet names were not accepted:")
632 for cidr in failures:
638 TestProgram(module=__name__, opts=subunitopts)