[PATCHES v1] GPO fixes

David Mulder dmulder at suse.com
Mon Jan 8 17:05:41 UTC 2018


Hoping to get these into 4.8;
Basically these are all the fixes/improvements from the machine policy
patches, minus the machine policy.
Includes:
* Fixes a crash in gpo unapply
* Don't stop parsing gpos if one fails
* Cache gpo versions and read from the cache, instead of reading
directly from the sysvol
* Call the gpupdate command from winbind, using the interval specified
by MS spec (random interval between 90 and 120 minutes).
* Enable gpupdate by default (this now only has the effect of enabling
the system access policies for the kdc).
* NEW: Provide a method for disabling gpo extensions. An extension will
now check if a <my filename>.disabled file is present, and the extension
is ignored if present. This required moving the system access policies
to their own file, which is now required for every extension.

This patch set *does not* contain any new gpo extensions, just
improvements to the overall gpo code (and making it easily extensible
for adding new extensions).

docs-xml/smbdotconf/domain/gpoupdatecommand.xml    |  11 +-
 docs-xml/smbdotconf/winbind/applygrouppolicies.xml |  19 ++++
 lib/param/loadparm.c                               |   1 +
 python/samba/gp_sec_ext.py                         | 140
+++++++++++++++++++++++++
 python/samba/gpclass.py                            | 233
+++++++++++------------------------------
 selftest/target/Samba4.pm                          |   2 +-
 source3/param/loadparm.c                           |   2 +
 source3/winbindd/winbindd.c                        |   2 +
 source3/winbindd/winbindd_gpupdate.c               | 116
+++++++++++++++++++++
 source3/winbindd/winbindd_proto.h                  |   3 +
 source3/winbindd/wscript_build                     |   3 +-
 source4/dsdb/gpo/gpo_update.c                      | 193
----------------------------------
 source4/dsdb/wscript_build                         |   9 --
 source4/scripting/bin/samba_gpoupdate              |  49 +++++++--
 source4/scripting/bin/wscript_build                |   2 +-
 source4/scripting/wscript_build                    |   7 +-
 source4/torture/gpo/apply.c                        | 258
+++++++++++++++++++++++++++++++++++++---------
 17 files changed, 608 insertions(+), 442 deletions(-)

-- 
David Mulder
SUSE Labs Software Engineer - Samba
dmulder at suse.com
SUSE Linux GmbH, GF: Felix Imendörffer, Jane Smithard, Graham Norton, HRB 21284 (AG Nürnberg)

-------------- next part --------------
From ec40c90af3f439fb639700d2b61034a3c023e681 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Mon, 8 Jan 2018 09:16:11 -0700
Subject: [PATCH 1/7] gpo: Fix crashes in gpo unapply

Signed-off-by: David Mulder <dmulder at suse.com>
---
 python/samba/gpclass.py | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index 00330eb5ecb..ca34120d513 100644
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -217,12 +217,19 @@ class gp_log:
         exts = guid_obj.findall('gp_ext')
         if exts is not None:
             for ext in exts:
-                ext_map = {val[0]: val[1] for (key, val) in \
-                    data_maps[ext.attrib['name']].items()}
                 attrs = ext.findall('attribute')
                 for attr in attrs:
-                    ret.append((attr.attrib['name'], attr.text,
-                                ext_map[attr.attrib['name']]))
+                    func = None
+                    if attr.attrib['name'] in data_maps[ext.attrib['name']]:
+                        func = data_maps[ext.attrib['name']]\
+                               [attr.attrib['name']][-1]
+                    else:
+                        for dmap in data_maps[ext.attrib['name']].keys():
+                            if data_maps[ext.attrib['name']][dmap][0] == \
+                               attr.attrib['name']:
+                                func = data_maps[ext.attrib['name']][dmap][-1]
+                                break
+                    ret.append((attr.attrib['name'], attr.text, func))
         return ret
 
     def delete(self, gp_ext_name, attribute):
-- 
2.13.6


From 671b7ac6fad29ef48f8edba46d1e080b59c119d6 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Mon, 8 Jan 2018 09:19:13 -0700
Subject: [PATCH 2/7] gpo: Continue parsing GPOs even if one fails

Signed-off-by: David Mulder <dmulder at suse.com>
---
 source4/scripting/bin/samba_gpoupdate | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
index f593e473fb1..e74b2c896a1 100755
--- a/source4/scripting/bin/samba_gpoupdate
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -74,13 +74,15 @@ def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
             gp_db.state(GPOSTATE.ENFORCE)
         gp_db.set_guid(guid)
         store.start()
-        try:
-            for ext in gp_extensions:
+        for ext in gp_extensions:
+            try:
                 ext.parse(ext.list(path), test_ldb, conn, gp_db, lp)
-        except:
-            logger.error('Failed to parse gpo %s' % guid)
-            store.cancel()
-            continue
+            except Exception as e:
+                logger.error('Failed to parse gpo %s for extension %s' % \
+                    (guid, str(ext)))
+                logger.error('Message was: ' + str(e))
+                store.cancel()
+                continue
         store.store(guid, '%i' % version)
         store.commit()
 
-- 
2.13.6


From 811bb32500e9c5e45061ed4fb20d280e9811f1cb Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Mon, 8 Jan 2018 07:17:29 -0700
Subject: [PATCH 3/7] gpo: Read GPO versions from a cache, not sysvol

Signed-off-by: David Mulder <dmulder at suse.com>
---
 source4/scripting/bin/samba_gpoupdate | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
index e74b2c896a1..b8162cf17e8 100755
--- a/source4/scripting/bin/samba_gpoupdate
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -50,6 +50,15 @@ def get_gpo_list(dc_hostname, creds, lp):
         gpos = ads.get_gpo_list(creds.get_username())
     return gpos
 
+def gpo_version(conn, path):
+    local_path = os.path.join(lp.get('cache directory'), 'gpt', path, 'GPT.INI')
+    if not os.path.exists(os.path.dirname(local_path)):
+        os.makedirs(os.path.dirname(local_path))
+    data = conn.loadfile(os.path.join(path, 'GPT.INI').replace('/', '\\'))
+    encoding = chardet.detect(data)
+    open(local_path, 'w').write(data.decode(encoding['encoding']))
+    return int(gpo.gpo_get_sysvol_gpt_version(os.path.dirname(local_path))[1])
+
 def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
     gp_db = store.get_gplog(creds.get_username())
     dc_hostname = get_dc_hostname(creds, lp)
@@ -65,8 +74,7 @@ def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
         if guid == 'Local Policy':
             continue
         path = os.path.join(lp.get('realm').lower(), 'Policies', guid)
-        local_path = os.path.join(lp.get("path", "sysvol"), path)
-        version = int(gpo.gpo_get_sysvol_gpt_version(local_path)[1])
+        version = gpo_version(conn, path)
         if version != store.get_int(guid):
             logger.info('GPO %s has changed' % guid)
             gp_db.state(GPOSTATE.APPLY)
-- 
2.13.6


From 8902fbf64c6e1fa9db3dfef1ee1be65f98fdb383 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Wed, 6 Dec 2017 12:51:22 -0700
Subject: [PATCH 4/7] Revert "gpo: Create the gpo update service"

This reverts commit 5662e49b49f6557c80f216f510f224bbf800f40a.
---
 source4/dsdb/gpo/gpo_update.c | 193 ------------------------------------------
 source4/dsdb/wscript_build    |   9 --
 2 files changed, 202 deletions(-)
 delete mode 100644 source4/dsdb/gpo/gpo_update.c

