[SCM] Samba Shared Repository - branch master updated

Stefan Metzmacher metze at samba.org
Fri Mar 18 12:46:01 UTC 2022


The branch, master has been updated
       via  cf8048cd49a s4:rpc_server/samr: Use extended DN when searching for user
       via  7b710a05de4 samba-tool group: Add --special parameter to add predefined special group
       via  4f1b7684ed4 functionalprep.sh: Add test for samba-tool add group --special
       via  bf509bf7df1 tests/sam: Ensure that Protected Users group cannot be deleted
       via  62cf7a4ad3e s4:rpc_server/samr: Simplify lp_ctx expression
       via  16a7ce0cdfb s4:auth: Disable NTLM authentication for Protected Users
       via  402d5f59bcb s4:kdc: Add KDC support for Protected Users group
       via  233ce6b2b88 s4:kdc: Add function to get user_info_dc from database
       via  831c245adb3 s4:kdc: simplify samba_kdc_message2entry by using data_blob_string_const("computer")
       via  3a8670c4ca2 dsdb/common: Add helper function for determining if account is in Protected Users group
       via  fb0f65b0b5f s4:provision_users.ldif: Add Protected Users group
       via  410b8b7e06b tests/passwords: Test that LDAP password changes work for Protected Users
       via  fd765aaa5b3 tests/password_lockout: Test NTLM and SAMR password changes with Protected Users
       via  3e0c94a345d tests/krb5: Add tests for the Protected Users group
       via  eba1a9d964b auth/credentials: Add encrypt_samr_password()
       via  b308240cb4b selftest/dbcheck: Fix up msDS-RevealedUsers links with deleted target DN
       via  ded5115f73d tests/krb5: Add helper function to modify ticket flags
       via  c80cd8c9570 tests/krb5: Remove unused import
       via  042137f8fa5 tests/krb5: Add account to cleanup list before adding it to database
       via  539cdaa75ba tests/krb5: Add more encryption type constants
       via  90e5802773a tests/krb5: Remove accounts in reverse order of addition
       via  26334df74fa s4:kdc: Fix copy-paste typo
      from  c91af5f1a8b tests/krb5: Simplify logic

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit cf8048cd49abba5f3da297530219fca6c67f4da1
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Thu Mar 3 14:54:00 2022 +1300

    s4:rpc_server/samr: Use extended DN when searching for user
    
    Switch to dsdb_search() for looking up the user for changing the
    password, and specify that we want extended DNs. Using the SID or GUID
    avoids a race condition if the DN of the user changes.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>
    
    Autobuild-User(master): Stefan Metzmacher <metze at samba.org>
    Autobuild-Date(master): Fri Mar 18 12:45:17 UTC 2022 on sn-devel-184

