samba-tool: gpo load extension names
authorDavid Mulder <dmulder@suse.com>
Tue, 15 Feb 2022 18:09:12 +0000 (11:09 -0700)
committerAndrew Bartlett <abartlet@samba.org>
Mon, 30 Jan 2023 09:00:39 +0000 (09:00 +0000)
Signed-off-by: David Mulder <dmulder@suse.com>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Tested-by: Kees van Vloten <keesvanvloten@gmail.com>
python/samba/netcmd/gpo.py
python/samba/policies.py
python/samba/tests/samba_tool/gpo.py

index 7facb6c2cedd903069278ea7e6943cf90ef4df9e..24a9b1f2b65813a040b83419ba10e9e269057bce 100644 (file)
@@ -625,6 +625,10 @@ class cmd_show(GPOCommand):
         self.outf.write("GPO          : %s\n" % msg['name'][0])
         self.outf.write("display name : %s\n" % msg['displayName'][0])
         self.outf.write("path         : %s\n" % msg['gPCFileSysPath'][0])
+        if 'gPCMachineExtensionNames' in msg:
+            self.outf.write("Machine Exts : %s\n" % msg['gPCMachineExtensionNames'][0])
+        if 'gPCUserExtensionNames' in msg:
+            self.outf.write("User Exts    : %s\n" % msg['gPCUserExtensionNames'][0])
         self.outf.write("dn           : %s\n" % msg.dn)
         self.outf.write("version      : %s\n" % attr_default(msg, 'versionNumber', '0'))
         self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(msg, 'flags', 0))))
@@ -712,6 +716,13 @@ class cmd_load(GPOCommand):
 
     Valid class attributes: MACHINE|USER|BOTH
     Data arrays are interpreted as bytes.
+
+    The --machine-ext-name and --user-ext-name options are multi-value inputs
+    which respectively set the gPCMachineExtensionNames and gPCUserExtensionNames
+    ldap attributes on the GPO. These attributes must be set to the correct GUID
+    names for Windows Group Policy to work correctly. These GUIDs represent
+    the client side extensions to apply on the machine. Linux Group Policy does
+    not enforce this constraint.
     """
 
     synopsis = "%prog <gpo> [options]"
@@ -726,11 +737,17 @@ class cmd_load(GPOCommand):
 
     takes_options = [
         Option("-H", help="LDB URL for database or target server", type=str),
-        Option("--content", help="JSON file of policy inputs", type=str)
+        Option("--content", help="JSON file of policy inputs", type=str),
+        Option("--machine-ext-name",
+            action="append", default=[], dest="machine_exts",
+            help="A machine extension name to add to gPCMachineExtensionNames"),
+        Option("--user-ext-name",
+            action="append", default=[], dest="user_exts",
+            help="A user extension name to add to gPCUserExtensionNames")
     ]
 
-    def run(self, gpo, H=None, content=None, sambaopts=None, credopts=None,
-            versionopts=None):
+    def run(self, gpo, H=None, content=None, machine_exts=[], user_exts=[],
+            sambaopts=None, credopts=None, versionopts=None):
         if content is None:
             policy_defs = json.loads(sys.stdin.read())
         elif os.path.exists(content):
@@ -744,6 +761,10 @@ class cmd_load(GPOCommand):
         self.url = dc_url(self.lp, self.creds, H)
         self.samdb_connect()
         reg = RegistryGroupPolicies(gpo, self.lp, self.creds, self.samdb, H)
+        for ext_name in machine_exts:
+            reg.register_extension_name(ext_name, 'gPCMachineExtensionNames')
+        for ext_name in user_exts:
+            reg.register_extension_name(ext_name, 'gPCUserExtensionNames')
         try:
             reg.merge_s(policy_defs)
         except NTSTATUSError as e:
@@ -799,11 +820,17 @@ class cmd_remove(GPOCommand):
 
     takes_options = [
         Option("-H", help="LDB URL for database or target server", type=str),
-        Option("--content", help="JSON file of policy inputs", type=str)
+        Option("--content", help="JSON file of policy inputs", type=str),
+        Option("--machine-ext-name",
+            action="append", default=[], dest="machine_exts",
+            help="A machine extension name to remove from gPCMachineExtensionNames"),
+        Option("--user-ext-name",
+            action="append", default=[], dest="user_exts",
+            help="A user extension name to remove from gPCUserExtensionNames")
     ]
 
-    def run(self, gpo, H=None, content=None, sambaopts=None, credopts=None,
-            versionopts=None):
+    def run(self, gpo, H=None, content=None, machine_exts=[], user_exts=[],
+            sambaopts=None, credopts=None, versionopts=None):
         if content is None:
             policy_defs = json.loads(sys.stdin.read())
         elif os.path.exists(content):
@@ -817,6 +844,10 @@ class cmd_remove(GPOCommand):
         self.url = dc_url(self.lp, self.creds, H)
         self.samdb_connect()
         reg = RegistryGroupPolicies(gpo, self.lp, self.creds, self.samdb, H)
+        for ext_name in machine_exts:
+            reg.unregister_extension_name(ext_name, 'gPCMachineExtensionNames')
+        for ext_name in user_exts:
+            reg.unregister_extension_name(ext_name, 'gPCUserExtensionNames')
         try:
             reg.remove_s(policy_defs)
         except NTSTATUSError as e:
index b32d68cf373bd67c4b32ee31c1e4695e621308b4..7878b81645f8c2ef6c22b345810f71c296def286 100644 (file)
@@ -32,9 +32,12 @@ from samba.registry import str_regtype
 from samba.ntstatus import (
     NT_STATUS_OBJECT_NAME_INVALID,
     NT_STATUS_OBJECT_NAME_NOT_FOUND,
-    NT_STATUS_OBJECT_PATH_NOT_FOUND
+    NT_STATUS_OBJECT_PATH_NOT_FOUND,
+    NT_STATUS_INVALID_PARAMETER
 )
 from samba.gp_parse.gp_ini import GPTIniParser
+from samba.common import get_string
+from samba.dcerpc.misc import GUID
 
 GPT_EMPTY = \
 """