diff --git a/source4/dsdb/gpo/gpo_update.c b/source4/dsdb/gpo/gpo_update.c
deleted file mode 100644
index 997e97ec14d..00000000000
--- a/source4/dsdb/gpo/gpo_update.c
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
-   Unix SMB/CIFS mplementation.
-   GPO update service
-
-   Copyright (C) Luke Morrison 2013
-
-   Inspired by dns_updates.c written by Andrew Trigell 2009
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/
-
-*/
-
-#include "includes.h"
-#include "dsdb/samdb/samdb.h"
-#include "auth/auth.h"
-#include "smbd/service.h"
-#include "lib/messaging/irpc.h"
-#include "param/param.h"
-#include "system/filesys.h"
-#include "dsdb/common/util.h"
-#include "libcli/composite/composite.h"
-#include "libcli/security/dom_sid.h"
-#include "librpc/gen_ndr/ndr_irpc.h"
-#include "libds/common/roles.h"
-
-struct gpoupdate_service {
-	struct auth_session_info *system_session_info;
-	struct task_server *task;
-
-	/* status for periodic sysvol/GPO scan update - >sysvscan */
-	struct {
-		uint32_t interval;
-		struct tevent_timer *te;
-		struct tevent_req *subreq;
-		NTSTATUS status;
-	} sysvscan;
-};
-
-/*
-Called when the sysvol scan has finished
-*/
-static void gpoupdate_sysvscan_done(struct tevent_req *subreq)
-{
-	struct gpoupdate_service *service = tevent_req_callback_data(subreq,
-								     struct
-								     gpoupdate_service);
-	int ret;
-	int sys_errno;
-
-	service->sysvscan.subreq = NULL;
-
-	ret = samba_runcmd_recv(subreq, &sys_errno);
-	TALLOC_FREE(subreq);
-	if (ret != 0) {
-		service->sysvscan.status =
-		    map_nt_error_from_unix_common(sys_errno);
-	} else {
-		service->sysvscan.status = NT_STATUS_OK;
-	}
-
-	if (!NT_STATUS_IS_OK(service->sysvscan.status)) {
-		DEBUG(0, (__location__ ": Failed GPO update - %s\n",
-			  nt_errstr(service->sysvscan.status)));
-	} else {
-		DEBUG(3, ("Completed GPO update check OK\n"));
-	}
-}
-
-static NTSTATUS gpoupdate_sysvscan_schedule(struct gpoupdate_service *service);
-
-static void gpoupdate_scan_apply(struct gpoupdate_service *service);
-
-static void gpoupdate_sysvscan_handler_te(struct tevent_context *ev,
-					  struct tevent_timer *te,
-					  struct timeval t, void *ptr)
-{
-	struct gpoupdate_service *service =
-	    talloc_get_type(ptr, struct gpoupdate_service);
-
-	gpoupdate_scan_apply(service);
-	gpoupdate_sysvscan_schedule(service);
-}
-
-static NTSTATUS gpoupdate_sysvscan_schedule(struct gpoupdate_service *service)
-{
-	/*
-	 * This is configured, default to 900 sec (15 mins) in
-	 * gpoupdate_task_init via gpoupdate:config interval
-	 */
-	service->sysvscan.te =
-	    tevent_add_timer(service->task->event_ctx, service,
-			     timeval_current_ofs(service->sysvscan.interval, 0),
-			     gpoupdate_sysvscan_handler_te, service);
-	NT_STATUS_HAVE_NO_MEMORY(service->sysvscan.te);
-	return NT_STATUS_OK;
-}
-
-static void gpoupdate_scan_apply(struct gpoupdate_service *service)
-{
-	const char *const *gpo_update_command =
-	    lpcfg_gpo_update_command(service->task->lp_ctx);
-	const char *smbconf = lpcfg_configfile(service->task->lp_ctx);
-	TALLOC_FREE(service->sysvscan.subreq);
-	DEBUG(3, ("Calling GPO update script\n"));
-	service->sysvscan.subreq = samba_runcmd_send(service,
-						     service->task->event_ctx,
-						     timeval_current_ofs(20, 0),
-						     2, 0,
-						     gpo_update_command,
-						     smbconf, NULL);
-	if (service->sysvscan.subreq == NULL) {
-		DEBUG(0,
-		      (__location__
-		       ": samba_runcmd_send() failed with no memory\n"));
-		return;
-	}
-	tevent_req_set_callback(service->sysvscan.subreq,
-				gpoupdate_sysvscan_done, service);
-}
-
-static void gpoupdate_task_init(struct task_server *task)
-{
-	NTSTATUS status;
-	struct gpoupdate_service *service;
-
-	if (lpcfg_server_role(task->lp_ctx) != ROLE_ACTIVE_DIRECTORY_DC) {
-		/* not useful for non-DC */
-		return;
-	}
-
-	task_server_set_title(task, "task[gpoupdate]");
-
-	service = talloc_zero(task, struct gpoupdate_service);
-	if (!service) {
-		task_server_terminate(task,
-				      "gpoupdate_task_init: out of memory",
-				      true);
-		return;
-	}
-	service->task = task;
-	task->private_data = service;
-
-	service->system_session_info = system_session(service->task->lp_ctx);
-	if (!service->system_session_info) {
-		task_server_terminate(task,
-				      "gpoupdate: Failed to obtain server "
-				      "credentials\n",
-				      true);
-		return;
-	}
-
-	service->sysvscan.interval = lpcfg_parm_int(task->lp_ctx, NULL,
-						    "gpoupdate",
-						    "config interval",
-						    900); /* in seconds */
-	status = gpoupdate_sysvscan_schedule(service);
-	if (!NT_STATUS_IS_OK(status)) {
-		task_server_terminate(task,
-				      talloc_asprintf(task,
-						      "gpoupdate: Failed to update "
-						      "sysvol scan schedule: %s\n",
-						      nt_errstr(status)),
-				      true);
-		return;
-	}
-}
-
-NTSTATUS server_service_gpoupdate_init(TALLOC_CTX *ctx);
-
-/*
-  register ourselves as a available server
-*/
-NTSTATUS server_service_gpoupdate_init(TALLOC_CTX *ctx)
-{
-	struct service_details details = {
-		.inhibit_fork_on_accept = true,
-		.inhibit_pre_fork = true
-	};
-	return register_server_service(ctx, "gpoupdate",
-				       gpoupdate_task_init,
-				       &details);
-}
diff --git a/source4/dsdb/wscript_build b/source4/dsdb/wscript_build
index 328497c8590..29c6f0e4a70 100644
--- a/source4/dsdb/wscript_build
+++ b/source4/dsdb/wscript_build
@@ -62,15 +62,6 @@ bld.SAMBA_MODULE('service_dns_update',
 	enabled=bld.AD_DC_BUILD_IS_ENABLED()
 	)
 