commit 7b710a05de4aa66b6b20ff399f7ef64c506353af
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Thu Feb 10 17:14:56 2022 +1300

    samba-tool group: Add --special parameter to add predefined special group
    
    This allows default security groups that have been added since Windows
    Server 2008 R2, such as Protected Users, to be created in pre-existing
    domains. An error message is generated if a group already exists with
    the same name, DN, or SID.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 4f1b7684ed437d1e4bf77a867ee0384bc939f312
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Thu Mar 3 20:59:48 2022 +1300

    functionalprep.sh: Add test for samba-tool add group --special
    
    Test that we can add the special Protected Users group, and that we get
    an appropriate error message when attempting to add it a second time.
    
    We add these tests here so that we can make use of an old provision that
    does not already have the Protected Users group added.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit bf509bf7df1348f4793a32dea99c9ec3384c9ad0
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Wed Feb 2 15:47:05 2022 +1300

    tests/sam: Ensure that Protected Users group cannot be deleted
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 62cf7a4ad3eaad056604809880549ab7c8f4196c
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Thu Feb 3 15:17:40 2022 +1300

    s4:rpc_server/samr: Simplify lp_ctx expression
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 16a7ce0cdfb8acba782436066cc8383900ef7e93
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Tue Feb 1 21:08:44 2022 +1300

    s4:auth: Disable NTLM authentication for Protected Users
    
    We also move the authentication to after checking whether the user is
    protected, so that if a user in the Protected Users group tries to
    authenticate with a wrong password, the bag password count is not
    incremented and the account is not locked out. This does not match
    MS-APDS, but matches the behaviour of Windows.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 402d5f59bcb1929cf3db5efb03edf2f62748e40e
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Wed Feb 2 17:08:41 2022 +1300

    s4:kdc: Add KDC support for Protected Users group
    
    Accounts in the Protected Users group acting as clients lack support for
    the RC4 encryption type. TGTs issued to such accounts have a lifetime
    restricted to four hours, and are unable to be proxied or forwarded.
    
    To determine at lookup time whether a client account is a member of
    Protected Users, we now also create an auth_user_info_dc structure when
    creating the database entry for an AS-REQ, rather than only when
    creating a PAC for a TGT, or when recreating the PAC from an RODC-issued
    TGT.
    
    This means that the user's groups are now expanded even for AS-REQs that
    result in an error (such as a PREAUTH_REQUIRED error), but this is
    required to be able to correctly determine the account's available
    encryption types, which are needed soon after fetching the user account.
    
    Currently, the TGT lifetime may exceed four hours (for Heimdal
    specifically). This may happen if PKINIT is used, and either the
    pkinit_max_life_from_cert_extension option is TRUE and
    pkinit_max_life_bound is greater than four hours, or
    pkinit_max_life_from_cert is greater than four hours.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 233ce6b2b88851bdad8c12ae668e2881beba2cd7
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Fri Mar 18 11:13:40 2022 +1300

    s4:kdc: Add function to get user_info_dc from database
    
    The resulting user_info_dc is kept in the 'samba_kdc_entry' structure,
    so it can be reused between calls.
    
    This allows us to simplify samba_kdc_get_pac_blobs(), as it no longer
    need to return a user_info_dc structure.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 831c245adb37ad04c7f52c831a7b45f0d275e760
Author: Stefan Metzmacher <metze at samba.org>
Date:   Tue Feb 8 16:49:35 2022 +0100

    s4:kdc: simplify samba_kdc_message2entry by using data_blob_string_const("computer")
    
    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>

commit 3a8670c4ca246d6b754352056bc06d8564e1a77d
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Thu Feb 3 11:11:56 2022 +1300

    dsdb/common: Add helper function for determining if account is in Protected Users group
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit fb0f65b0b5f140ee0dd8fc962c744f23162e9d46
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Tue Feb 1 21:04:40 2022 +1300

    s4:provision_users.ldif: Add Protected Users group
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 410b8b7e06b879ec71b6cfd24b17be90233803a9
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Wed Feb 9 13:57:47 2022 +1300

    tests/passwords: Test that LDAP password changes work for Protected Users
    
    We want to disable SAMR password changes for Protected Users, but need
    to ensure that other methods of changing the password still work.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit fd765aaa5b319ac997cb82b6fa296e6af2f67db9
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Wed Feb 9 13:50:10 2022 +1300

    tests/password_lockout: Test NTLM and SAMR password changes with Protected Users
    
    Test that NTLM and SAMR password changes cannot be used for Protected
    Users, and that lockouts are not triggered for attempting to use them.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 3e0c94a345d82b25b43c0a93096b2a1779766094
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Tue Feb 1 21:00:16 2022 +1300

    tests/krb5: Add tests for the Protected Users group
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit eba1a9d964b8f91b687809efdec0ee58602839bc
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Wed Feb 23 20:57:44 2022 +1300

    auth/credentials: Add encrypt_samr_password()
    
    This method encrypts a samr_Password structure with the current session
    key, which allows for interactive SamLogon from Python.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit b308240cb4b57a9b379b26933217dee0f8d0b654
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Fri Feb 11 16:30:13 2022 +1300

    selftest/dbcheck: Fix up msDS-RevealedUsers links with deleted target DN
    
    Replicating test accounts to the RODC and then deleting them caused
    stale msDS-RevealedUsers links to remain in the database.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit ded5115f73dff5b8b2f3212988e03f9dbe0c2aa3
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Tue Feb 8 12:15:36 2022 +1300

    tests/krb5: Add helper function to modify ticket flags
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit c80cd8c95708f0c1ef54c4a923f89e89e0d11f18
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Tue Feb 1 20:59:15 2022 +1300

    tests/krb5: Remove unused import
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 042137f8fa5f9672ace7e26969748716ca83baf9
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Tue Feb 1 20:57:22 2022 +1300

    tests/krb5: Add account to cleanup list before adding it to database
    
    This ensures accounts are still cleaned up if a test fails before adding
    it to the cleanup list.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 539cdaa75bab843fa26a50c0775f2c5bfe77958e
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Tue Feb 1 20:55:56 2022 +1300

    tests/krb5: Add more encryption type constants
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 90e5802773ad2031afb2b52893bb4d011a07d662
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Tue Feb 1 20:54:39 2022 +1300

    tests/krb5: Remove accounts in reverse order of addition
    
    This prevents problems if accounts are added as children of other
    accounts.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