@@ -51,7 +54,7 @@ class RegistryGroupPolicies(object):
         realm = self.lp.get('realm')
         self.pol_dir = '\\'.join([realm.lower(), 'Policies', gpo, '%s'])
         self.pol_file = '\\'.join([self.pol_dir, 'Registry.pol'])
-
+        self.policy_dn = get_gpo_dn(self.samdb, self.gpo)
 
         if host and host.startswith('ldap://'):
             dc_hostname = host[7:]
@@ -172,13 +175,62 @@ class RegistryGroupPolicies(object):
             self.conn.savefile(GPT_INI, out_data.read().encode('utf-8'))
 
         # Set the new versionNumber on the ldap object
-        policy_dn = get_gpo_dn(self.samdb, self.gpo)
         m = ldb.Message()
-        m.dn = policy_dn
+        m.dn = self.policy_dn
         m['new_value'] = ldb.MessageElement(str(version), ldb.FLAG_MOD_REPLACE,
                                             'versionNumber')
         self.samdb.modify(m)
 
+    def __validate_extension_registration(self, ext_name, ext_attr):
+        try:
+            ext_name_guid = GUID(ext_name)
+        except samba.NTSTATUSError as e:
+            if e.args[0] == NT_STATUS_INVALID_PARAMETER:
+                raise SyntaxError('Extension name not formated correctly')
+            raise
+        if ext_attr not in ['gPCMachineExtensionNames',
+                            'gPCUserExtensionNames']:
+            raise SyntaxError('Extension attribute incorrect')
+        return '{%s}' % ext_name_guid
+
+    def register_extension_name(self, ext_name, ext_attr):
+        ext_name = self.__validate_extension_registration(ext_name, ext_attr)
+        res = self.samdb.search(base=self.policy_dn, scope=ldb.SCOPE_BASE,
+                                attrs=[ext_attr])
+        if len(res) == 0 or ext_attr not in res[0]:
+            ext_names = '[]'
+        else:
+            ext_names = get_string(res[0][ext_attr][-1])
+        if ext_name not in ext_names:
+            ext_names = '[' + ext_names.strip('[]') + ext_name + ']'
+        else:
+            return
+
+        m = ldb.Message()
+        m.dn = self.policy_dn
+        m['new_value'] = ldb.MessageElement(ext_names, ldb.FLAG_MOD_REPLACE,
+                                            ext_attr)
+        self.samdb.modify(m)
+
+    def unregister_extension_name(self, ext_name, ext_attr):
+        ext_name = self.__validate_extension_registration(ext_name, ext_attr)
+        res = self.samdb.search(base=self.policy_dn, scope=ldb.SCOPE_BASE,
+                                attrs=[ext_attr])
+        if len(res) == 0 or ext_attr not in res[0]:
+            return
+        else:
+            ext_names = get_string(res[0][ext_attr][-1])
+        if ext_name in ext_names:
+            ext_names = ext_names.replace(ext_name, '')
+        else:
+            return
+
+        m = ldb.Message()
+        m.dn = self.policy_dn
+        m['new_value'] = ldb.MessageElement(ext_names, ldb.FLAG_MOD_REPLACE,
+                                            ext_attr)
+        self.samdb.modify(m)
+
     def remove_s(self, json_input):
         '''remove_s
         json_input: JSON list of entries to remove from GPO
index fcb2682eb246e5540f317eda7315ca7274b94b36..2583f71a6f0a220fbc45ac7124e8d7ccfe4e130d 100644 (file)
@@ -95,6 +95,11 @@ b"""
 ]
 """
 
+# These are new GUIDs, not used elsewhere, made up for the use of testing the
+# adding of extension GUIDs in `samba-tool gpo load`.
+ext_guids = ['{123d2b56-7b14-4516-bbc4-763d29d57654}',
+             '{d000e91b-e70f-481b-9549-58de7929bcee}']
+
 source_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../.."))
 
 def has_difference(path1, path2, binary=True, xml=True, sortlines=False):
@@ -1568,6 +1573,10 @@ class GpoCmdTestCase(SambaToolCmdTest):
             (result, out, err) = self.runsubcmd("gpo", "load",
                                                  self.gpo_guid,
                                                  "--content=%s" % f.name,
+                                                 "--machine-ext-name=%s" %
+                                                 ext_guids[0],
+                                                 "--user-ext-name=%s" %
+                                                 ext_guids[1],
                                                  "-H", "ldap://%s" %
                                                  os.environ["SERVER"],
                                                  "-U%s%%%s" %
@@ -1580,6 +1589,8 @@ class GpoCmdTestCase(SambaToolCmdTest):
         self.assertCmdSuccess(result, out, err, 'Failed to fetch gpos')
         self.assertIn('homepage', out, 'Homepage policy not loaded')
         self.assertIn('samba.org', out, 'Homepage policy not loaded')
+        self.assertIn(ext_guids[0], out, 'Machine extension not loaded')
+        self.assertIn(ext_guids[1], out, 'User extension not loaded')
         toolbar_data = '"valuename": "IEToolbar",\n        "class": "USER",' + \
                        '\n        "type": "REG_BINARY",' + \
                        '\n        "data": [\n            0\n        ]'
@@ -1595,6 +1606,10 @@ class GpoCmdTestCase(SambaToolCmdTest):
             (result, out, err) = self.runsubcmd("gpo", "remove",
                                                  self.gpo_guid,
                                                  "--content=%s" % f.name,
+                                                 "--machine-ext-name=%s" %
+                                                 ext_guids[0],
+                                                 "--user-ext-name=%s" %
+                                                 ext_guids[1],
                                                  "-H", "ldap://%s" %
                                                  os.environ["SERVER"],
                                                  "-U%s%%%s" %
@@ -1606,6 +1621,8 @@ class GpoCmdTestCase(SambaToolCmdTest):
                                             "ldap://%s" % os.environ["SERVER"])
         self.assertCmdSuccess(result, out, err, 'Failed to fetch gpos')
         self.assertNotIn('samba.org', out, 'Homepage policy not removed')
+        self.assertNotIn(ext_guids[0], out, 'Machine extension not unloaded')
+        self.assertNotIn(ext_guids[1], out, 'User extension not unloaded')
 
     def setUp(self):
         """set up a temporary GPO to work with"""