-bld.SAMBA_MODULE('service_gpo_update',
-	source='gpo/gpo_update.c',
-	subsystem='service',
-	init_function='server_service_gpoupdate_init',
-	deps='samdb UTIL_RUNCMD samba-util ldb samdb-common samba-errors talloc auth_system_session samba-hostconfig',
-	internal_module=False,
-	enabled=bld.AD_DC_BUILD_IS_ENABLED()
-	)
-
 bld.SAMBA_PYTHON('python_dsdb',
 	source='pydsdb.c',
 	# the dependency on dcerpc here is because gensec
-- 
2.13.6


From b75eb023e65c36f55b7aa097f622cf959c78860e Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Tue, 21 Nov 2017 03:44:12 -0700
Subject: [PATCH 5/7] gpo: Add the winbind call to gpupdate

Signed-off-by: David Mulder <dmulder at suse.com>
---
 docs-xml/smbdotconf/domain/gpoupdatecommand.xml    |  11 +-
 docs-xml/smbdotconf/winbind/applygrouppolicies.xml |  19 ++++
 lib/param/loadparm.c                               |   1 +
 python/samba/gpclass.py                            |   9 +-
 selftest/target/Samba4.pm                          |   2 +-
 source3/param/loadparm.c                           |   2 +
 source3/winbindd/winbindd.c                        |   2 +
 source3/winbindd/winbindd_gpupdate.c               | 116 +++++++++++++++++++++
 source3/winbindd/winbindd_proto.h                  |   3 +
 source3/winbindd/wscript_build                     |   3 +-
 source4/scripting/bin/samba_gpoupdate              |  21 +++-
 source4/scripting/bin/wscript_build                |   2 +-
 source4/scripting/wscript_build                    |   7 +-
 13 files changed, 178 insertions(+), 20 deletions(-)
 create mode 100644 docs-xml/smbdotconf/winbind/applygrouppolicies.xml
 create mode 100644 source3/winbindd/winbindd_gpupdate.c

diff --git a/docs-xml/smbdotconf/domain/gpoupdatecommand.xml b/docs-xml/smbdotconf/domain/gpoupdatecommand.xml
index 22a42163f27..77b596051b7 100644
--- a/docs-xml/smbdotconf/domain/gpoupdatecommand.xml
+++ b/docs-xml/smbdotconf/domain/gpoupdatecommand.xml
@@ -5,10 +5,13 @@
                  xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
 <description>
 	<para>This option sets the command that is called to apply GPO policies.
-        The samba_gpoupdate script applies System Access and Kerberos Policies.
-        System Access policies set minPwdAge, maxPwdAge, minPwdLength, and
-        pwdProperties in the samdb. Kerberos Policies set kdc:service ticket lifetime,
-        kdc:user ticket lifetime, and kdc:renewal lifetime in smb.conf.
+        The samba_gpoupdate script applies System Access and Kerberos Policies
+	to the KDC, or Environment Variable policies to client machines. System
+	Access policies set minPwdAge, maxPwdAge, minPwdLength, and
+	pwdProperties in the samdb. Kerberos Policies set kdc:service ticket
+	lifetime, kdc:user ticket lifetime, and kdc:renewal lifetime in
+	smb.conf. Environment Variable policies apply environment variables,
+	such as PATH, to /etc/profile.
 	</para>
 </description>
 
diff --git a/docs-xml/smbdotconf/winbind/applygrouppolicies.xml b/docs-xml/smbdotconf/winbind/applygrouppolicies.xml
new file mode 100644
index 00000000000..67baa0d1426
--- /dev/null
+++ b/docs-xml/smbdotconf/winbind/applygrouppolicies.xml
@@ -0,0 +1,19 @@
+<samba:parameter name="apply group policies"
+                 context="G"
+                 type="boolean"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+
+	<para>This option controls whether winbind will execute the gpupdate
+	command defined in <smbconfoption name="gpo update command"/> on the
+	Group Policy update interval. The Group	Policy update interval is
+	defined as every 90 minutes, plus a random offset between 0 and	30
+	minutes. This applies Group Policy Machine polices to the client or
+	KDC and machine policies to a server.
+	</para>
+
+</description>
+
+<value type="default">no</value>
+<value type="example">yes</value>
+</samba:parameter>
diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c
index ddb45073b1b..ea44dbf1d5a 100644
--- a/lib/param/loadparm.c
+++ b/lib/param/loadparm.c
@@ -2733,6 +2733,7 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx)
 	lpcfg_do_global_parameter(lp_ctx, "winbindd socket directory", dyn_WINBINDD_SOCKET_DIR);
 	lpcfg_do_global_parameter(lp_ctx, "ntp signd socket directory", dyn_NTP_SIGND_SOCKET_DIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "gpo update command", "%s/samba_gpoupdate", dyn_SCRIPTSBINDIR);
+	lpcfg_do_global_parameter_var(lp_ctx, "apply group policies", "False");
 	lpcfg_do_global_parameter_var(lp_ctx, "dns update command", "%s/samba_dnsupdate", dyn_SCRIPTSBINDIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "spn update command", "%s/samba_spnupdate", dyn_SCRIPTSBINDIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "samba kcc command",
diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index ca34120d513..33c9001cb6d 100644
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -19,19 +19,12 @@ import sys
 import os
 import tdb
 sys.path.insert(0, "bin/python")
-import samba.gpo as gpo
-import optparse
-import ldb
-from samba.auth import system_session
-import samba.getopt as options
-from samba.samdb import SamDB
-from samba.netcmd import gpo as gpo_user
-import codecs
 from samba import NTSTATUSError
 from ConfigParser import ConfigParser
 from StringIO import StringIO
 from abc import ABCMeta, abstractmethod
 import xml.etree.ElementTree as etree
+import re
 
 try:
     from enum import Enum
diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm
index e2e78abd0bb..3477465bb2d 100755
--- a/selftest/target/Samba4.pm
+++ b/selftest/target/Samba4.pm
@@ -616,7 +616,7 @@ sub provision_raw_step1($$)
 	rndc command = true
 	dns update command = $ctx->{samba_dnsupdate}
 	spn update command = $ENV{SRCDIR_ABS}/source4/scripting/bin/samba_spnupdate -s $ctx->{smb_conf}
-	gpo update command = $ENV{SRCDIR_ABS}/source4/scripting/bin/samba_gpoupdate -s $ctx->{smb_conf} -H $ctx->{privatedir}/sam.ldb
+	gpo update command = $ENV{SRCDIR_ABS}/source4/scripting/bin/samba_gpoupdate -s $ctx->{smb_conf} -H $ctx->{privatedir}/sam.ldb --machine
 	dreplsrv:periodic_startup_interval = 0
 	dsdb:schema update allowed = yes
 
diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c
index a2fcc4246c9..878957b96f7 100644
--- a/source3/param/loadparm.c
+++ b/source3/param/loadparm.c
@@ -924,6 +924,8 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals)
 	Globals.gpo_update_command = str_list_make_v3_const(NULL, s, NULL);
 	TALLOC_FREE(s);
 
+	Globals.apply_group_policies = false;
+
 	s = talloc_asprintf(talloc_tos(), "%s/samba_spnupdate", get_dyn_SCRIPTSBINDIR());
 	if (s == NULL) {
 		smb_panic("init_globals: ENOMEM");
diff --git a/source3/winbindd/winbindd.c b/source3/winbindd/winbindd.c
index fc7c1691873..c06e29265ec 100644
--- a/source3/winbindd/winbindd.c
+++ b/source3/winbindd/winbindd.c
@@ -1773,6 +1773,8 @@ int main(int argc, const char **argv)
 		daemon_ready("winbindd");
 	}
 