commit 26334df74faaa40501942ea36007d2b45df431f2
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Tue Feb 1 20:52:16 2022 +1300

    s4:kdc: Fix copy-paste typo
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Stefan Metzmacher <metze at samba.org>

-----------------------------------------------------------------------

Summary of changes:
 auth/credentials/pycredentials.c                 |   43 +-
 python/samba/netcmd/group.py                     |  197 +++-
 python/samba/tests/krb5/kdc_base_test.py         |  142 ++-
 python/samba/tests/krb5/kdc_tgs_tests.py         |   18 +-
 python/samba/tests/krb5/protected_users_tests.py | 1195 ++++++++++++++++++++++
 python/samba/tests/krb5/raw_testcase.py          |   42 +-
 python/samba/tests/krb5/rfc4120_constants.py     |    8 +
 python/samba/tests/krb5/s4u_tests.py             |   17 +-
 python/samba/tests/usage.py                      |    1 +
 selftest/knownfail.d/protected_users             |    2 +
 selftest/knownfail_heimdal_kdc                   |    7 +
 selftest/knownfail_mit_kdc                       |   18 +-
 selftest/tests.py                                |    4 +
 source4/auth/ntlm/auth_sam.c                     |   49 +-
 source4/dsdb/common/util.c                       |   31 +
 source4/dsdb/tests/python/password_lockout.py    |  277 +++++
 source4/dsdb/tests/python/passwords.py           |   77 ++
 source4/dsdb/tests/python/sam.py                 |    1 +
 source4/kdc/db-glue.c                            |   71 +-
 source4/kdc/db-glue.h                            |    1 +
 source4/kdc/hdb-samba4.c                         |    2 +-
 source4/kdc/mit_samba.c                          |   18 +-
 source4/kdc/pac-glue.c                           |   72 +-
 source4/kdc/pac-glue.h                           |    7 +-
 source4/kdc/samba_kdc.h                          |    1 +
 source4/kdc/wdc-samba4.c                         |    3 +-
 source4/kdc/wscript_build                        |    2 +-
 source4/rpc_server/samr/samr_password.c          |   32 +-
 source4/selftest/tests.py                        |    4 +
 source4/setup/provision_users.ldif               |    9 +
 testprogs/blackbox/dbcheck.sh                    |    2 +-
 testprogs/blackbox/test_special_group.sh         |   52 +
 32 files changed, 2265 insertions(+), 140 deletions(-)
 create mode 100755 python/samba/tests/krb5/protected_users_tests.py
 create mode 100644 selftest/knownfail.d/protected_users
 create mode 100755 testprogs/blackbox/test_special_group.sh


