1 # Unix SMB/CIFS implementation.
3 # Copyright 2021 (C) Catalyst IT Ltd
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 from samba
.samdb
import SamDB
24 from samba
.auth
import system_session
26 from samba
.sd_utils
import SDUtils
27 from samba
.credentials
import DONT_USE_KERBEROS
, Credentials
28 from samba
.gensec
import FEATURE_SEAL
29 from samba
.tests
.subunitrun
import SubunitOptions
, TestProgram
30 from samba
.tests
import TestCase
, ldb_err
31 from samba
.tests
import DynamicTestCase
32 import samba
.getopt
as options
34 from samba
.colour
import c_RED
, c_GREEN
, c_DARK_YELLOW
35 from samba
.dsdb
import (
36 UF_SERVER_TRUST_ACCOUNT
,
37 UF_TRUSTED_FOR_DELEGATION
,
41 SPN_GUID
= 'f3a64788-5306-11d1-a9c5-0000f80367c1'
43 RELEVANT_ATTRS
= {'dNSHostName',
44 'servicePrincipalName',
52 operr
= ldb
.ERR_OPERATIONS_ERROR
53 denied
= ldb
.ERR_INSUFFICIENT_ACCESS_RIGHTS
54 constraint
= ldb
.ERR_CONSTRAINT_VIOLATION
55 exists
= ldb
.ERR_ENTRY_ALREADY_EXISTS
57 add
= ldb
.FLAG_MOD_ADD
58 replace
= ldb
.FLAG_MOD_REPLACE
59 delete
= ldb
.FLAG_MOD_DELETE
71 # This needs to happen before the class definition, and we put it
72 # in a function to keep the namespace clean.
73 global LP
, CREDS
, SERVER
, REALM
, COLOUR_TEXT
, subunitopts
, FILTER
75 parser
= optparse
.OptionParser(
76 "python3 ldap_spn.py <server> [options]")
77 sambaopts
= options
.SambaOptions(parser
)
78 parser
.add_option_group(sambaopts
)
80 # use command line creds if available
81 credopts
= options
.CredentialsOptions(parser
)
82 parser
.add_option_group(credopts
)
83 subunitopts
= SubunitOptions(parser
)
84 parser
.add_option_group(subunitopts
)
86 parser
.add_option('--colour', action
="store_true",
87 help="use colour text",
88 default
=sys
.stdout
.isatty())
90 parser
.add_option('--filter', help="only run tests matching this regex")
92 opts
, args
= parser
.parse_args()
97 LP
= sambaopts
.get_loadparm()
98 CREDS
= credopts
.get_credentials(LP
)
100 REALM
= CREDS
.get_realm()
101 COLOUR_TEXT
= opts
.colour
108 def colour_text(x
, state
=None):
116 return c_DARK_YELLOW(x
)
119 def get_samdb(creds
=None):
122 session
= system_session()
126 return SamDB(url
=f
"ldap://{SERVER}",
128 session_info
=session
,
132 def add_unpriv_user(samdb
, ou
, username
,
133 writeable_objects
=None,
134 password
="samba123@"):
135 creds
= Credentials()
136 creds
.set_username(username
)
137 creds
.set_password(password
)
138 creds
.set_domain(CREDS
.get_domain())
139 creds
.set_realm(CREDS
.get_realm())
140 creds
.set_workstation(CREDS
.get_workstation())
141 creds
.set_gensec_features(CREDS
.get_gensec_features() | FEATURE_SEAL
)
142 creds
.set_kerberos_state(DONT_USE_KERBEROS
)
143 dnstr
= f
"CN={username},{ou}"
145 # like, WTF, samdb.newuser(), this is what you make us do.
146 short_ou
= ou
.split(',', 1)[0]
148 samdb
.newuser(username
, password
, userou
=short_ou
)
150 if writeable_objects
:
151 sd_utils
= SDUtils(samdb
)
152 sid
= sd_utils
.get_object_sid(dnstr
)
153 for obj
in writeable_objects
:
154 mod
= f
"(OA;CI;WP;{ SPN_GUID };;{ sid })"
155 sd_utils
.dacl_add_ace(obj
, mod
)
157 unpriv_samdb
= get_samdb(creds
=creds
)
161 class LdapSpnTestBase(TestCase
):
165 def setUpDynamicTestCases(cls
):
166 if getattr(cls
, '_disabled', False):
168 for doc
, *rows
in cls
.cases
:
170 if not re
.search(FILTER
, doc
):
172 name
= re
.sub(r
'\W+', '_', doc
)
173 cls
.generate_dynamic_test("test_spn", name
, rows
, doc
)
175 def setup_objects(self
, rows
):
176 objects
= set(r
[0] for r
in rows
)
179 objtype
, name
= name
.split(':', 1)
182 getattr(self
, f
'add_{objtype}')(name
)
184 def setup_users(self
, rows
):
185 # When you are adding an SPN that aliases (or would be aliased
186 # by) another SPN on another object, you need to have write
187 # permission on that other object too.
189 # To test this negatively and positively, we need to have
190 # users with various combinations of write permission, which
191 # means fiddling with SDs on the objects.
194 # '' : user with no special permissions
196 # 'A' : user can write to A only
197 # 'A,C' : user can write to A and C
198 # 'C,A' : same, but makes another user
203 permissions
= set(r
[2] for r
in rows
)
204 for p
in permissions
:
209 writeable_objects
= None
211 user
= 'writes_' + p
.replace(",", '_')
212 writeable_objects
= [self
.objects
[x
][0] for x
in p
.split(',')]
214 self
.userdbs
[p
] = add_unpriv_user(self
.samdb
, self
.ou
, user
,
217 def _test_spn_with_args(self
, rows
, doc
):
218 cdoc
= colour_text(doc
)
219 edoc
= colour_text(doc
, 'error')
220 pdoc
= colour_text(doc
, 'pass')
224 print('\n', c_DARK_YELLOW('#' * 10), f
'starting «{cdoc}»\n')
227 self
.samdb
= get_samdb()
228 self
.base_dn
= self
.samdb
.get_default_basedn()
229 self
.short_id
= self
.id().rsplit('.', 1)[1][:63]
231 self
.ou
= f
"OU={ self.short_id },{ self.base_dn }"
232 self
.addCleanup(self
.samdb
.delete
, self
.ou
, ["tree_delete:1"])
233 self
.samdb
.create_ou(self
.ou
)
235 self
.setup_objects(rows
)
236 self
.setup_users(rows
)
238 for i
, row
in enumerate(rows
):
240 obj
, data
, rights
, expected
, op
= row
242 obj
, data
, rights
, expected
= row
243 op
= ldb
.FLAG_MOD_REPLACE
245 # We use this DB with possibly restricted rights for this row
246 samdb
= self
.userdbs
[rights
]
249 objtype
, obj
= obj
.split(':', 1)
253 dn
, dnsname
= self
.objects
[obj
]
256 if isinstance(data
, dict):
259 m
['servicePrincipalName'] = data
261 # for python's sake (and our sanity) we try to ensure we
262 # have consistent canonical case in our attributes
264 if not keys
.issubset(RELEVANT_ATTRS
):
265 raise ValueError(f
"unexpected attr {keys - RELEVANT_ATTRS}. "
268 for k
in ('dNSHostName', 'servicePrincipalName'):
269 if isinstance(m
.get(k
), str):
270 m
[k
] = m
[k
].format(dnsname
=f
"x.{REALM}")
271 elif isinstance(m
.get(k
), list):
272 m
[k
] = [x
.format(dnsname
=f
"x.{REALM}") for x
in m
[k
]]
274 msg
= ldb
.Message
.from_dict(samdb
, m
, op
)
279 except ldb
.LdbError
as e
:
280 print(f
"row {i+1} of '{pdoc}' failed as expected with "
283 self
.fail(f
"row {i+1}: "
284 f
"{rights} {pprint.pformat(m)} on {objtype} {obj} "
285 f
"should fail ({edoc})")
290 except ldb
.LdbError
as e
:
291 self
.fail(f
"row {i+1} of {edoc} failed with {ldb_err(e)}:\n"
292 f
"{rights} {pprint.pformat(m)} on {objtype} {obj}")
294 elif expected
is report
:
296 self
.samdb
.modify(msg
)
298 f
"of '{cdoc}' {colour_text('SUCCEEDED', 'pass')}:\n"
299 f
"{pprint.pformat(m)} on {obj}")
300 except ldb
.LdbError
as e
:
302 f
"of '{cdoc}' {colour_text('FAILED', 'error')} "
303 f
"with {ldb_err(e)}:\n{pprint.pformat(m)} on {obj}")
305 elif expected
is breakpoint
:
309 except ldb
.LdbError
as e
:
310 print(f
"row {i+1} of '{pdoc}' FAILED with {ldb_err(e)}\n")
312 else: # an ldb error number
315 except ldb
.LdbError
as e
:
316 if e
.args
[0] == expected
:
318 self
.fail(f
"row {i+1} of '{edoc}' "
319 f
"should have failed with {ldb_err(expected)}:\n"
320 f
"not {ldb_err(e)}:\n"
321 f
"{rights} {pprint.pformat(m)} on {objtype} {obj}")
322 self
.fail(f
"row {i+1} of '{edoc}' "
323 f
"should have failed with {ldb_err(expected)}:\n"
324 f
"{rights} {pprint.pformat(m)} on {objtype} {obj}")
326 def add_dc(self
, name
):
327 dn
= f
"CN={name},OU=Domain Controllers,{self.base_dn}"
328 dnsname
= f
"{name}.{REALM}".lower()
331 "objectclass": "computer",
332 "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT |
333 UF_TRUSTED_FOR_DELEGATION
),
334 "dnsHostName": dnsname
,
335 "carLicense": self
.id()
337 self
.addCleanup(self
.remove_object
, name
)
338 self
.objects
[name
] = (dn
, dnsname
)
340 def add_user(self
, name
):
341 dn
= f
"CN={name},{self.ou}"
345 "samAccountName": name
,
346 "objectclass": "user",
347 "carLicense": self
.id()
349 self
.addCleanup(self
.remove_object
, name
)
350 self
.objects
[name
] = (dn
, None)
352 def remove_object(self
, name
):
353 dn
, dnsname
= self
.objects
.pop(name
)
354 self
.samdb
.delete(dn
)
358 class LdapSpnTest(LdapSpnTestBase
):
359 """Make sure we can't add clashing servicePrincipalNames.
361 This would be possible using sPNMappings aliases — for example, if
362 the mapping maps host/ to cifs/, we should not be able to add
363 different addresses for each.
366 # default sPNMappings: host=alerter, appmgmt, cisvc, clipsrv,
367 # browser, dhcp, dnscache, replicator, eventlog, eventsystem,
368 # policyagent, oakley, dmserver, dns, mcsvc, fax, msiserver, ias,
369 # messenger, netlogon, netman, netdde, netddedsm, nmagent,
370 # plugplay, protectedstorage, rasman, rpclocator, rpc, rpcss,
371 # remoteaccess, rsvp, samss, scardsvr, scesrv, seclogon, scm,
372 # dcom, cifs, spooler, snmp, schedule, tapisrv, trksvr, trkwks,
373 # ups, time, wins, www, http, w3svc, iisadmin, msdtc
375 # I think in practice this is rarely if ever changed or added to.
379 ('A', 'host/{dnsname}', '*', ok
),
381 ("add one as rightful user",
382 ('A', 'host/{dnsname}', 'A', ok
),
384 ("attempt to add one as nobody",
385 ('A', 'host/{dnsname}', '', denied
),
388 ("add and replace as admin",
389 ('A', 'host/{dnsname}', '*', ok
),
390 ('A', 'host/x.{dnsname}', '*', ok
),
392 ("replace as rightful user",
393 ('A', 'host/{dnsname}', 'A', ok
),
394 ('A', 'host/x.{dnsname}', 'A', ok
),
396 ("attempt to replace one as nobody",
397 ('A', 'host/{dnsname}', '*', ok
),
398 ('A', 'host/x.{dnsname}', '', denied
),
401 ("add second as admin",
402 ('A', 'host/{dnsname}', '*', ok
),
403 ('A', 'host/x.{dnsname}', '*', ok
, add
),
405 ("add second as rightful user",
406 ('A', 'host/{dnsname}', 'A', ok
),
407 ('A', 'host/x.{dnsname}', 'A', ok
, add
),
409 ("attempt to add second as nobody",
410 ('A', 'host/{dnsname}', '*', ok
),
411 ('A', 'host/x.{dnsname}', '', denied
, add
),
414 ("add the same one twice, simple duplicate error",
415 ('A', 'host/{dnsname}', '*', ok
),
416 ('A', 'host/{dnsname}', '*', bad
, add
),
418 ("simple duplicate attributes, as non-admin",
419 ('A', 'host/{dnsname}', '*', ok
),
420 ('A', 'host/{dnsname}', 'A', bad
, add
),
423 ("add the same one twice, identical duplicate",
424 ('A', 'host/{dnsname}', '*', ok
),
425 ('A', 'host/{dnsname}', '*', bad
, add
),
428 ("add a conflict, host first, as nobody",
429 ('A', 'host/z.{dnsname}', '*', ok
),
430 ('B', 'cifs/z.{dnsname}', '', denied
),
433 ("add a conflict, service first, as nobody",
434 ('A', 'cifs/{dnsname}', '*', ok
),
435 ('B', 'host/{dnsname}', '', denied
),
439 ("three way conflict, host first, as admin",
440 ('A', 'host/z.{dnsname}', '*', ok
),
441 ('B', 'cifs/z.{dnsname}', '*', ok
),
442 ('C', 'www/z.{dnsname}', '*', ok
),
444 ("three way conflict, host first, with sufficient rights",
445 ('A', 'host/z.{dnsname}', 'A', ok
),
446 ('B', 'cifs/z.{dnsname}', 'B,A', ok
),
447 ('C', 'www/z.{dnsname}', 'C,A', ok
),
449 ("three way conflict, host first, adding duplicate",
450 ('A', 'host/z.{dnsname}', 'A', ok
),
451 ('B', 'cifs/z.{dnsname}', 'B,A', ok
),
452 ('C', 'cifs/z.{dnsname}', 'C,A', bad
),
454 ("three way conflict, host first, adding duplicate, full rights",
455 ('A', 'host/z.{dnsname}', 'A', ok
),
456 ('B', 'cifs/z.{dnsname}', 'B,A', ok
),
457 ('C', 'cifs/z.{dnsname}', 'C,B,A', bad
),
460 ("three way conflict, host first, with other write rights",
461 ('A', 'host/z.{dnsname}', '*', ok
),
462 ('B', 'cifs/z.{dnsname}', 'A,B', ok
),
463 ('C', 'cifs/z.{dnsname}', 'A,B', bad
),
466 ("three way conflict, host first, as nobody",
467 ('A', 'host/z.{dnsname}', '*', ok
),
468 ('B', 'cifs/z.{dnsname}', '*', ok
),
469 ('C', 'www/z.{dnsname}', '', denied
),
472 ("three way conflict, services first, as admin",
473 ('A', 'cifs/{dnsname}', '*', ok
),
474 ('B', 'www/{dnsname}', '*', ok
),
475 ('C', 'host/{dnsname}', '*', constraint
),
477 ("three way conflict, services first, with service write rights",
478 ('A', 'cifs/{dnsname}', '*', ok
),
479 ('B', 'www/{dnsname}', '*', ok
),
480 ('C', 'host/{dnsname}', 'A,B', bad
),
483 ("three way conflict, service first, as nobody",
484 ('A', 'cifs/{dnsname}', '*', ok
),
485 ('B', 'www/{dnsname}', '*', ok
),
486 ('C', 'host/{dnsname}', '', denied
),
488 ("replace host before specific",
489 ('A', 'host/{dnsname}', '*', ok
),
490 ('A', 'cifs/{dnsname}', '*', ok
),
492 ("replace host after specific, as nobody",
493 ('A', 'cifs/{dnsname}', '*', ok
),
494 ('A', 'host/{dnsname}', '', denied
),
497 ("non-conflict host before specific",
498 ('A', 'host/{dnsname}', '*', ok
),
499 ('A', 'cifs/{dnsname}', '*', ok
, add
),
501 ("non-conflict host after specific",
502 ('A', 'cifs/{dnsname}', '*', ok
),
503 ('A', 'host/{dnsname}', '*', ok
, add
),
505 ("non-conflict host before specific, non-admin",
506 ('A', 'host/{dnsname}', 'A', ok
),
507 ('A', 'cifs/{dnsname}', 'A', ok
, add
),
509 ("non-conflict host after specific, as nobody",
510 ('A', 'cifs/{dnsname}', '*', ok
),
511 ('A', 'host/{dnsname}', '', denied
, add
),
514 ("add a conflict, host first on user, as admin",
515 ('user:C', 'host/{dnsname}', '*', ok
),
516 ('B', 'cifs/{dnsname}', '*', ok
),
518 ("add a conflict, host first on user, host rights",
519 ('user:C', 'host/{dnsname}', '*', ok
),
520 ('B', 'cifs/{dnsname}', 'C', denied
),
522 ("add a conflict, host first on user, both rights",
523 ('user:C', 'host/{dnsname}', '*', ok
),
524 ('B', 'cifs/{dnsname}', 'B,C', ok
),
526 ("add a conflict, host first both on user",
527 ('user:C', 'host/{dnsname}', '*', ok
),
528 ('user:D', 'www/{dnsname}', '*', ok
),
530 ("add a conflict, host first both on user, host rights",
531 ('user:C', 'host/{dnsname}', '*', ok
),
532 ('user:D', 'www/{dnsname}', 'C', denied
),
534 ("add a conflict, host first both on user, both rights",
535 ('user:C', 'host/{dnsname}', '*', ok
),
536 ('user:D', 'www/{dnsname}', 'C,D', ok
),
538 ("add a conflict, host first both on user, as nobody",
539 ('user:C', 'host/{dnsname}', '*', ok
),
540 ('user:D', 'www/{dnsname}', '', denied
),
542 ("add a conflict, host first, with both write rights",
543 ('A', 'host/z.{dnsname}', '*', ok
),
544 ('B', 'cifs/z.{dnsname}', 'A,B', ok
),
547 ("add a conflict, host first, second on user, as admin",
548 ('A', 'host/{dnsname}', '*', ok
),
549 ('user:D', 'cifs/{dnsname}', '*', ok
),
551 ("add a conflict, host first, second on user, with rights",
552 ('A', 'host/{dnsname}', '*', ok
),
553 ('user:D', 'cifs/{dnsname}', 'A,D', ok
),
556 ("nonsense SPNs, part 1, as admin",
557 ('A', 'a-b-c/{dnsname}', '*', ok
),
558 ('A', 'rrrrrrrrrrrrr /{dnsname}', '*', ok
),
560 ("nonsense SPNs, part 1, as user",
561 ('A', 'a-b-c/{dnsname}', 'A', ok
),
562 ('A', 'rrrrrrrrrrrrr /{dnsname}', 'A', ok
),
564 ("nonsense SPNs, part 1, as nobody",
565 ('A', 'a-b-c/{dnsname}', '', denied
),
566 ('A', 'rrrrrrrrrrrrr /{dnsname}', '', denied
),
569 ("add a conflict, using port",
570 ('A', 'dns/{dnsname}', '*', ok
),
571 ('B', 'dns/{dnsname}:53', '*', ok
),
573 ("add a conflict, using port, port first",
574 ('user:C', 'dns/{dnsname}:53', '*', ok
),
575 ('user:D', 'dns/{dnsname}', '*', ok
),
578 ('A', {'dNSHostName': '{dnsname}'}, '*', ok
),
579 ('A', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok
),
580 ('B', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', constraint
),
581 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok
),
582 ('B', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok
),
583 ('B', 'cifs/y.{dnsname}/DomainDNSZones.{dnsname}', '*', constraint
),
585 ("three part nonsense spns",
586 ('A', {'dNSHostName': 'bean'}, '*', ok
),
587 ('A', 'cifs/bean/DomainDNSZones.bean', '*', ok
),
588 ('B', 'cifs/bean/DomainDNSZones.bean', '*', constraint
),
589 ('A', {'dNSHostName': 'y.bean'}, '*', ok
),
590 ('B', 'cifs/bean/DomainDNSZones.bean', '*', ok
),
591 ('B', 'cifs/y.bean/DomainDNSZones.bean', '*', constraint
),
592 ('C', 'host/bean/bean', '*', ok
),
595 ("one part spns (no slashes)",
596 ('A', '{dnsname}', '*', constraint
),
597 ('B', 'cifs', '*', constraint
),
598 ('B', 'cifs/', '*', ok
),
599 ('B', ' ', '*', constraint
),
600 ('user:C', 'host', '*', constraint
),
604 # These tests pass on Windows. An SPN must have one or two
605 # slashes, with at least one character before the first one,
606 # UNLESS the first slash is followed by a good enough service
607 # name (e.g. "/host/x.y" rather than "sdfsd/x.y").
608 ('A', '\\/{dnsname}', '*', ok
),
609 ('B', 'cifs/\\\\{dnsname}', '*', ok
),
610 ('B', r
'cifs/\\\{dnsname}', '*', ok
),
611 ('B', r
'cifs/\\\{dnsname}/', '*', ok
),
612 ('A', r
'cīfs/\\\{dnsname}/', '*', constraint
), # 'ī' maps to 'i'
613 # on the next two, full-width solidus (U+FF0F) does not work
615 ('A', 'cifs/sfic', '*', constraint
, add
),
616 ('A', r
'cifs/\\\{dnsname}', '*', constraint
, add
),
617 ('B', '\n', '*', constraint
),
618 ('B', '\n/\n', '*', ok
),
619 ('B', '\n/\n/\n', '*', ok
),
620 ('B', '\n/\n/\n/\n', '*', constraint
),
621 ('B', ' /* and so on */ ', '*', ok
, add
),
622 ('B', r
'¯\_(ツ)_/¯', '*', ok
, add
), # ¯\_(ツ)_/¯
623 # つ is hiragana for katakana ツ, so the next one fails for
624 # something analogous to casefold reasons.
625 ('A', r
'¯\_(つ)_/¯', '*', constraint
),
626 ('A', r
'¯\_(㋡)_/¯', '*', constraint
), # circled ツ
627 ('B', '//', '*', constraint
), # all can't be empty,
628 ('B', ' //', '*', ok
), # service can be space
629 ('B', '/host/{dnsname}', '*', ok
), # or empty if others aren't
630 ('B', '/host/x.y.z', '*', ok
),
631 ('B', '/ /x.y.z', '*', ok
),
632 ('B', ' / / ', '*', ok
),
633 ('user:C', b
'host/', '*', ok
),
634 ('user:C', ' /host', '*', ok
), # service is ' ' (space)
635 ('B', ' /host', '*', constraint
), # already on C
636 ('B', ' /HōST', '*', constraint
), # ō equiv to O
637 ('B', ' /ħØşt', '*', constraint
), # maps to ' /host'
638 ('B', ' /H0ST', '*', ok
), # 0 is zero
639 ('B', ' /НoST', '*', ok
), # Cyrillic Н (~N)
640 ('B', ' /host', '*', ok
), # two space
641 ('B', '\u00a0/host', '*', ok
), # non-breaking space
642 ('B', ' 2/HōST/⌷[ ][]¨(', '*', ok
),
643 ('B', ' (//)', '*', ok
, add
),
644 ('B', ' ///', '*', constraint
),
645 ('B', r
' /\//', '*', constraint
), # escape doesn't help
646 ('B', ' /\\//', '*', constraint
), # double escape doesn't help
647 ('B', r
'\//', '*', ok
),
648 ('A', r
'\\/\\/', '*', ok
),
649 ('B', '|//|', '*', ok
, add
),
650 ('B', r
'\/\/\\', '*', ok
, add
),
652 ('A', ':', '*', constraint
),
653 ('A', ':/:', '*', ok
),
654 ('A', ':/:80', '*', ok
), # port number syntax is not special
655 ('A', ':/:( ツ', '*', ok
),
656 ('A', ':/:/:', '*', ok
),
657 ('B', b
'cifs/\x11\xaa\xbb\xcc\\example.com', '*', ok
),
658 ('A', b
':/\xcc\xcc\xcc\xcc', '*', ok
),
659 ('A', b
':/b\x00/b/b/b', '*', ok
), # string handlng truncates at \x00
660 ('A', b
'a@b/a@b/a@b', '*', ok
),
661 ('A', b
'a/a@b/a@b', '*', ok
),
663 ("empty part spns (consecutive slashes)",
664 ('A', 'cifs//{dnsname}', '*', ok
),
665 ('B', 'cifs//{dnsname}', '*', bad
), # should clash with line 1
666 ('B', 'cifs/zzzy.{dnsname}/', '*', ok
),
667 ('B', '/host/zzzy.{dnsname}', '*', ok
),
669 ("too many spn parts",
670 ('A', 'cifs/{dnsname}/{dnsname}/{dnsname}', '*', bad
),
671 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok
),
672 ('B', 'cifs/{dnsname}/{dnsname}/', '*', bad
),
673 ('B', 'cifs/y.{dnsname}/{dnsname}/toop', '*', bad
),
674 ('B', 'host/{dnsname}/a/b/c', '*', bad
),
676 ("add a conflict, host first, as admin",
677 ('A', 'host/z.{dnsname}', '*', ok
),
678 ('B', 'cifs/z.{dnsname}', '*', ok
),
680 ("add a conflict, host first, with host write rights",
681 ('A', 'host/z.{dnsname}', '*', ok
),
682 ('B', 'cifs/z.{dnsname}', 'A', denied
),
684 ("add a conflict, service first, with service write rights",
685 ('A', 'cifs/{dnsname}', '*', ok
),
686 ('B', 'host/{dnsname}', 'A', denied
),
688 ("adding dNSHostName after cifs with no old dNSHostName",
689 ('A', 'cifs/{dnsname}', '*', ok
),
690 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok
),
691 ('B', 'cifs/{dnsname}', '*', constraint
),
692 ('B', 'cifs/y.{dnsname}', '*', ok
),
693 ('B', 'host/y.{dnsname}', '*', ok
),
695 ("changing dNSHostName after cifs",
696 ('A', {'dNSHostName': '{dnsname}'}, '*', ok
),
697 ('A', 'cifs/{dnsname}', '*', ok
),
698 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok
),
699 ('B', 'cifs/{dnsname}', '*', ok
),
700 ('B', 'cifs/y.{dnsname}', '*', bad
),
701 ('B', 'host/y.{dnsname}', '*', bad
),
707 class LdapSpnSambaOnlyTest(LdapSpnTestBase
):
708 # We don't run these ones outside of selftest, where we are
709 # probably testing against Windows and these are known failures.
710 _disabled
= 'SAMBA_SELFTEST' not in os
.environ
712 ("add a conflict, host first, with service write rights",
713 ('A', 'host/z.{dnsname}', '*', ok
),
714 ('B', 'cifs/z.{dnsname}', 'B', denied
),
716 ("add a conflict, service first, with host write rights",
717 ('A', 'cifs/{dnsname}', '*', ok
),
718 ('B', 'host/{dnsname}', 'B', constraint
),
720 ("add a conflict, service first, as admin",
721 ('A', 'cifs/{dnsname}', '*', ok
),
722 ('B', 'host/{dnsname}', '*', constraint
),
724 ("add a conflict, service first, with both write rights",
725 ('A', 'cifs/{dnsname}', '*', ok
),
726 ('B', 'host/{dnsname}', 'A,B', constraint
),
728 ("add a conflict, host first both on user, service rights",
729 ('user:C', 'host/{dnsname}', '*', ok
),
730 ('user:D', 'www/{dnsname}', 'D', denied
),
732 ("add a conflict, along with a re-added SPN",
733 ('A', 'cifs/{dnsname}', '*', ok
),
734 ('B', 'cifs/heeble.example.net', 'B', ok
),
735 ('B', ['cifs/heeble.example.net', 'host/{dnsname}'], 'B', constraint
),
738 ("changing dNSHostName after host",
739 ('A', {'dNSHostName': '{dnsname}'}, '*', ok
),
740 ('A', 'host/{dnsname}', '*', ok
),
741 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok
),
742 ('B', 'cifs/{dnsname}', 'B', ok
), # no clash with A
743 ('B', 'cifs/y.{dnsname}', 'B', bad
), # should clash with A
744 ('B', 'host/y.{dnsname}', '*', bad
),
747 ("mystery dnsname clash, host first",
748 ('user:C', 'host/heeble.example.net', '*', ok
),
749 ('user:D', 'www/heeble.example.net', '*', ok
),
751 ("mystery dnsname clash, www first",
752 ('user:D', 'www/heeble.example.net', '*', ok
),
753 ('user:C', 'host/heeble.example.net', '*', constraint
),
756 ('A', 'cifs/{dnsname}', '*', ok
),
757 ('A', 'host/{dnsname}', '*', ok
),
758 ('A', 'cifs/{dnsname}', '*', ok
),
760 ("replace as non-admin with rights",
761 ('A', 'cifs/{dnsname}', '*', ok
),
762 ('A', 'host/{dnsname}', 'A', ok
),
763 ('A', 'cifs/{dnsname}', 'A', ok
),
765 ("replace vial delete as non-admin with rights",
766 ('A', 'cifs/{dnsname}', '*', ok
),
767 ('A', 'host/{dnsname}', 'A', ok
),
768 ('A', 'host/{dnsname}', 'A', ok
, delete
),
769 ('A', 'cifs/{dnsname}', 'A', ok
, add
),
771 ("replace as non-admin without rights",
772 ('B', 'cifs/b', '*', ok
),
773 ('A', 'cifs/{dnsname}', '*', ok
),
774 ('A', 'host/{dnsname}', 'B', denied
),
775 ('A', 'cifs/{dnsname}', 'B', denied
),
777 ("replace as nobody",
778 ('B', 'cifs/b', '*', ok
),
779 ('A', 'cifs/{dnsname}', '*', ok
),
780 ('A', 'host/{dnsname}', '', denied
),
781 ('A', 'cifs/{dnsname}', '', denied
),
783 ("accumulate and delete as admin",
784 ('A', 'cifs/{dnsname}', '*', ok
),
785 ('A', 'host/{dnsname}', '*', ok
, add
),
786 ('A', 'www/{dnsname}', '*', ok
, add
),
787 ('A', 'www/...', '*', ok
, add
),
788 ('A', 'host/...', '*', ok
, add
),
789 ('A', 'www/{dnsname}', '*', ok
, delete
),
790 ('A', 'host/{dnsname}', '*', ok
, delete
),
791 ('A', 'host/{dnsname}', '*', ok
, add
),
792 ('A', 'www/{dnsname}', '*', ok
, add
),
793 ('A', 'host/...', '*', ok
, delete
),
795 ("accumulate and delete with user rights",
796 ('A', 'cifs/{dnsname}', '*', ok
),
797 ('A', 'host/{dnsname}', 'A', ok
, add
),
798 ('A', 'www/{dnsname}', 'A', ok
, add
),
799 ('A', 'www/...', 'A', ok
, add
),
800 ('A', 'host/...', 'A', ok
, add
),
801 ('A', 'www/{dnsname}', 'A', ok
, delete
),
802 ('A', 'host/{dnsname}', 'A', ok
, delete
),
803 ('A', 'host/{dnsname}', 'A', ok
, add
),
804 ('A', 'www/{dnsname}', 'A', ok
, add
),
805 ('A', 'host/...', 'A', ok
, delete
),
807 ("three way conflict, host first, with partial write rights",
808 ('A', 'host/z.{dnsname}', 'A', ok
),
809 ('B', 'cifs/z.{dnsname}', 'B', denied
),
810 ('C', 'www/z.{dnsname}', 'C', denied
),
812 ("three way conflict, host first, with partial write rights 2",
813 ('A', 'host/z.{dnsname}', 'A', ok
),
814 ('B', 'cifs/z.{dnsname}', 'B', bad
),
815 ('C', 'www/z.{dnsname}', 'C,A', ok
),
818 ("three way conflict sandwich, sufficient rights",
819 ('B', 'host/{dnsname}', 'B', ok
),
820 ('A', 'cifs/{dnsname}', 'A,B', ok
),
821 # the replaces don't fail even though they appear to affect A
822 # and B, because they are effectively no-ops, leaving
823 # everything as it was before.
824 ('A', 'cifs/{dnsname}', 'A', ok
),
825 ('B', 'host/{dnsname}', 'B', ok
),
826 ('C', 'www/{dnsname}', 'A,B,C', ok
),
827 ('C', 'www/{dnsname}', 'B,C', ok
),
828 # because B already has host/, C doesn't matter
829 ('B', 'host/{dnsname}', 'A,B', ok
),
830 # removing host (via replace) frees others, needs B only
831 ('B', 'ldap/{dnsname}', 'B', ok
),
832 ('C', 'www/{dnsname}', 'C', ok
),
833 ('A', 'cifs/{dnsname}', 'A', ok
),
835 # re-adding host is now impossible while A and C have {dnsname} spns
836 ('B', 'host/{dnsname}', '*', bad
),
837 ('B', 'host/{dnsname}', 'A,B,C', bad
),
838 # so let's remove those... (not needing B rights)
839 ('C', 'www/{dnsname}', 'C', ok
, delete
),
840 ('A', 'cifs/{dnsname}', 'A', ok
, delete
),
841 # and now we can add host/ again
842 ('B', 'host/{dnsname}', 'B', ok
),
843 ('C', 'www/{dnsname}', 'B,C', ok
, add
),
844 ('A', 'cifs/{dnsname}', 'A,B', ok
),
846 ("three way conflict, service first, with all write rights",
847 ('A', 'cifs/{dnsname}', '*', ok
),
848 ('B', 'www/{dnsname}', 'A,B,C', ok
),
849 ('C', 'host/{dnsname}', 'A,B,C', bad
),
851 ("three way conflict, service first, just sufficient rights",
852 ('A', 'cifs/{dnsname}', 'A', ok
),
853 ('B', 'www/{dnsname}', 'B', ok
),
854 ('C', 'host/{dnsname}', 'A,B,C', bad
),
857 ("three way conflict, service first, with host write rights",
858 ('A', 'cifs/{dnsname}', '*', ok
),
859 ('B', 'www/{dnsname}', '*', ok
),
860 ('C', 'host/{dnsname}', 'C', bad
),
862 ("three way conflict, service first, with both write rights",
863 ('A', 'cifs/{dnsname}', '*', ok
),
864 ('A', 'cifs/{dnsname}', '*', ok
, delete
),
865 ('A', 'www/{dnsname}', 'A,B,C', ok
),
866 ('B', 'host/{dnsname}', 'A,B', bad
),
867 ('A', 'www/{dnsname}', 'A', ok
, delete
),
868 ('B', 'host/{dnsname}', 'A,B', ok
),
869 ('C', 'cifs/{dnsname}', 'C', bad
),
870 ('C', 'cifs/{dnsname}', 'B,C', ok
),
872 ("three way conflict, services first, with partial rights",
873 ('A', 'cifs/{dnsname}', 'A,C', ok
),
874 ('B', 'www/{dnsname}', '*', ok
),
875 ('C', 'host/{dnsname}', 'A,C', bad
),
881 class LdapSpnAmbitiousTest(LdapSpnTestBase
):
884 ("add a conflict with port, host first both on user",
885 ('user:C', 'host/{dnsname}', '*', ok
),
886 ('user:D', 'www/{dnsname}:80', '*', bad
),
888 # see https://bugzilla.samba.org/show_bug.cgi?id=8929
889 ("add the same one twice, case-insensitive duplicate",
890 ('A', 'host/{dnsname}', '*', ok
),
891 ('A', 'Host/{dnsname}', '*', bad
, add
),
894 # should fail because we don't have all the DSA infrastructure
895 ('A', ("E3514235-4B06-11D1-AB04-00C04FC2DCD2/"
896 "75b84f00-a81b-4a19-8ef2-8e483cccff11/"
897 "{dnsname}"), '*', constraint
)
899 ("single part SPNs matching sAMAccountName",
900 # setting them both together is allegedly a MacOS behaviour,
901 # but all we get from Windows is a mysterious NO_SUCH_OBJECT.
902 ('user:A', {'sAMAccountName': 'A',
903 'servicePrincipalName': 'A'}, '*', ldb
.ERR_NO_SUCH_OBJECT
),
904 ('user:B', {'sAMAccountName': 'B'}, '*', ok
),
905 ('user:B', {'servicePrincipalName': 'B'}, '*', constraint
),
906 ('user:C', {'servicePrincipalName': 'C'}, '*', constraint
),
907 ('user:C', {'sAMAccountName': 'C'}, '*', ok
),
909 ("three part spns with dnsHostName",
910 ('A', {'dNSHostName': '{dnsname}'}, '*', ok
),
911 ('A', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok
),
912 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok
),
913 ('B', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok
),
914 ('B', 'cifs/y.{dnsname}/DomainDNSZones.{dnsname}', '*', constraint
),
915 ('C', 'host/{y.dnsname}/{y.dnsname}', '*', constraint
),
916 ('A', 'host/y.{dnsname}/{dnsname}', '*', constraint
),
922 TestProgram(module
=__name__
, opts
=subunitopts
)