+	gpupdate_init();
+
 	/* Loop waiting for requests */
 	while (1) {
 		frame = talloc_stackframe();
diff --git a/source3/winbindd/winbindd_gpupdate.c b/source3/winbindd/winbindd_gpupdate.c
new file mode 100644
index 00000000000..48ebb5501aa
--- /dev/null
+++ b/source3/winbindd/winbindd_gpupdate.c
@@ -0,0 +1,116 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Group Policy Update event for winbindd
+ * Copyright (C) David Mulder 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "includes.h"
+#include "param/param.h"
+#include "param/loadparm.h"
+#include "winbindd.h"
+
+/*
+ * gpupdate_interval()
+ * return   Random integer between 5400 and 7200, the group policy update
+ *          interval in seconds
+ *
+ * Group Policy should be updated every 90 minutes in the background,
+ * with a random offset between 0 and 30 minutes. This ensures mutiple
+ * clients will not update at the same time.
+ */
+#define GPUPDATE_INTERVAL       (90*60)
+#define GPUPDATE_RAND_OFFSET    (30*60)
+static uint32_t gpupdate_interval(void)
+{
+	int rand_int_offset = rand() % GPUPDATE_RAND_OFFSET;
+	return GPUPDATE_INTERVAL+rand_int_offset;
+}
+
+struct gpupdate_state {
+	TALLOC_CTX *ctx;
+	struct loadparm_context *lp_ctx;
+};
+
+static void gpupdate_callback(struct tevent_context *ev,
+			      struct tevent_timer *tim,
+			      struct timeval current_time,
+			      void *private_data)
+{
+	struct tevent_timer *time_event;
+	struct timeval schedule;
+	struct tevent_req *req = NULL;
+	struct gpupdate_state *data =
+		talloc_get_type_abort(private_data, struct gpupdate_state);
+	const char *const *gpupdate_cmd =
+		lpcfg_gpo_update_command(data->lp_ctx);
+	const char *smbconf = lp_default_path();
+
+	/* Execute gpupdate */
+	req = samba_runcmd_send(data->ctx, ev, timeval_zero(), 2, 0,
+				gpupdate_cmd,
+				"-s",
+				smbconf,
+				"--machine",
+				"--machine-pass",
+				NULL);
+	if (req == NULL) {
+		DEBUG(0, ("Failed to execute the gpupdate command\n"));
+		return;
+	}
+
+	/* Schedule the next event */
+	schedule = tevent_timeval_current_ofs(gpupdate_interval(), 0);
+	time_event = tevent_add_timer(ev, data->ctx, schedule,
+				      gpupdate_callback, data);
+	if (time_event == NULL) {
+		DEBUG(0, ("Failed scheduling the next gpupdate event\n"));
+	}
+}
+
+void gpupdate_init(void)
+{
+	struct tevent_timer *time_event;
+	struct timeval schedule;
+	TALLOC_CTX * ctx = talloc_new(server_event_context());
+	struct gpupdate_state *data = talloc(ctx, struct gpupdate_state);
+	struct loadparm_context *lp_ctx =
+		loadparm_init_s3(NULL, loadparm_s3_helpers());
+
+	/*
+	 * Check if gpupdate is enabled for winbind, if not
+	 * return without scheduling any events.
+	 */
+	if (!lpcfg_apply_group_policies(lp_ctx)) {
+		return;
+	}
+
+	/*
+	 * Execute the first event immediately, future events
+	 * will execute on the gpupdate interval, which is every
+	 * 90 to 120 minutes (at random).
+	 */
+	schedule = tevent_timeval_current_ofs(0, 0);
+	data->ctx = ctx;
+	data->lp_ctx = lp_ctx;
+	if (data->lp_ctx == NULL) {
+		smb_panic("Could not load smb.conf\n");
+	}
+	time_event = tevent_add_timer(server_event_context(), data->ctx,
+				      schedule, gpupdate_callback, data);
+	if (time_event == NULL) {
+		DEBUG(0, ("Failed scheduling the gpupdate event\n"));
+	}
+}
+
diff --git a/source3/winbindd/winbindd_proto.h b/source3/winbindd/winbindd_proto.h
index cf01337aaad..0e59a51ba54 100644
--- a/source3/winbindd/winbindd_proto.h
+++ b/source3/winbindd/winbindd_proto.h
@@ -932,4 +932,7 @@ NTSTATUS wb_irpc_register(void);
 /* The following definitions come from winbindd/winbindd_reconnect.c  */
 bool reconnect_need_retry(NTSTATUS status, struct winbindd_domain *domain);
 
+/* The following definitions come from winbindd/winbindd_gpupdate.c  */
+void gpupdate_init(void);
+
 #endif /*  _WINBINDD_PROTO_H_  */
diff --git a/source3/winbindd/wscript_build b/source3/winbindd/wscript_build
index 51264e9e365..48250ea565e 100644
--- a/source3/winbindd/wscript_build
+++ b/source3/winbindd/wscript_build
@@ -254,7 +254,8 @@ bld.SAMBA3_BINARY('winbindd',
                  winbindd_pam_logoff.c
                  winbindd_pam_chauthtok.c
                  winbindd_pam_auth_crap.c
-                 winbindd_pam_chng_pswd_auth_crap.c''',
+                 winbindd_pam_chng_pswd_auth_crap.c
+                 winbindd_gpupdate.c''',
                  deps='''
                  talloc
                  tevent
diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
index b8162cf17e8..d6c0ecc5db5 100755
--- a/source4/scripting/bin/samba_gpoupdate
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -29,11 +29,18 @@ sys.path.insert(0, "bin/python")
 
 import optparse
 from samba import getopt as options
+from samba.auth import system_session
+try:
+    from samba.samdb import SamDB
+except:
+    SamDB = None
 from samba.gpclass import *
 from samba.net import Net
 from samba.dcerpc import nbt
 from samba import smb
+import samba.gpo as gpo
 import logging
+import chardet
 
 ''' Fetch the hostname of a writable DC '''
 def get_dc_hostname(creds, lp):
@@ -125,6 +132,8 @@ if __name__ == "__main__":
     parser.add_option('-H', '--url', dest='url', help='URL for the samdb')
     parser.add_option('-X', '--unapply', help='Unapply Group Policy',
                       action='store_true')
+    parser.add_option('-M', '--machine', help='Apply machine policy',
+                      action='store_true', default=False)
     parser.add_option_group(credopts)
 
     # Set the options and the arguments
@@ -158,10 +167,18 @@ if __name__ == "__main__":
     cache_dir = lp.get('cache directory')
     store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb'))
 
-    gp_extensions = [gp_sec_ext(logger)]
+    gp_extensions = []
+    if opts.machine:
+        if lp.get('server role') == 'active directory domain controller':
+            gp_extensions.append(gp_sec_ext(logger))
+    else:
+        pass # User extensions
 
     # Get a live instance of Samba
-    test_ldb = SamDB(url, session_info=session, credentials=creds, lp=lp)
+    if SamDB:
+        test_ldb = SamDB(url, session_info=session, credentials=creds, lp=lp)
+    else:
+        test_ldb = None
 
     if not opts.unapply:
         apply_gp(lp, creds, test_ldb, logger, store, gp_extensions)
diff --git a/source4/scripting/bin/wscript_build b/source4/scripting/bin/wscript_build
index 737f1bce411..043442b3407 100644
--- a/source4/scripting/bin/wscript_build
+++ b/source4/scripting/bin/wscript_build
@@ -7,6 +7,6 @@ if bld.CONFIG_SET('AD_DC_BUILD_IS_ENABLED'):
                    'samba_kcc',
                    'samba_upgradeprovision',
                    'samba_upgradedns',
-                   'samba_gpoupdate',
                    'gen_output.py']:
         bld.SAMBA_SCRIPT(script, pattern=script, installdir='.')
+bld.SAMBA_SCRIPT('samba_gpoupdate', pattern='samba_gpoupdate', installdir='.')
diff --git a/source4/scripting/wscript_build b/source4/scripting/wscript_build
index eb11c4202fe..2f53cce12b7 100644
--- a/source4/scripting/wscript_build
+++ b/source4/scripting/wscript_build
@@ -2,10 +2,11 @@
 
 from samba_utils import MODE_755
 
-sbin_files = None
+sbin_files = ''
 if bld.CONFIG_SET('AD_DC_BUILD_IS_ENABLED'):
-    sbin_files = 'bin/samba_dnsupdate bin/samba_spnupdate bin/samba_upgradedns bin/samba_kcc bin/samba_gpoupdate'
-    man_files = 'man/samba_gpoupdate.8'
+    sbin_files = 'bin/samba_dnsupdate bin/samba_spnupdate bin/samba_upgradedns bin/samba_kcc '
+sbin_files += 'bin/samba_gpoupdate'
+man_files = 'man/samba_gpoupdate.8'
 
 if sbin_files:
     bld.INSTALL_FILES('${SBINDIR}',
-- 
2.13.6


From 6a4a328dcd7e19039f07ea3c8394e61105a6644d Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Fri, 8 Dec 2017 14:12:02 -0700
Subject: [PATCH 6/7] gpo: enable group policy apply from winbind as default

Signed-off-by: David Mulder <dmulder at suse.com>
---
 docs-xml/smbdotconf/winbind/applygrouppolicies.xml | 4 ++--
 lib/param/loadparm.c                               | 2 +-
 source3/param/loadparm.c                           | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/docs-xml/smbdotconf/winbind/applygrouppolicies.xml b/docs-xml/smbdotconf/winbind/applygrouppolicies.xml
index 67baa0d1426..750d2d74ae1 100644
--- a/docs-xml/smbdotconf/winbind/applygrouppolicies.xml
+++ b/docs-xml/smbdotconf/winbind/applygrouppolicies.xml
@@ -14,6 +14,6 @@
 
 </description>
 
-<value type="default">no</value>
-<value type="example">yes</value>
+<value type="default">yes</value>
+<value type="example">no</value>
 </samba:parameter>
diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c
index ea44dbf1d5a..234207a054a 100644
--- a/lib/param/loadparm.c
+++ b/lib/param/loadparm.c
@@ -2733,7 +2733,7 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx)
 	lpcfg_do_global_parameter(lp_ctx, "winbindd socket directory", dyn_WINBINDD_SOCKET_DIR);
 	lpcfg_do_global_parameter(lp_ctx, "ntp signd socket directory", dyn_NTP_SIGND_SOCKET_DIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "gpo update command", "%s/samba_gpoupdate", dyn_SCRIPTSBINDIR);
-	lpcfg_do_global_parameter_var(lp_ctx, "apply group policies", "False");
+	lpcfg_do_global_parameter_var(lp_ctx, "apply group policies", "True");
 	lpcfg_do_global_parameter_var(lp_ctx, "dns update command", "%s/samba_dnsupdate", dyn_SCRIPTSBINDIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "spn update command", "%s/samba_spnupdate", dyn_SCRIPTSBINDIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "samba kcc command",
diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c
index 878957b96f7..d3e12c523da 100644
--- a/source3/param/loadparm.c
+++ b/source3/param/loadparm.c
@@ -924,7 +924,7 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals)
 	Globals.gpo_update_command = str_list_make_v3_const(NULL, s, NULL);
 	TALLOC_FREE(s);
 
-	Globals.apply_group_policies = false;
+	Globals.apply_group_policies = true;
 
 	s = talloc_asprintf(talloc_tos(), "%s/samba_spnupdate", get_dyn_SCRIPTSBINDIR());
 	if (s == NULL) {
-- 
2.13.6


From 805d12991b209652f9353ff4fac4c9d4355e7786 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Mon, 8 Jan 2018 07:39:49 -0700
Subject: [PATCH 7/7] gpo: provide a means for disabling gpo exts

Signed-off-by: David Mulder <dmulder at suse.com>
---
 python/samba/gp_sec_ext.py            | 140 ++++++++++++++++++
 python/samba/gpclass.py               | 209 +++++++--------------------
 source4/scripting/bin/samba_gpoupdate |   4 +-
 source4/torture/gpo/apply.c           | 258 ++++++++++++++++++++++++++++------
 4 files changed, 402 insertions(+), 209 deletions(-)
 create mode 100644 python/samba/gp_sec_ext.py

diff --git a/python/samba/gp_sec_ext.py b/python/samba/gp_sec_ext.py
new file mode 100644
index 00000000000..b32793e1043
--- /dev/null
+++ b/python/samba/gp_sec_ext.py
@@ -0,0 +1,140 @@
+import os.path
+from gpclass import file_to, gp_inf_ext
+
+class inf_to_kdc_tdb(file_to):
+    def mins_to_hours(self):
+        return '%d' % (int(self.val)/60)
+
+    def days_to_hours(self):
+        return '%d' % (int(self.val)*24)
+
+    def set_kdc_tdb(self, val):
+        old_val = self.gp_db.gpostore.get(self.attribute)
+        self.logger.info('%s was changed from %s to %s' % (self.attribute,
+                                                           old_val, val))
+        if val is not None:
+            self.gp_db.gpostore.store(self.attribute, val)
+            self.gp_db.store(str(self), self.attribute, old_val)
+        else:
+            self.gp_db.gpostore.delete(self.attribute)
+            self.gp_db.delete(str(self), self.attribute)
+
+    def mapper(self):
+        return { 'kdc:user_ticket_lifetime': (self.set_kdc_tdb, self.explicit),
+                 'kdc:service_ticket_lifetime': (self.set_kdc_tdb,
+                                                 self.mins_to_hours),
+                 'kdc:renewal_lifetime': (self.set_kdc_tdb,
+                                          self.days_to_hours),
+               }
+
+    def __str__(self):
+        return 'Kerberos Policy'
+
+class inf_to_ldb(file_to):
+    '''This class takes the .inf file parameter (essentially a GPO file mapped
+    to a GUID), hashmaps it to the Samba parameter, which then uses an ldb
+    object to update the parameter to Samba4. Not registry oriented whatsoever.
+    '''
+
+    def ch_minPwdAge(self, val):
+        old_val = self.ldb.get_minPwdAge()
+        self.logger.info('KDC Minimum Password age was changed from %s to %s' \
+                         % (old_val, val))
+        self.gp_db.store(str(self), self.attribute, old_val)
+        self.ldb.set_minPwdAge(val)
+
+    def ch_maxPwdAge(self, val):
+        old_val = self.ldb.get_maxPwdAge()
+        self.logger.info('KDC Maximum Password age was changed from %s to %s' \
+                         % (old_val, val))
+        self.gp_db.store(str(self), self.attribute, old_val)
+        self.ldb.set_maxPwdAge(val)
+
+    def ch_minPwdLength(self, val):
+        old_val = self.ldb.get_minPwdLength()
+        self.logger.info(
+            'KDC Minimum Password length was changed from %s to %s' \
+             % (old_val, val))
+        self.gp_db.store(str(self), self.attribute, old_val)
+        self.ldb.set_minPwdLength(val)
+
+    def ch_pwdProperties(self, val):
+        old_val = self.ldb.get_pwdProperties()
+        self.logger.info('KDC Password Properties were changed from %s to %s' \
+                         % (old_val, val))
+        self.gp_db.store(str(self), self.attribute, old_val)
+        self.ldb.set_pwdProperties(val)
+
+    def days2rel_nttime(self):
+        seconds = 60
+        minutes = 60
+        hours = 24
+        sam_add = 10000000
+        val = (self.val)
+        val = int(val)
+        return  str(-(val * seconds * minutes * hours * sam_add))
+
+    def mapper(self):
+        '''ldap value : samba setter'''
+        return { "minPwdAge" : (self.ch_minPwdAge, self.days2rel_nttime),
+                 "maxPwdAge" : (self.ch_maxPwdAge, self.days2rel_nttime),
+                 # Could be none, but I like the method assignment in
+                 # update_samba
+                 "minPwdLength" : (self.ch_minPwdLength, self.explicit),
+                 "pwdProperties" : (self.ch_pwdProperties, self.explicit),
+
+               }
+
+    def __str__(self):
+        return 'System Access'
+
+class gp_sec_ext(gp_inf_ext):
+    '''This class does the following two things:
+        1) Identifies the GPO if it has a certain kind of filepath,
+        2) Finally parses it.
+    '''
+
+    count = 0
+
+    def __str__(self):
+        return "Security GPO extension"
+
+    def list(self, rootpath):
+        return os.path.join(rootpath,
+            "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
+
+    def listmachpol(self, rootpath):
+        return os.path.join(rootpath, "Machine/Registry.pol")
+
+    def listuserpol(self, rootpath):
+        return os.path.join(rootpath, "User/Registry.pol")
+
+    def apply_map(self):
+        return {"System Access": {"MinimumPasswordAge": ("minPwdAge",
+                                                         inf_to_ldb),
+                                  "MaximumPasswordAge": ("maxPwdAge",
+                                                         inf_to_ldb),
+                                  "MinimumPasswordLength": ("minPwdLength",
+                                                            inf_to_ldb),
+                                  "PasswordComplexity": ("pwdProperties",
+                                                         inf_to_ldb),
+                                 },
+                "Kerberos Policy": {"MaxTicketAge": (
+                                        "kdc:user_ticket_lifetime",
+                                        inf_to_kdc_tdb
+                                    ),
+                                    "MaxServiceAge": (
+                                        "kdc:service_ticket_lifetime",
+                                        inf_to_kdc_tdb
+                                    ),
+                                    "MaxRenewAge": (
+                                        "kdc:renewal_lifetime",
+                                        inf_to_kdc_tdb
+                                    ),
+                                   }
+               }
+
+    @staticmethod
+    def disabled_file():
+        return os.path.splitext(os.path.abspath(__file__))[0] + '.py.disabled'
+
diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index 33c9001cb6d..2ec00d5f69b 100644
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -286,6 +286,9 @@ class GPOStorage:
 class gp_ext(object):
     __metaclass__ = ABCMeta
 
+    def __init__(self, logger):
+        self.logger = logger
+
     @abstractmethod
     def list(self, rootpath):
         pass
@@ -295,14 +298,45 @@ class gp_ext(object):
         pass
 
     @abstractmethod
-    def parse(self, afile, ldb, conn, gp_db, lp):
+    def read(self, policy):
         pass
 
+    @classmethod
+    def enabled(cls):
+        return not os.path.exists(cls.disabled_file())
+
+    def parse(self, afile, ldb, conn, gp_db, lp):
+        self.ldb = ldb
+        self.gp_db = gp_db
+        self.lp = lp
+
+        # Fixing the bug where only some Linux Boxes capitalize MACHINE
+        try:
+            blist = afile.split('/')
+            idx = afile.lower().split('/').index('machine')
+            for case in [
+                            blist[idx].upper(),
+                            blist[idx].capitalize(),
+                            blist[idx].lower()
+                        ]:
+                bfile = '/'.join(blist[:idx]) + '/' + case + '/' + \
+                    '/'.join(blist[idx+1:])
+                try:
+                    return self.read(conn.loadfile(bfile.replace('/', '\\')))
+                except NTSTATUSError:
+                    continue
+        except ValueError:
+            try:
+                return self.read(conn.loadfile(afile.replace('/', '\\')))
+            except Exception as e:
+                self.logger.error(str(e))
+                return None
+
     @abstractmethod
     def __str__(self):
         pass
 
-class inf_to():
+class file_to():
     __metaclass__ = ABCMeta
 
     def __init__(self, logger, ldb, gp_db, lp, attribute, val):
@@ -317,7 +351,7 @@ class inf_to():
         return self.val
 
     def update_samba(self):
-        (upd_sam, value) = self.mapper().get(self.attribute)
+        (upd_sam, value) = self.mapper()[self.attribute]
         upd_sam(value())
 
     @abstractmethod
@@ -328,148 +362,19 @@ class inf_to():
     def __str__(self):
         pass
 
-class inf_to_kdc_tdb(inf_to):
-    def mins_to_hours(self):
-        return '%d' % (int(self.val)/60)
-
-    def days_to_hours(self):
-        return '%d' % (int(self.val)*24)
-
-    def set_kdc_tdb(self, val):
-        old_val = self.gp_db.gpostore.get(self.attribute)
-        self.logger.info('%s was changed from %s to %s' % (self.attribute,
-                                                           old_val, val))
-        if val is not None:
-            self.gp_db.gpostore.store(self.attribute, val)
-            self.gp_db.store(str(self), self.attribute, old_val)
-        else:
-            self.gp_db.gpostore.delete(self.attribute)
-            self.gp_db.delete(str(self), self.attribute)
-
-    def mapper(self):
-        return { 'kdc:user_ticket_lifetime': (self.set_kdc_tdb, self.explicit),
-                 'kdc:service_ticket_lifetime': (self.set_kdc_tdb,
-                                                 self.mins_to_hours),
-                 'kdc:renewal_lifetime': (self.set_kdc_tdb,
-                                          self.days_to_hours),
-               }
-
-    def __str__(self):
-        return 'Kerberos Policy'
-
-class inf_to_ldb(inf_to):
-    '''This class takes the .inf file parameter (essentially a GPO file mapped
-    to a GUID), hashmaps it to the Samba parameter, which then uses an ldb
-    object to update the parameter to Samba4. Not registry oriented whatsoever.
-    '''
-
-    def ch_minPwdAge(self, val):
-        old_val = self.ldb.get_minPwdAge()
-        self.logger.info('KDC Minimum Password age was changed from %s to %s' \
-                         % (old_val, val))
-        self.gp_db.store(str(self), self.attribute, old_val)
-        self.ldb.set_minPwdAge(val)
-
-    def ch_maxPwdAge(self, val):
-        old_val = self.ldb.get_maxPwdAge()
-        self.logger.info('KDC Maximum Password age was changed from %s to %s' \
-                         % (old_val, val))
-        self.gp_db.store(str(self), self.attribute, old_val)
-        self.ldb.set_maxPwdAge(val)
-
-    def ch_minPwdLength(self, val):
-        old_val = self.ldb.get_minPwdLength()
-        self.logger.info(
-            'KDC Minimum Password length was changed from %s to %s' \
-             % (old_val, val))
-        self.gp_db.store(str(self), self.attribute, old_val)
-        self.ldb.set_minPwdLength(val)
-
-    def ch_pwdProperties(self, val):
-        old_val = self.ldb.get_pwdProperties()
-        self.logger.info('KDC Password Properties were changed from %s to %s' \
-                         % (old_val, val))
-        self.gp_db.store(str(self), self.attribute, old_val)
-        self.ldb.set_pwdProperties(val)
-
-    def days2rel_nttime(self):
-        seconds = 60
-        minutes = 60
-        hours = 24
-        sam_add = 10000000
-        val = (self.val)
-        val = int(val)
-        return  str(-(val * seconds * minutes * hours * sam_add))
-
-    def mapper(self):
-        '''ldap value : samba setter'''
-        return { "minPwdAge" : (self.ch_minPwdAge, self.days2rel_nttime),
-                 "maxPwdAge" : (self.ch_maxPwdAge, self.days2rel_nttime),
-                 # Could be none, but I like the method assignment in
-                 # update_samba
-                 "minPwdLength" : (self.ch_minPwdLength, self.explicit),
-                 "pwdProperties" : (self.ch_pwdProperties, self.explicit),
-
-               }
-
-    def __str__(self):
-        return 'System Access'
-
-
-class gp_sec_ext(gp_ext):
-    '''This class does the following two things:
-        1) Identifies the GPO if it has a certain kind of filepath,
-        2) Finally parses it.
-    '''
-
-    count = 0
-
-    def __init__(self, logger):
-        self.logger = logger
-
-    def __str__(self):
-        return "Security GPO extension"
-
+class gp_inf_ext(gp_ext):
+    @abstractmethod
     def list(self, rootpath):
-        return os.path.join(rootpath,
-                            "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
-
-    def listmachpol(self, rootpath):
-        return os.path.join(rootpath, "Machine/Registry.pol")
-
-    def listuserpol(self, rootpath):
-        return os.path.join(rootpath, "User/Registry.pol")
+        pass
 
+    @abstractmethod
     def apply_map(self):
-        return {"System Access": {"MinimumPasswordAge": ("minPwdAge",
-                                                         inf_to_ldb),
-                                  "MaximumPasswordAge": ("maxPwdAge",
-                                                         inf_to_ldb),
-                                  "MinimumPasswordLength": ("minPwdLength",
-                                                            inf_to_ldb),
-                                  "PasswordComplexity": ("pwdProperties",
-                                                         inf_to_ldb),
-                                 },
-                "Kerberos Policy": {"MaxTicketAge": (
-                                        "kdc:user_ticket_lifetime",
-                                        inf_to_kdc_tdb
-                                    ),
-                                    "MaxServiceAge": (
-                                        "kdc:service_ticket_lifetime",
-                                        inf_to_kdc_tdb
-                                    ),
-                                    "MaxRenewAge": (
-                                        "kdc:renewal_lifetime",
-                                        inf_to_kdc_tdb
-                                    ),
-                                   }
-               }
-
-    def read_inf(self, path, conn):
+        pass
+
+    def read(self, policy):
         ret = False
         inftable = self.apply_map()
 
-        policy = conn.loadfile(path.replace('/', '\\'))
         current_section = None
 
         # So here we would declare a boolean,
@@ -499,27 +404,7 @@ class gp_sec_ext(gp_ext):
                     self.gp_db.commit()
         return ret
 
-    def parse(self, afile, ldb, conn, gp_db, lp):
-        self.ldb = ldb
-        self.gp_db = gp_db
-        self.lp = lp
-
-        # Fixing the bug where only some Linux Boxes capitalize MACHINE
-        if afile.endswith('inf'):
-            try:
-                blist = afile.split('/')
-                idx = afile.lower().split('/').index('machine')
-                for case in [blist[idx].upper(), blist[idx].capitalize(),
-                             blist[idx].lower()]:
-                    bfile = '/'.join(blist[:idx]) + '/' + case + '/' + \
-                            '/'.join(blist[idx+1:])
-                    try:
-                        return self.read_inf(bfile, conn)
-                    except NTSTATUSError:
-                        continue
-            except ValueError:
-                try:
-                    return self.read_inf(afile, conn)
-                except:
-                    return None
+    @abstractmethod
+    def __str__(self):
+        pass
 
diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
index d6c0ecc5db5..b8b2bb464a0 100755
--- a/source4/scripting/bin/samba_gpoupdate
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -35,6 +35,7 @@ try:
 except:
     SamDB = None
 from samba.gpclass import *
+from samba.gp_sec_ext import gp_sec_ext
 from samba.net import Net
 from samba.dcerpc import nbt
 from samba import smb
@@ -170,7 +171,8 @@ if __name__ == "__main__":
     gp_extensions = []
     if opts.machine:
         if lp.get('server role') == 'active directory domain controller':
-            gp_extensions.append(gp_sec_ext(logger))
+            if gp_sec_ext.enabled():
+                gp_extensions.append(gp_sec_ext(logger))
     else:
         pass # User extensions
 
diff --git a/source4/torture/gpo/apply.c b/source4/torture/gpo/apply.c
index 88da0b1e5fc..dcfdd7a5dbe 100644
--- a/source4/torture/gpo/apply.c
+++ b/source4/torture/gpo/apply.c
@@ -27,13 +27,16 @@
 #include "lib/ldb/include/ldb.h"
 #include "torture/gpo/proto.h"
 #include <unistd.h>
+#include <fcntl.h>
 
 struct torture_suite *gpo_apply_suite(TALLOC_CTX *ctx)
 {
 	struct torture_suite *suite = torture_suite_create(ctx, "apply");
 
-	torture_suite_add_simple_test(suite, "gpo_param_from_gpo",
+	torture_suite_add_simple_test(suite, "gpo_system_access_policies",
 				      torture_gpo_system_access_policies);
+	torture_suite_add_simple_test(suite, "gpo_disable_policies",
+				      torture_gpo_disable_policies);
 
 	suite->description = talloc_strdup(suite, "Group Policy apply tests");
 
@@ -78,10 +81,85 @@ PasswordComplexity = %d\n\
 #define GPTINI "addom.samba.example.com/Policies/"\
 	       "{31B2F340-016D-11D2-945F-00C04FB984F9}/GPT.INI"
 
+static void increment_gpt_ini(TALLOC_CTX *ctx, const char *gpt_file)
+{
+	FILE *fp = NULL;
+	int vers = 0;
+
+	/* Update the version in the GPT.INI */
+	if ( (fp = fopen(gpt_file, "r")) ) {
+		char line[256];
+		while (fgets(line, 256, fp)) {
+			if (strncasecmp(line, "Version=", 8) == 0) {
+				vers = atoi(line+8);
+				break;
+			}
+		}
+		fclose(fp);
+	}
+	if ( (fp = fopen(gpt_file, "w")) ) {
+		char *data = talloc_asprintf(ctx,
+					     "[General]\nVersion=%d\n",
+					     ++vers);
+		fputs(data, fp);
+		fclose(fp);
+	}
+}
+
+static bool exec_gpo_update_command(struct torture_context *tctx)
+{
+	int ret = 0;
+	const char **gpo_update_cmd;
+
+	/* Get the gpo update command */
+	gpo_update_cmd = lpcfg_gpo_update_command(tctx->lp_ctx);
+	torture_assert(tctx, gpo_update_cmd && gpo_update_cmd[0],
+		       "Failed to fetch the gpo update command");
+
+	/* Run the gpo update command */
+	ret = exec_wait(discard_const_p(char *, gpo_update_cmd));
+	torture_assert(tctx, ret == 0,
+		       "Failed to execute the gpo update command");
+
+	return true;
+}
+
+static bool exec_gpo_unapply_command(struct torture_context *tctx)
+{
+	TALLOC_CTX *ctx = talloc_new(tctx);
+	char **gpo_unapply_cmd;
+	const char **gpo_update_cmd;
+	int gpo_update_len = 0;
+	const char **itr;
+	int ret = 0, i;
+
+	/* Get the gpo update command */
+	gpo_update_cmd = lpcfg_gpo_update_command(tctx->lp_ctx);
+	torture_assert(tctx, gpo_update_cmd && gpo_update_cmd[0],
+		       "Failed to fetch the gpo update command");
+
+	for (itr = gpo_update_cmd; *itr != NULL; itr++) {
+		gpo_update_len++;
+	}
+	gpo_unapply_cmd = talloc_array(ctx, char*, gpo_update_len+2);
+	for (i = 0; i < gpo_update_len; i++) {
+		gpo_unapply_cmd[i] = talloc_strdup(gpo_unapply_cmd,
+						   gpo_update_cmd[i]);
+	}
+	gpo_unapply_cmd[i] = talloc_asprintf(gpo_unapply_cmd, "--unapply");
+	gpo_unapply_cmd[i+1] = NULL;
+	ret = exec_wait(gpo_unapply_cmd);
+	torture_assert(tctx, ret == 0,
+		       "Failed to execute the gpo unapply command");
+
+	talloc_free(ctx);
+	return true;
+}
+
 bool torture_gpo_system_access_policies(struct torture_context *tctx)
 {
 	TALLOC_CTX *ctx = talloc_new(tctx);
-	int ret, vers = 0, i;
+	int ret, i;
 	const char *sysvol_path = NULL, *gpo_dir = NULL;
 	const char *gpo_file = NULL, *gpt_file = NULL;
 	struct ldb_context *samdb = NULL;
@@ -94,15 +172,11 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 		NULL
 	};
 	FILE *fp = NULL;
-	const char **gpo_update_cmd;
-	char **gpo_unapply_cmd;
 	int minpwdcases[] = { 0, 1, 998 };
 	int maxpwdcases[] = { 0, 1, 999 };
 	int pwdlencases[] = { 0, 1, 14 };
 	int pwdpropcases[] = { 0, 1, 1 };
 	struct ldb_message *old_message = NULL;
-	const char **itr;
-	int gpo_update_len = 0;
 
 	sysvol_path = lpcfg_path(lpcfg_service(tctx->lp_ctx, "sysvol"),
 				 lpcfg_default_service(tctx->lp_ctx), tctx);
@@ -113,11 +187,6 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 	mkdir_p(gpo_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
 	gpo_file = talloc_asprintf(ctx, "%s/%s", gpo_dir, GPOFILE);
 
-	/* Get the gpo update command */
-	gpo_update_cmd = lpcfg_gpo_update_command(tctx->lp_ctx);
-	torture_assert(tctx, gpo_update_cmd && gpo_update_cmd[0],
-		       "Failed to fetch the gpo update command");
-
 	/* Open and read the samba db and store the initial password settings */
 	samdb = samdb_connect(ctx, tctx->ev, tctx->lp_ctx,
 			      system_session(tctx->lp_ctx), 0);
@@ -139,30 +208,10 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 			fclose(fp);
 		}
 
-		/* Update the version in the GPT.INI */
 		gpt_file = talloc_asprintf(ctx, "%s/%s", sysvol_path, GPTINI);
-		if ( (fp = fopen(gpt_file, "r")) ) {
-			char line[256];
-			while (fgets(line, 256, fp)) {
-				if (strncasecmp(line, "Version=", 8) == 0) {
-					vers = atoi(line+8);
-					break;
-				}
-			}
-			fclose(fp);
-		}
-		if ( (fp = fopen(gpt_file, "w")) ) {
-			char *data = talloc_asprintf(ctx,
-						     "[General]\nVersion=%d\n",
-						     ++vers);
-			fputs(data, fp);
-			fclose(fp);
-		}
+		increment_gpt_ini(ctx, gpt_file);
 
-		/* Run the gpo update command */
-		ret = exec_wait(discard_const_p(char *, gpo_update_cmd));
-		torture_assert(tctx, ret == 0,
-			       "Failed to execute the gpo update command");
+		exec_gpo_update_command(tctx);
 
 		ret = ldb_search(samdb, ctx, &result,
 				 ldb_get_default_basedn(samdb),
@@ -204,19 +253,8 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 	}
 
 	/* Unapply the settings and verify they are removed */
-	for (itr = gpo_update_cmd; *itr != NULL; itr++) {
-		gpo_update_len++;
-	}
-	gpo_unapply_cmd = talloc_array(ctx, char*, gpo_update_len+2);
-	for (i = 0; i < gpo_update_len; i++) {
-		gpo_unapply_cmd[i] = talloc_strdup(gpo_unapply_cmd,
-						   gpo_update_cmd[i]);
-	}
-	gpo_unapply_cmd[i] = talloc_asprintf(gpo_unapply_cmd, "--unapply");
-	gpo_unapply_cmd[i+1] = NULL;
-	ret = exec_wait(gpo_unapply_cmd);
-	torture_assert(tctx, ret == 0,
-		       "Failed to execute the gpo unapply command");
+	exec_gpo_unapply_command(tctx);
+
 	ret = ldb_search(samdb, ctx, &result, ldb_get_default_basedn(samdb),
 			 LDB_SCOPE_BASE, attrs, NULL);
 	torture_assert(tctx, ret == LDB_SUCCESS && result->count == 1,
@@ -265,3 +303,131 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 	talloc_free(ctx);
 	return true;
 }
+
+bool torture_gpo_disable_policies(struct torture_context *tctx)
+{
+	TALLOC_CTX *ctx = talloc_new(tctx);
+	int ret, i;
+	const char *sysvol_path = NULL, *gpo_dir = NULL;
+	const char *gpo_file = NULL, *gpt_file = NULL;
+	struct ldb_context *samdb = NULL;
+	struct ldb_result *result;
+	const char *attrs[] = {
+		"minPwdAge",
+		"maxPwdAge",
+		"minPwdLength",
+		"pwdProperties",
+		NULL
+	};
+	FILE *fp = NULL;
+	int minpwdcases[] = { 0, 1, 998 };
+	int maxpwdcases[] = { 0, 1, 999 };
+	int pwdlencases[] = { 0, 1, 14 };
+	int pwdpropcases[] = { 0, 1, 1 };
+	struct ldb_message *old_message = NULL;
+	const char *disable_file = "bin/python/samba/gp_sec_ext.py.disabled";
+
+	sysvol_path = lpcfg_path(lpcfg_service(tctx->lp_ctx, "sysvol"),
+				 lpcfg_default_service(tctx->lp_ctx), tctx);
+	torture_assert(tctx, sysvol_path, "Failed to fetch the sysvol path");
+
+	/* Ensure the sysvol path exists */
+	gpo_dir = talloc_asprintf(ctx, "%s/%s", sysvol_path, GPODIR);
+	mkdir_p(gpo_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+	gpo_file = talloc_asprintf(ctx, "%s/%s", gpo_dir, GPOFILE);
+
+	/* Open and read the samba db and store the initial password settings */
+	samdb = samdb_connect(ctx, tctx->ev, tctx->lp_ctx,
+			      system_session(tctx->lp_ctx), 0);
+	torture_assert(tctx, samdb, "Failed to connect to the samdb");
+
+	ret = ldb_search(samdb, ctx, &result, ldb_get_default_basedn(samdb),
+			 LDB_SCOPE_BASE, attrs, NULL);
+	torture_assert(tctx, ret == LDB_SUCCESS && result->count == 1,
+		       "Searching the samdb failed");
+
+	old_message = result->msgs[0];
+
+	/* Disable the policy */
+	open(disable_file, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, 0666);
+
+	for (i = 0; i < 3; i++) {
+		/* Write out the sysvol */
+		if ( (fp = fopen(gpo_file, "w")) ) {
+			fputs(talloc_asprintf(ctx, GPTTMPL, minpwdcases[i],
+					      maxpwdcases[i], pwdlencases[i],
+					      pwdpropcases[i]), fp);
+			fclose(fp);
+		}
+
+		gpt_file = talloc_asprintf(ctx, "%s/%s", sysvol_path, GPTINI);
+		increment_gpt_ini(ctx, gpt_file);
+
+		exec_gpo_update_command(tctx);
+
+		ret = ldb_search(samdb, ctx, &result,
+				 ldb_get_default_basedn(samdb),
+				 LDB_SCOPE_BASE, attrs, NULL);
+		torture_assert(tctx, ret == LDB_SUCCESS && result->count == 1,
+			       "Searching the samdb failed");
+		/* minPwdAge */
+		torture_assert_int_equal(tctx, unix2nttime(
+			ldb_msg_find_attr_as_string(
+				result->msgs[0],
+				attrs[0],
+				"")
+			),
+			unix2nttime(ldb_msg_find_attr_as_string(old_message,
+				attrs[0],
+				"")
+			),
+			"The minPwdAge should not have been applied");
+		/* maxPwdAge */
+		torture_assert_int_equal(tctx, unix2nttime(
+			ldb_msg_find_attr_as_string(
+				result->msgs[0],
+				attrs[1],
+				"")
+			),
+			unix2nttime(ldb_msg_find_attr_as_string(old_message,
+				attrs[1],
+				"")
+			),
+			"The maxPwdAge should not have been applied");
+		/* minPwdLength */
+		torture_assert_int_equal(tctx,
+			ldb_msg_find_attr_as_int(
+				result->msgs[0],
+				attrs[2],
+				-1
+			),
+			ldb_msg_find_attr_as_int(
+				old_message,
+				attrs[2],
+				-2
+			),
+			"The minPwdLength should not have been applied");
+		/* pwdProperties */
+		torture_assert_int_equal(tctx,
+			ldb_msg_find_attr_as_int(
+				result->msgs[0],
+				attrs[3],
+				-1
+			),
+			ldb_msg_find_attr_as_int(
+				old_message,
+				attrs[3],
+				-2
+			),
+			"The pwdProperties should not have been applied");
+	}
+
+	/* Unapply the settings and verify they are removed */
+	exec_gpo_unapply_command(tctx);
+
+	/* Re-enable the policy */
+	remove(disable_file);
+
+	talloc_free(ctx);
+	return true;
+}
-- 
2.13.6



More information about the samba-technical mailing list