Changeset truncated at 500 lines:

diff --git a/auth/credentials/pycredentials.c b/auth/credentials/pycredentials.c
index 08b78e9dfce..49ea06bcd69 100644
--- a/auth/credentials/pycredentials.c
+++ b/auth/credentials/pycredentials.c
@@ -970,6 +970,38 @@ static PyObject *py_creds_encrypt_netr_crypt_password(PyObject *self,
 	Py_RETURN_NONE;
 }
 
+static PyObject *py_creds_encrypt_samr_password(PyObject *self,
+						PyObject *args)
+{
+	DATA_BLOB data = data_blob_null;
+	struct cli_credentials *creds  = NULL;
+	struct samr_Password   *pwd    = NULL;
+	NTSTATUS status;
+	PyObject *py_cp = Py_None;
+
+	creds = PyCredentials_AsCliCredentials(self);
+	if (creds == NULL) {
+		PyErr_Format(PyExc_TypeError, "Credentials expected");
+		return NULL;
+	}
+
+	if (!PyArg_ParseTuple(args, "O", &py_cp)) {
+		return NULL;
+	}
+
+	pwd = pytalloc_get_type(py_cp, struct samr_Password);
+	if (pwd == NULL) {
+		/* pytalloc_get_type sets TypeError */
+		return NULL;
+	}
+	data = data_blob_const(pwd->hash, sizeof(pwd->hash));
+	status = netlogon_creds_session_encrypt(creds->netlogon_creds, data);
+
+	PyErr_NTSTATUS_IS_ERR_RAISE(status);
+
+	Py_RETURN_NONE;
+}
+
 static PyObject *py_creds_get_smb_signing(PyObject *self, PyObject *unused)
 {
 	enum smb_signing_setting signing_state;
@@ -1389,10 +1421,19 @@ static PyMethodDef py_creds_methods[] = {
 		.ml_name  = "encrypt_netr_crypt_password",
 		.ml_meth  = py_creds_encrypt_netr_crypt_password,
 		.ml_flags = METH_VARARGS,
-		.ml_doc   = "S.encrypt_netr_crypt_password(password) -> NTSTATUS\n"
+		.ml_doc   = "S.encrypt_netr_crypt_password(password) -> None\n"
 			    "Encrypt the supplied password using the session key and\n"
 			    "the negotiated encryption algorithm in place\n"
 			    "i.e. it overwrites the original data"},
+	{
+		.ml_name  = "encrypt_samr_password",
+		.ml_meth  = py_creds_encrypt_samr_password,
+		.ml_flags = METH_VARARGS,
+		.ml_doc   = "S.encrypt_samr_password(password) -> None\n"
+			    "Encrypt the supplied password using the session key and\n"
+			    "the negotiated encryption algorithm in place\n"
+			    "i.e. it overwrites the original data"
+	},
 	{
 		.ml_name  = "get_smb_signing",
 		.ml_meth  = py_creds_get_smb_signing,
diff --git a/python/samba/netcmd/group.py b/python/samba/netcmd/group.py
index 3c8a9054339..5666cde614d 100644
--- a/python/samba/netcmd/group.py
+++ b/python/samba/netcmd/group.py
@@ -19,13 +19,14 @@
 import samba.getopt as options
 from samba.netcmd import Command, SuperCommand, CommandError, Option
 import ldb
-from samba.ndr import ndr_unpack
+from samba.ndr import ndr_pack, ndr_unpack
 from samba.dcerpc import security
 
 from samba.auth import system_session
 from samba.samdb import SamDB
 from samba.dsdb import (
     ATYPE_SECURITY_GLOBAL_GROUP,
+    DS_GUID_USERS_CONTAINER,
     GTYPE_SECURITY_BUILTIN_LOCAL_GROUP,
     GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
     GTYPE_SECURITY_GLOBAL_GROUP,
@@ -33,11 +34,14 @@ from samba.dsdb import (
     GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
     GTYPE_DISTRIBUTION_GLOBAL_GROUP,
     GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
+    SYSTEM_FLAG_DISALLOW_DELETE,
+    SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE,
+    SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME,
     UF_ACCOUNTDISABLE,
 )
 from collections import defaultdict
 from subprocess import check_call, CalledProcessError
-from samba.common import get_bytes
+from samba.common import get_bytes, normalise_int32
 import os
 import tempfile
 from . import common
@@ -106,13 +110,15 @@ Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (b
         Option("--notes", help="Groups's notes", type=str),
         Option("--gid-number", help="Group's Unix/RFC2307 GID number", type=int),
         Option("--nis-domain", help="SFU30 NIS Domain", type=str),
+        Option("--special", help="Add a special predefined group", action="store_true", default=False),
     ]
 
     takes_args = ["groupname"]
 
     def run(self, groupname, credopts=None, sambaopts=None,
             versionopts=None, H=None, groupou=None, group_scope=None,
-            group_type=None, description=None, mail_address=None, notes=None, gid_number=None, nis_domain=None):
+            group_type=None, description=None, mail_address=None, notes=None, gid_number=None, nis_domain=None,
+            special=False):
 
         if (group_type or "Security") == "Security":
             gtype = security_group.get(group_scope, GTYPE_SECURITY_GLOBAL_GROUP)
@@ -128,6 +134,191 @@ Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (b
         try:
             samdb = SamDB(url=H, session_info=system_session(),
                           credentials=creds, lp=lp)
+        except Exception as e:
+            # FIXME: catch more specific exception
+            raise CommandError(f'Failed to add group "{groupname}"', e)
+
+        if special:
+            invalid_option = None
+            if group_scope is not None:
+                invalid_option = 'group-scope'
+            elif group_type is not None:
+                invalid_option = 'group-type'
+            elif description is not None:
+                invalid_option = 'description'
+            elif mail_address is not None:
+                invalid_option = 'mail-address'
+            elif notes is not None:
+                invalid_option = 'notes'
+            elif gid_number is not None:
+                invalid_option = 'gid-number'
+            elif nis_domain is not None:
+                invalid_option = 'nis-domain'
+
+            if invalid_option is not None:
+                raise CommandError(f'Superfluous option --{invalid_option} '
+                                   f'specified with --special')
+
+            if not samdb.am_pdc():
+                raise CommandError('Adding special groups is only permitted '
+                                   'against the PDC!')
+
+            special_groups = {
+                # On Windows, this group is added automatically when the PDC
+                # role is held by a DC running Windows Server 2012 R2 or later.
+                # https://docs.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/protected-users-security-group#BKMK_Requirements
+                'Protected Users'.lower(): (
+                    'Protected Users',
+                    GTYPE_SECURITY_GLOBAL_GROUP,
+                    security.DOMAIN_RID_PROTECTED_USERS,
+                    'Members of this group are afforded additional '
+                    'protections against authentication security threats'),
+            }
+
+            special_group = special_groups.get(groupname.lower())
+            if special_group is None:
+                raise CommandError(f'Unknown special group "{groupname}".')
+
+            groupname, gtype, rid, description = special_group
+            group_type = normalise_int32(gtype)
+
+            group_dn = samdb.get_default_basedn()
+
+            if gtype == GTYPE_SECURITY_GLOBAL_GROUP:
+                object_sid = security.dom_sid(
+                    f'{samdb.get_domain_sid()}-{rid}')
+                system_flags = None
+
+                if not groupou:
+                    group_dn = samdb.get_wellknown_dn(group_dn,
+                                                      DS_GUID_USERS_CONTAINER)
+
+            elif gtype == GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
+                object_sid = security.dom_sid(f'S-1-5-32-{rid}')
+                system_flags = (SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE |
+                                SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME |
+                                SYSTEM_FLAG_DISALLOW_DELETE)
+
+                if not groupou and not group_dn.add_child('CN=Builtin'):
+                    raise RuntimeError('Error getting Builtin objects DN')
+            else:
+                raise RuntimeError(f'Unknown group type {gtype}')
+
+            if groupou and not group_dn.add_child(groupou):
+                raise CommandError(f'Invalid group OU "{groupou}"')
+
+            if not group_dn.add_child(f'CN={groupname}'):
+                raise CommandError(f'Invalid group name "{groupname}"')
+
+            msg = {
+                'dn': group_dn,
+                'sAMAccountName': groupname,
+                'objectClass': 'group',
+                'groupType': group_type,
+                'description': description,
+                'objectSid': ndr_pack(object_sid),
+                'isCriticalSystemObject': 'TRUE',
+            }
+
+            if system_flags is not None:
+                msg['systemFlags'] = system_flags
+
+            try:
+                samdb.add(msg, controls=['relax:0'])
+            except ldb.LdbError as e:
+                num, estr = e.args
+                if num == ldb.ERR_CONSTRAINT_VIOLATION:
+                    try:
+                        res = samdb.search(
+                            expression=f'(objectSid={object_sid})',
+                            attrs=['sAMAccountName'])
+                    except ldb.LdbError:
+                        raise CommandError(
+                            f'Failed to add group "{groupname}"', e)
+
+                    if len(res) != 1:
+                        raise CommandError(
+                            f'Failed to add group "{groupname}"', e)
+
+                    name = res[0].get('sAMAccountName', idx=0)
+                    if name:
+                        with_name = f' with name "{name}"'
+                    else:
+                        with_name = ''
+
+                    raise CommandError(
+                        f'Failed to add group "{groupname}" - Special group '
+                        f'already exists{with_name} at "{res[0].dn}".')
+
+                elif num == ldb.ERR_ENTRY_ALREADY_EXISTS:
+                    try:
+                        res = samdb.search(base=group_dn,
+                                           scope=ldb.SCOPE_BASE,
+                                           attrs=['sAMAccountName',
+                                                  'objectSid',
+                                                  'groupType'])
+                    except ldb.LdbError:
+                        try:
+                            res = samdb.search(
+                                expression=f'(sAMAccountName={groupname})',
+                                attrs=['sAMAccountName',
+                                       'objectSid',
+                                       'groupType'])
+                        except ldb.LdbError:
+                            raise CommandError(
+                                f'Failed to add group "{groupname}"', e)
+
+                    if len(res) != 1:
+                        raise CommandError(
+                            f'Failed to add group "{groupname}"', e)
+
+                    got_name = res[0].get('sAMAccountName', idx=0)
+                    if got_name:
+                        named = f'named "{got_name}"'
+                    else:
+                        named = 'with no name'
+
+                    got_group_type = res[0].get('groupType',
+                                                idx=0).decode('utf-8')
+                    if group_type != got_group_type:
+                        raise CommandError(
+                            f'Failed to add group "{groupname}" - An object '
+                            f'{named} at "{res[0].dn}" already exists, but it '
+                            f'is not a security group. Rename or remove this '
+                            f'existing object before attempting to add this '
+                            f'special group.')
+
+                    sid = res[0].get('objectSid', idx=0)
+                    if sid is None:
+                        raise CommandError(
+                            f'Failed to add group "{groupname}" - A security '
+                            f'group {named} at "{res[0].dn}" already exists, '
+                            f'but it lacks a SID. Rename or remove this '
+                            f'existing object before attempting to add this '
+                            f'special group.')
+                    else:
+                        sid = ndr_unpack(security.dom_sid, sid)
+                        if sid == object_sid:
+                            raise CommandError(
+                                f'Failed to add group "{groupname}" - The '
+                                f'security group {named} at "{res[0].dn}" '
+                                f'already exists.')
+                        else:
+                            raise CommandError(
+                                f'Failed to add group "{groupname}" - A '
+                                f'security group {named} at "{res[0].dn}" '
+                                f'already exists, but it has the wrong SID, '
+                                f'and will not function as expected. Rename '
+                                f'or remove this existing object before '
+                                f'attempting to add this special group.')
+                else:
+                    raise CommandError(f'Failed to add group "{groupname}"', e)
+            else:
+                self.outf.write(f'Added group {groupname}\n')
+
+            return
+
+        try:
             samdb.newgroup(groupname, groupou=groupou, grouptype=gtype,
                            description=description, mailaddress=mail_address, notes=notes,
                            gidnumber=gid_number, nisdomain=nis_domain)
diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py
index 4fa9384cba9..16e3f7a6a73 100644
--- a/python/samba/tests/krb5/kdc_base_test.py
+++ b/python/samba/tests/krb5/kdc_base_test.py
@@ -30,7 +30,12 @@ import ldb
 from ldb import SCOPE_BASE
 from samba import generate_random_password
 from samba.auth import system_session
-from samba.credentials import Credentials, SPECIFIED, MUST_USE_KERBEROS
+from samba.credentials import (
+    Credentials,
+    SPECIFIED,
+    DONT_USE_KERBEROS,
+    MUST_USE_KERBEROS,
+)
 from samba.dcerpc import drsblobs, drsuapi, misc, krb5pac, krb5ccache, security
 from samba.drs_utils import drs_Replicate, drsuapi_connect
 from samba.dsdb import (
@@ -75,7 +80,6 @@ from samba.tests.krb5.rfc4120_constants import (
     KU_PA_ENC_TIMESTAMP,
     KU_TICKET,
     NT_PRINCIPAL,
-    NT_SRV_HST,
     NT_SRV_INST,
     PADATA_ENCRYPTED_CHALLENGE,
     PADATA_ENC_TIMESTAMP,
@@ -115,8 +119,8 @@ class KDCBaseTest(RawKerberosTest):
         cls.account_base = f'{secrets.token_hex(4)}_'
         cls.account_id = 0
 
-        # A set containing DNs of accounts created as part of testing.
-        cls.accounts = set()
+        # A list containing DNs of accounts created as part of testing.
+        cls.accounts = []
 
         cls.account_cache = {}
         cls.tkt_cache = {}
@@ -137,7 +141,7 @@ class KDCBaseTest(RawKerberosTest):
                 except ldb.LdbError:
                     pass
 
-            for dn in cls.accounts:
+            for dn in reversed(cls.accounts):
                 delete_force(cls._ldb, dn)
 
         if cls._rodc_ctx is not None:
@@ -241,6 +245,28 @@ class KDCBaseTest(RawKerberosTest):
 
         return default_enctypes
 
+    def create_group(self, samdb, name, ou=None):
+        if ou is None:
+            ou = samdb.get_wellknown_dn(samdb.get_default_basedn(),
+                                        DS_GUID_USERS_CONTAINER)
+
+        dn = f'CN={name},{ou}'
+
+        # Remove the group if it exists; this will happen if a previous test
+        # run failed.
+        delete_force(samdb, dn)
+
+        # Save the group name so it can be deleted in tearDownClass.
+        self.accounts.append(dn)
+
+        details = {
+            'dn': dn,
+            'objectClass': 'group'
+        }
+        samdb.add(details)
+
+        return dn
+
     def create_account(self, samdb, name, account_type=AccountType.USER,
                        spn=None, upn=None, additional_details=None,
                        ou=None, account_control=0, add_dollar=True):
@@ -299,6 +325,8 @@ class KDCBaseTest(RawKerberosTest):
             details["userPrincipalName"] = upn
         if additional_details is not None:
             details.update(additional_details)
+        # Save the account name so it can be deleted in tearDownClass
+        self.accounts.append(dn)
         samdb.add(details)
 
         creds = KerberosCredentials()
@@ -314,9 +342,6 @@ class KDCBaseTest(RawKerberosTest):
         creds.set_dn(ldb.Dn(samdb, dn))
         creds.set_upn(upn)
         creds.set_spn(spn)
-        #
-        # Save the account name so it can be deleted in tearDownClass
-        self.accounts.add(dn)
 
         self.creds_set_enctypes(creds)
 
@@ -620,16 +645,28 @@ class KDCBaseTest(RawKerberosTest):
         creds.set_tgs_supported_enctypes(supported_enctypes)
         creds.set_ap_supported_enctypes(supported_enctypes)
 
-    def add_to_group(self, account_dn, group_dn, group_attr):
+    def add_to_group(self, account_dn, group_dn, group_attr, expect_attr=True):
         samdb = self.get_samdb()
 
-        res = samdb.search(base=group_dn,
-                           scope=ldb.SCOPE_BASE,
-                           attrs=[group_attr])
+        try:
+            res = samdb.search(base=group_dn,
+                               scope=ldb.SCOPE_BASE,
+                               attrs=[group_attr])
+        except ldb.LdbError as err:
+            num, _ = err.args
+            if num != ldb.ERR_NO_SUCH_OBJECT:
+                raise
+
+            self.fail(err)
+
         orig_msg = res[0]
-        self.assertIn(group_attr, orig_msg)
+        members = orig_msg.get(group_attr)
+        if expect_attr:
+            self.assertIsNotNone(members)
+        elif members is None:
+            members = ()
 
-        members = list(orig_msg[group_attr])
+        members = list(members)
         members.append(account_dn)
 
         msg = ldb.Message()
@@ -644,6 +681,32 @@ class KDCBaseTest(RawKerberosTest):
 
         return cleanup
 
+    def remove_from_group(self, account_dn, group_dn):
+        samdb = self.get_samdb()
+
+        res = samdb.search(base=group_dn,
+                           scope=ldb.SCOPE_BASE,
+                           attrs=['member'])
+        orig_msg = res[0]
+        self.assertIn('member', orig_msg)
+        members = list(orig_msg['member'])
+
+        account_dn = str(account_dn).encode('utf-8')
+        self.assertIn(account_dn, members)
+        members.remove(account_dn)
+
+        msg = ldb.Message()
+        msg.dn = group_dn
+        msg['member'] = ldb.MessageElement(members,
+                                           ldb.FLAG_MOD_REPLACE,
+                                           'member')
+
+        cleanup = samdb.msg_diff(msg, orig_msg)
+        self.ldb_cleanups.append(cleanup)
+        samdb.modify(msg)
+
+        return cleanup
+
     def get_cached_creds(self, *,
                          account_type,
                          opts=None,
@@ -657,6 +720,7 @@ class KDCBaseTest(RawKerberosTest):
             'add_dollar': True,
             'upn': None,
             'spn': None,
+            'additional_details': None,
             'allowed_replication': False,
             'allowed_replication_mock': False,
             'denied_replication': False,
@@ -670,6 +734,9 @@ class KDCBaseTest(RawKerberosTest):
             'delegation_from_dn': None,
             'trusted_to_auth_for_delegation': False,
             'fast_support': False,
+            'member_of': None,
+            'kerberos_enabled': True,
+            'secure_channel_type': None,
             'id': None
         }
 
@@ -699,6 +766,7 @@ class KDCBaseTest(RawKerberosTest):
                             add_dollar,
                             upn,
                             spn,
+                            additional_details,
                             allowed_replication,
                             allowed_replication_mock,
                             denied_replication,
@@ -712,6 +780,9 @@ class KDCBaseTest(RawKerberosTest):
                             delegation_from_dn,
                             trusted_to_auth_for_delegation,


-- 
Samba Shared Repository



More information about the samba-cvs mailing list