[PATCH 01/30] gpo: move mkdir_p to lib/util

David Mulder dmulder at suse.com
Wed Nov 1 14:14:20 UTC 2017


Move the mkdir_p function to lib/util so it can be used elsewhere

Signed-off-by: David Mulder <dmulder at suse.com>
---
 ctdb/common/system.h      |  1 -
 ctdb/common/system_util.c | 49 ++-------------------------------
 lib/util/mkdir_p.c        | 70 +++++++++++++++++++++++++++++++++++++++++++++++
 lib/util/mkdir_p.h        | 21 ++++++++++++++
 lib/util/wscript_build    |  2 +-
 5 files changed, 94 insertions(+), 49 deletions(-)
 create mode 100644 lib/util/mkdir_p.c
 create mode 100644 lib/util/mkdir_p.h

diff --git a/ctdb/common/system.h b/ctdb/common/system.h
index ae01c58cfed..e6f65b5e621 100644
--- a/ctdb/common/system.h
+++ b/ctdb/common/system.h
@@ -53,7 +53,6 @@ bool parse_ip_mask(const char *str, const char *ifaces, ctdb_sock_addr *addr,
 
 void lockdown_memory(bool valgrinding);
 
-int mkdir_p(const char *dir, int mode);
 void mkdir_p_or_die(const char *dir, int mode);
 
 void ctdb_wait_for_process_to_exit(pid_t pid);
diff --git a/ctdb/common/system_util.c b/ctdb/common/system_util.c
index 63dcd53795b..f27eed7038d 100644
--- a/ctdb/common/system_util.c
+++ b/ctdb/common/system_util.c
@@ -41,6 +41,8 @@
 #include <procinfo.h>
 #endif
 
+#include "lib/util/mkdir_p.h"
+
 /*
   if possible, make this task real time
  */
@@ -272,53 +274,6 @@ void lockdown_memory(bool valgrinding)
 #endif
 }
 
-int mkdir_p(const char *dir, int mode)
-{
-	char t[PATH_MAX];
-	ssize_t len;
-	int ret;
-
-	if (strcmp(dir, "/") == 0) {
-		return 0;
-	}
-
-	if (strcmp(dir, ".") == 0) {
-		return 0;
-	}
-
-	/* Try to create directory */
-	ret = mkdir(dir, mode);
-	/* Succeed if that worked or if it already existed */
-	if (ret == 0 || errno == EEXIST) {
-		return 0;
-	}
-	/* Fail on anything else except ENOENT */
-	if (errno != ENOENT) {
-		return ret;
-	}
-
-	/* Create ancestors */
-	len = strlen(dir);
-	if (len >= PATH_MAX) {
-		errno = ENAMETOOLONG;
-		return -1;
-	}
-	strncpy(t, dir, len+1);
-
-	ret = mkdir_p(dirname(t), mode);
-	if (ret != 0) {
-		return ret;
-	}
-
-	/* Create directory */
-	ret = mkdir(dir, mode);
-	if ((ret == -1) && (errno == EEXIST)) {
-		ret = 0;
-	}
-
-	return ret;
-}
-
 void mkdir_p_or_die(const char *dir, int mode)
 {
 	int ret;
diff --git a/lib/util/mkdir_p.c b/lib/util/mkdir_p.c
new file mode 100644
index 00000000000..290a1f39520
--- /dev/null
+++ b/lib/util/mkdir_p.c
@@ -0,0 +1,70 @@
+/*
+   mkdir -p
+
+   Copyright (C) Amitay Isaacs  2014
+   Copyright (C) Martin Schwenke  2014
+
+   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 "replace.h"
+#include <sys/stat.h>
+#include <libgen.h>
+#include "mkdir_p.h"
+
+int mkdir_p(const char *dir, int mode)
+{
+	char t[PATH_MAX];
+	ssize_t len;
+	int ret;
+
+	if (strcmp(dir, "/") == 0) {
+		return 0;
+	}
+
+	if (strcmp(dir, ".") == 0) {
+		return 0;
+	}
+
+	/* Try to create directory */
+	ret = mkdir(dir, mode);
+	/* Succeed if that worked or if it already existed */
+	if (ret == 0 || errno == EEXIST) {
+		return 0;
+	}
+	/* Fail on anything else except ENOENT */
+	if (errno != ENOENT) {
+		return ret;
+	}
+
+	/* Create ancestors */
+	len = strlen(dir);
+	if (len >= PATH_MAX) {
+		errno = ENAMETOOLONG;
+		return -1;
+	}
+	strncpy(t, dir, len+1);
+
+	ret = mkdir_p(dirname(t), mode);
+	if (ret != 0) {
+		return ret;
+	}
+
+	/* Create directory */
+	ret = mkdir(dir, mode);
+	if ((ret == -1) && (errno == EEXIST)) {
+		ret = 0;
+	}
+
+	return ret;
+}
diff --git a/lib/util/mkdir_p.h b/lib/util/mkdir_p.h
new file mode 100644
index 00000000000..9281de8cb91
--- /dev/null
+++ b/lib/util/mkdir_p.h
@@ -0,0 +1,21 @@
+/*
+   mkdir -p
+
+   Copyright (C) Amitay Isaacs  2014
+   Copyright (C) Martin Schwenke  2014
+
+   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/>.
+*/
+
+int mkdir_p(const char *dir, int mode);
diff --git a/lib/util/wscript_build b/lib/util/wscript_build
index bb3cdd134ad..0b16b6f8216 100644
--- a/lib/util/wscript_build
+++ b/lib/util/wscript_build
@@ -72,7 +72,7 @@ bld.SAMBA_SUBSYSTEM('samba-util-core',
                               signal.c util.c idtree.c fault.c
                               substitute.c util_process.c util_strlist.c
                               strv_util.c bitmap.c select.c pidfile.c
-                              become_daemon.c ''',
+                              become_daemon.c mkdir_p.c''',
                     deps='''time-basic samba-debug socket-blocking talloc
                             tevent execinfo pthread strv''',
                     local_include=False)
-- 
2.14.2


>From 302adc51ea2f50d08c314d506bd22e6e91d26dd0 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Fri, 31 Jan 2014 13:15:41 +1300
Subject: [PATCH 02/30] Revert "libgpo: remove unused libgpo wscript_build."

This reverts commit feffac806800c1740521133e88a7ac777ce8f368.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
---
 libgpo/wscript_build | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 libgpo/wscript_build

diff --git a/libgpo/wscript_build b/libgpo/wscript_build
new file mode 100644
index 00000000000..034af0a163f
--- /dev/null
+++ b/libgpo/wscript_build
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+
+bld.SAMBA_SUBSYSTEM('LIBGPO',
+	source='gpo_util.c gpo_sec.c ../libgpo/gpext/gpext.c gpo_fetch.c gpo_ini.c ../source4/libgpo/ads_convenience.c ../source3/libgpo/gpo_filesync.c ../source4/libgpo/gpo_filesync.c',
+	deps='ldb samba-net samba-util',
+	enabled=False
+	)
-- 
2.14.2


>From 38720b2c4230fcd49c8c0212fadaf0784a525c4c Mon Sep 17 00:00:00 2001
From: Luke Morrison <luc785 at hotmail.com>
Date: Fri, 31 Jan 2014 13:27:05 +1300
Subject: [PATCH 03/30] gpo: Add python libgpo bindings

Split from "Initial commit for GPO work done by Luke Morrison" by David Mulder

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Signed-off-by: Luke Morrison <luke at hubtrek.com>
---
 {source3/libgpo => libgpo}/gpo_filesync.c |  0
 {source3/libgpo => libgpo}/gpo_proto.h    |  0
 {source3/libgpo => libgpo}/gpo_reg.c      |  1 -
 libgpo/pygpo.c                            | 71 +++++++++++++++++++++++++++++++
 libgpo/wscript_build                      | 16 ++++---
 source3/wscript_build                     |  4 +-
 6 files changed, 84 insertions(+), 8 deletions(-)
 rename {source3/libgpo => libgpo}/gpo_filesync.c (100%)
 rename {source3/libgpo => libgpo}/gpo_proto.h (100%)
 rename {source3/libgpo => libgpo}/gpo_reg.c (99%)
 create mode 100644 libgpo/pygpo.c

diff --git a/source3/libgpo/gpo_filesync.c b/libgpo/gpo_filesync.c
similarity index 100%
rename from source3/libgpo/gpo_filesync.c
rename to libgpo/gpo_filesync.c
diff --git a/source3/libgpo/gpo_proto.h b/libgpo/gpo_proto.h
similarity index 100%
rename from source3/libgpo/gpo_proto.h
rename to libgpo/gpo_proto.h
diff --git a/source3/libgpo/gpo_reg.c b/libgpo/gpo_reg.c
similarity index 99%
rename from source3/libgpo/gpo_reg.c
rename to libgpo/gpo_reg.c
index 7f5fbc4fff1..18d0498c7e9 100644
--- a/source3/libgpo/gpo_reg.c
+++ b/libgpo/gpo_reg.c
@@ -1037,4 +1037,3 @@ WERROR reg_apply_registry_entry(TALLOC_CTX *mem_ctx,
 
 	return werr;
 }
-
diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c
new file mode 100644
index 00000000000..c42811f94d2
--- /dev/null
+++ b/libgpo/pygpo.c
@@ -0,0 +1,71 @@
+/*
+   Unix SMB/CIFS implementation.
+   Copyright (C) Luke Morrison <luc785 at hotmail.com> 2013
+
+   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 <Python.h>
+#include "includes.h"
+#include "version.h"
+#include "param/pyparam.h"
+#include "gpo.h"
+#include "ads.h"
+
+/* A Python C API module to use LIBGPO */
+
+#ifndef Py_RETURN_NONE
+#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
+#endif
+
+/* Parameter mapping and functions for the GP_EXT struct */
+void initgpo(void);
+
+/* Global methods aka do not need a special pyobject type */
+static PyObject *py_gpo_get_sysvol_gpt_version(PyObject * self, PyObject * args)
+{
+	TALLOC_CTX *tmp_ctx = NULL;
+	char *unix_path;
+	char *display_name = NULL;
+	uint32_t sysvol_version = 0;
+	PyObject *result;
+
+	tmp_ctx = talloc_new(NULL);
+
+	if (!PyArg_ParseTuple(args, "s", &unix_path)) {
+		return NULL;
+	}
+	gpo_get_sysvol_gpt_version(tmp_ctx, unix_path, &sysvol_version, &display_name);
+	talloc_free(tmp_ctx);
+	result = Py_BuildValue("[s,i]", display_name, sysvol_version);
+	return result;
+}
+
+static PyMethodDef py_gpo_methods[] = {
+	{"gpo_get_sysvol_gpt_version", (PyCFunction) py_gpo_get_sysvol_gpt_version, METH_VARARGS, NULL},
+	{NULL}
+};
+
+/* Will be called by python when loading this module */
+void initgpo(void)
+{
+	PyObject *m;
+
+	debug_setup_talloc_log();
+	/* Instantiate the types */
+	m = Py_InitModule3("gpo", py_gpo_methods, "libgpo python bindings");
+	if (m == NULL)
+		return;
+	PyModule_AddObject(m, "version", PyString_FromString(SAMBA_VERSION_STRING));
+}
diff --git a/libgpo/wscript_build b/libgpo/wscript_build
index 034af0a163f..598cfcb327c 100644
--- a/libgpo/wscript_build
+++ b/libgpo/wscript_build
@@ -1,7 +1,13 @@
 #!/usr/bin/env python
 
-bld.SAMBA_SUBSYSTEM('LIBGPO',
-	source='gpo_util.c gpo_sec.c ../libgpo/gpext/gpext.c gpo_fetch.c gpo_ini.c ../source4/libgpo/ads_convenience.c ../source3/libgpo/gpo_filesync.c ../source4/libgpo/gpo_filesync.c',
-	deps='ldb samba-net samba-util',
-	enabled=False
-	)
+LIBGPO_SRC = '''gpo_ldap.c gpo_ini.c gpo_util.c gpo_fetch.c gpo_filesync.c
+                gpo_sec.c gpo_reg.c gpext/gpext.c'''
+
+bld.SAMBA3_LIBRARY('gpo',
+                   source='${LIBGPO_SRC}',
+                   deps='talloc ads TOKEN_UTIL auth',
+                   vars=locals(),
+                   private_library=True)
+bld.SAMBA3_PYTHON('python_samba_libgpo', 'pygpo.c',
+                 deps='pyparam_util gpo talloc ads TOKEN_UTIL auth',
+                 realname='samba/gpo.so')
diff --git a/source3/wscript_build b/source3/wscript_build
index dc7a51cc97a..22ea9fc869d 100644
--- a/source3/wscript_build
+++ b/source3/wscript_build
@@ -82,9 +82,9 @@ bld.SAMBA3_LIBRARY('gpo',
                           ../libgpo/gpo_ini.c
                           ../libgpo/gpo_util.c
                           ../libgpo/gpo_fetch.c
-                          libgpo/gpo_filesync.c
+                          ../libgpo/gpo_filesync.c
                           ../libgpo/gpo_sec.c
-                          libgpo/gpo_reg.c
+                          ../libgpo/gpo_reg.c
                           ''',
                    deps='''
                         talloc
-- 
2.14.2


>From af12fd9bd150df4f275905f3ce301f6f7509402a Mon Sep 17 00:00:00 2001
From: Luke Morrison <luc785 at hotmail.com>
Date: Fri, 31 Jan 2014 13:27:05 +1300
Subject: [PATCH 04/30] gpo: Initial commit for GPO work

Enclosed is my Summer of Code 2013 patch to have vital password GPO always applied to the Samba4 Domain Controller using a GPO update service.

To try it out "make -j" your samba with the patch, apply a security password GPO and see the difference in ~20 seconds. It also takes GPO hierarchy into account.

Split from "Initial commit for GPO work done by Luke Morrison" by David Mulder

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Signed-off-by: Luke Morrison <luke at hubtrek.com>
---
 python/samba/gpclass.py               | 317 ++++++++++++++++++++++++++++++++++
 python/samba/samdb.py                 |  18 ++
 source4/scripting/bin/samba_gpoupdate | 235 +++++++++++++++++++++++++
 3 files changed, 570 insertions(+)
 create mode 100755 python/samba/gpclass.py
 create mode 100755 source4/scripting/bin/samba_gpoupdate

diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
new file mode 100755
index 00000000000..c0ea528ee17
--- /dev/null
+++ b/python/samba/gpclass.py
@@ -0,0 +1,317 @@
+#!/usr/bin/env python
+#
+# Reads important GPO parameters and updates Samba
+# Copyright (C) Luke Morrison <luc785 at .hotmail.com> 2013
+#
+# 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/>.
+
+
+import sys
+import os
+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
+
+class gp_ext(object):
+    def list(self, rootpath):
+        return None
+
+    def __str__(self):
+        return "default_gp_ext"
+
+
+class inf_to_ldb(object):
+    '''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 __init__(self, ldb, dn, attribute, val):
+        self.ldb = ldb
+        self.dn = dn
+        self.attribute = attribute
+        self.val = val
+
+    def ch_minPwdAge(self, val):
+        self.ldb.set_minPwdAge(val)
+
+    def ch_maxPwdAge(self, val):
+        self.ldb.set_maxPwdAge(val)
+
+    def ch_minPwdLength(self, val):
+        self.ldb.set_minPwdLength(val)
+
+    def ch_pwdProperties(self, val):
+        self.ldb.set_pwdProperties(val)
+
+    def explicit(self):
+        return self.val
+
+    def nttime2unix(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.nttime2unix),
+                 "maxPwdAge" : (self.ch_maxPwdAge, self.nttime2unix),
+                 # 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 update_samba(self):
+        (upd_sam, value) = self.mapper().get(self.attribute)
+        upd_sam(value())     # or val = value() then update(val)
+
+
+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 __str__(self):
+        return "Security GPO extension"
+
+    def list(self, rootpath):
+        path = "%s%s" % (rootpath, "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
+        return path
+
+    def listmachpol(self, rootpath):
+        path = "%s%s" % (rootpath, "Machine/Registry.pol")
+        return path
+
+    def listuserpol(self, rootpath):
+        path = "%s%s" % (rootpath, "User/Registry.pol")
+        return path
+
+    def populate_inf(self):
+        return {"System Access": {"MinimumPasswordAge": ("minPwdAge", inf_to_ldb),
+                                  "MaximumPasswordAge": ("maxPwdAge", inf_to_ldb),
+                                  "MinimumPasswordLength": ("minPwdLength", inf_to_ldb),
+                                  "PasswordComplexity": ("pwdProperties", inf_to_ldb),
+                                 }
+               }
+
+    def read_inf(self, path, conn, attr_log):
+        ret = False
+        inftable = self.populate_inf()
+
+        policy = conn.loadfile(path).decode('utf-16')
+        current_section = None
+        LOG = open(attr_log, "a")
+        LOG.write(str(path.split('/')[2]) + '\n')
+
+        # So here we would declare a boolean,
+        # that would get changed to TRUE.
+        #
+        # If at any point in time a GPO was applied,
+        # then we return that boolean at the end.
+
+        for line in policy.splitlines():
+            line = line.strip()
+            if line[0] == '[':
+                section = line[1: -1]
+                current_section = inftable.get(section.encode('ascii', 'ignore'))
+
+            else:
+                # We must be in a section
+                if not current_section:
+                    continue
+                (key, value) = line.split("=")
+                key = key.strip()
+                if current_section.get(key):
+                    (att, setter) = current_section.get(key)
+                    value = value.encode('ascii', 'ignore')
+                    ret = True
+                    setter(self.ldb, self.dn, att, value).update_samba()
+        return ret
+
+    def parse(self, afile, ldb, conn, attr_log):
+        self.ldb = ldb
+        self.dn = ldb.get_default_basedn()
+
+        # 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, attr_log)
+                    except NTSTATUSError:
+                        continue
+            except ValueError:
+                try:
+                    return self.read_inf(afile, conn, attr_log)
+                except:
+                    return None
+
+
+def scan_log(sysvol_path):
+    a = open(sysvol_path, "r")
+    data = {}
+    for line in a.readlines():
+        line = line.strip()
+        (guid, version) = line.split(" ")
+        data[guid] = int(version)
+    return data
+
+
+def Reset_Defaults(test_ldb):
+    test_ldb.set_minPwdAge(str(-25920000000000))
+    test_ldb.set_maxPwdAge(str(-38016000000000))
+    test_ldb.set_minPwdLength(str(7))
+    test_ldb.set_pwdProperties(str(1))
+
+
+def check_deleted(guid_list, backloggpo):
+    if backloggpo is None:
+        return False
+    for guid in backloggpo:
+        if guid not in guid_list:
+            return True
+    return False
+
+
+# The hierarchy is as per MS http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
+#
+# It does not care about local GPO, because GPO and snap-ins are not made in Linux yet.
+# It follows the linking order and children GPO are last written format.
+#
+# Also, couple further testing with call scripts entitled informant and informant2.
+# They explicitly show the returned hierarchically sorted list.
+
+
+def container_indexes(GUID_LIST):
+    '''So the original list will need to be seperated into containers.
+    Returns indexed list of when the container changes after hierarchy
+    '''
+    count = 0
+    container_indexes = []
+    while count < (len(GUID_LIST)-1):
+        if GUID_LIST[count][2] != GUID_LIST[count+1][2]:
+            container_indexes.append(count+1)
+        count += 1
+    container_indexes.append(len(GUID_LIST))
+    return container_indexes
+
+
+def sort_linked(SAMDB, guid_list, start, end):
+    '''So GPO in same level need to have link level.
+    This takes a container and sorts it.
+
+    TODO:  Small small problem, it is backwards
+    '''
+    containers = gpo_user.get_gpo_containers(SAMDB, guid_list[start][0])
+    for right_container in containers:
+        if right_container.get('dn') == guid_list[start][2]:
+            break
+    gplink = str(right_container.get('gPLink'))
+    gplink_split = gplink.split('[')
+    linked_order = []
+    ret_list = []
+    for ldap_guid in gplink_split:
+        linked_order.append(str(ldap_guid[10:48]))
+    count = len(linked_order) - 1
+    while count > 0:
+        ret_list.append([linked_order[count], guid_list[start][1], guid_list[start][2]])
+        count -= 1
+    return ret_list
+
+
+def establish_hierarchy(SamDB, GUID_LIST, DC_OU, global_dn):
+    '''Takes a list of GUID from gpo, and sorts them based on OU, and realm.
+    See http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
+    '''
+    final_list = []
+    count_unapplied_GPO = 0
+    for GUID in GUID_LIST:
+
+        container_iteration = 0
+        # Assume first it is not applied
+        applied = False
+        # Realm only written on last call, if the GPO is linked to multiple places
+        gpo_realm = False
+
+        # A very important call. This gets all of the linked information.
+        GPO_CONTAINERS = gpo_user.get_gpo_containers(SamDB, GUID)
+        for GPO_CONTAINER in GPO_CONTAINERS:
+
+            container_iteration += 1
+
+            if DC_OU == str(GPO_CONTAINER.get('dn')):
+                applied = True
+                insert_gpo = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
+                final_list.append(insert_gpo)
+                break
+
+            if global_dn == str(GPO_CONTAINER.get('dn')) and (len(GPO_CONTAINERS) == 1):
+                gpo_realm = True
+                applied = True
+
+
+            if global_dn == str(GPO_CONTAINER.get('dn')) and (len(GPO_CONTAINERS) > 1):
+                gpo_realm = True
+                applied = True
+
+
+            if container_iteration == len(GPO_CONTAINERS):
+                if gpo_realm == False:
+                    insert_dud = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
+                    final_list.insert(0, insert_dud)
+                    count_unapplied_GPO += 1
+                else:
+                    REALM_GPO = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
+                    final_list.insert(count_unapplied_GPO, REALM_GPO)
+
+    # After GPO are sorted into containers, let's sort the containers themselves.
+    # But first we can get the GPO that we don't care about, out of the way.
+    indexed_places = container_indexes(final_list)
+    count = 0
+    unapplied_gpo = []
+    # Sorted by container
+    sorted_gpo_list = []
+
+    # Unapplied GPO live at start of list, append them to final list
+    while final_list[0][1] == False:
+        unapplied_gpo.append(final_list[count])
+        count += 1
+    count = 0
+    sorted_gpo_list += unapplied_gpo
+
+    # A single container call gets the linked order for all GPO in container.
+    # So we need one call per container - > index of the Original list
+    indexed_places.insert(0, 0)
+    while count < (len(indexed_places)-1):
+        sorted_gpo_list += (sort_linked(SamDB, final_list, indexed_places[count], indexed_places[count+1]))
+        count += 1
+    return sorted_gpo_list
diff --git a/python/samba/samdb.py b/python/samba/samdb.py
index 6fe680d30d8..46456290c9e 100644
--- a/python/samba/samdb.py
+++ b/python/samba/samdb.py
@@ -831,6 +831,24 @@ accountExpires: %u
         else:
             return res[0]["minPwdAge"][0]
 
+    def set_maxPwdAge(self, value):
+        m = ldb.Message()
+        m.dn = ldb.Dn(self, self.domain_dn())
+        m["maxPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "maxPwdAge")
+        self.modify(m)
+
+
+    def get_maxPwdAge(self):
+        res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["maxPwdAge"])
+        if len(res) == 0:
+            return None
+        elif not "maxPwdAge" in res[0]:
+            return None
+        else:
+            return res[0]["maxPwdAge"][0]
+
+
+
     def set_minPwdLength(self, value):
         m = ldb.Message()
         m.dn = ldb.Dn(self, self.domain_dn())
diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
new file mode 100755
index 00000000000..ba83dcf7e91
--- /dev/null
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -0,0 +1,235 @@
+#!/usr/bin/env python
+# Copyright Luke Morrison <luc785 at .hotmail.com> July 2013
+# Co-Edited by Matthieu Pattou July 2013 from original August 2013
+# Edited by Garming Sam Feb. 2014
+# Edited by Luke Morrison April 2014
+
+# 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/>.
+
+'''This script reads a log file of previous GPO, gets all GPO from sysvol
+and sorts them by container. Then, it applies the ones that haven't been
+applied, have changed, or is in the right container'''
+
+import os
+import fcntl
+import sys
+import tempfile
+import subprocess
+
+sys.path.insert(0, "bin/python")
+
+import samba
+import optparse
+from samba import getopt as options
+from samba.gpclass import *
+from samba.net import Net
+from samba.dcerpc import nbt
+from samba import smb
+
+
+# Finds all GPO Files ending in inf
+def gp_path_list(path):
+
+    GPO_LIST = []
+    for ext in gp_extensions:
+        GPO_LIST.append((ext, ext.list(path)))
+    return GPO_LIST
+
+
+def gpo_parser(GPO_LIST, ldb, conn, attr_log):
+    '''The API method to parse the GPO
+    :param GPO_LIST:
+    :param ldb: Live instance of an LDB object AKA Samba
+    :param conn: Live instance of a CIFS connection
+    :param attr_log: backlog path for GPO and attribute to be written
+    no return except a newly updated Samba
+    '''
+
+    ret = False
+    for entry in GPO_LIST:
+        (ext, thefile) = entry
+        if ret == False:
+            ret = ext.parse(thefile, ldb, conn, attr_log)
+        else:
+            temp = ext.parse(thefile, ldb, conn, attr_log)
+    return ret
+
+
+class GPOServiceSetup:
+    def __init__(self):
+        """Initialize all components necessary to return instances of
+        a Samba lp context (smb.conf) and Samba LDB context
+        """
+
+        self.parser = optparse.OptionParser("samba_gpoupdate [options]")
+        self.sambaopts = options.SambaOptions(self.parser)
+        self.credopts = None
+        self.opts = None
+        self.args = None
+        self.lp = None
+        self.smbconf = None
+        self.creds = None
+        self.url = None
+
+    # Setters or Initializers
+    def init_parser(self):
+        '''Get the command line options'''
+        self.parser.add_option_group(self.sambaopts)
+        self.parser.add_option_group(options.VersionOptions(self.parser))
+        self.init_credopts()
+        self.parser.add_option("-H", dest="url", help="URL for the samdb")
+        self.parser.add_option_group(self.credopts)
+
+    def init_argsopts(self):
+        '''Set the options and the arguments'''
+        (opts, args) = self.parser.parse_args()
+
+        self.opts = opts
+        self.args = args
+
+    def init_credopts(self):
+        '''Set Credential operations'''
+        self.credopts = options.CredentialsOptions(self.parser)
+
+    def init_lp(self):
+        '''Set the loadparm context'''
+        self.lp = self.sambaopts.get_loadparm()
+        self.smbconf = self.lp.configfile
+        if (not self.opts.url):
+            self.url = self.lp.samdb_url()
+        else:
+            self.url = self.opts.url
+
+    def init_session(self):
+        '''Initialize the session'''
+        self.creds = self.credopts.get_credentials(self.lp,
+            fallback_machine=True)
+        self.session = system_session()
+
+    def InitializeService(self):
+        '''Inializer for the thread'''
+        self.init_parser()
+        self.init_argsopts()
+        self.init_lp()
+        self.init_session()
+
+    # Getters
+    def Get_LDB(self):
+        '''Return a live instance of Samba'''
+        SambaDB = SamDB(self.url, session_info=self.session,
+            credentials=self.creds, lp=self.lp)
+        return SambaDB
+
+    def Get_lp_Content(self):
+        '''Return an instance of a local lp context'''
+        return self.lp
+
+    def Get_Creds(self):
+        '''Return an instance of a local creds'''
+        return self.creds
+
+
+def GetBackLog(sys_log):
+    """Reads BackLog and makes thread aware of which GPO are unchanged or empty
+    :param String sys_log: path to backLog
+    :return Dictionary previous_scanned_version: {Unedited GPO: Version Number}
+    *NOTE on Version below
+    """
+    previous_scanned_version = {}
+    if os.path.isfile(sys_log):
+        previous_scanned_version = scan_log(sys_log)
+        return previous_scanned_version
+    else:
+        return None
+
+# Set up the GPO service
+GPOService = GPOServiceSetup()
+GPOService.InitializeService()
+
+# Get the Samba Instance
+test_ldb = GPOService.Get_LDB()
+
+# Get The lp context
+lp = GPOService.Get_lp_Content()
+
+# Get the CREDS
+creds = GPOService.Get_Creds()
+
+# Read the readable backLog into a hashmap
+# then open writable backLog in same location
+BackLoggedGPO = None
+sys_log = '%s/%s' % (lp.get("path", "sysvol"), 'syslog.txt')
+attr_log = '%s/%s' % (lp.get("path", "sysvol"), 'attrlog.txt')
+BackLoggedGPO = GetBackLog(sys_log)
+
+
+BackLog = open(sys_log, "w")
+
+
+# We need to know writable DC to setup SMB connection
+net = Net(creds=creds, lp=lp)
+cldap_ret = net.finddc(domain=lp.get('realm'), flags=(nbt.NBT_SERVER_LDAP |
+    nbt.NBT_SERVER_DS))
+dc_hostname = cldap_ret.pdc_dns_name
+
+try:
+    conn = smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds)
+except Exception, e:
+    raise Exception("Error connecting to '%s' using SMB" % dc_hostname, e)
+
+# Get the dn of the domain, and the dn of readable/writable DC
+global_dn = test_ldb.domain_dn()
+DC_OU = "OU=Domain Controllers" + ',' + global_dn
+
+# Set up a List of the GUID for all GPO's
+guid_list = [x['name'] for x in conn.list('%s/Policies' % lp.get("realm").lower())]
+SYSV_PATH = '%s/%s/%s' % (lp.get("path", "sysvol"), lp.get("realm"), 'Policies')
+
+hierarchy_gpos = establish_hierarchy(test_ldb, guid_list, DC_OU, global_dn)
+change_backlog = False
+
+# Take a local list of all current GPO list and run it against previous GPO's
+# to see if something has changed. If so reset default and re-apply GPO.
+Applicable_GPO = []
+for i in hierarchy_gpos:
+    Applicable_GPO += i
+
+# Flag gets set when
+GPO_Changed = False
+GPO_Deleted = check_deleted(Applicable_GPO, BackLoggedGPO)
+if (GPO_Deleted):
+    # Null the backlog
+    BackLoggedGPO = {}
+    # Reset defaults then overwrite them
+    Reset_Defaults(test_ldb)
+    GPO_Changed = False
+
+for guid_eval in hierarchy_gpos:
+    guid = guid_eval[0]
+    gp_extensions = [gp_sec_ext()]
+    local_path = '%s/Policies' % lp.get("realm").lower() + '/' + guid + '/'
+    version = gpo.gpo_get_sysvol_gpt_version(lp.get("path", "sysvol") + '/' + local_path)[1]
+    gpolist = gp_path_list(local_path)
+    if(version != BackLoggedGPO.get(guid)):
+        GPO_Changed = True
+    # If the GPO has a dn that is applicable to Samba
+    if guid_eval[1]:
+        # If it has a GPO file that could apply to Samba
+        if gpolist[0][1]:
+            # If it we have not read it before and is not empty
+            # Rewrite entire logfile here
+            if  (version != 0) and GPO_Changed == True:
+                change_backlog = gpo_parser(gpolist, test_ldb, conn, attr_log)
+
+    BackLog.write('%s %i\n' % (guid, version))
-- 
2.14.2


>From 964ccab429aa3b318f5f52fb54225998f646028b Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Sat, 11 Feb 2017 07:53:07 -0700
Subject: [PATCH 05/30] gpo: Make the gpoupdate script much more reliable

Using a static file blanks the file when samba_gpoupdate crashes. Transformed
to a tdb file and added transactions. Add info logging to monitor gpo changes,
etc. Also handle parse errors and log an error message, then recover. Modified
the parsing code to use ConfigParser. Also, use the backslash in path names
when opening smb files, otherwise it fails against a windows server.

Signed-off-by: David Mulder <dmulder at suse.com>
---
 python/samba/gpclass.py               | 46 +++++++++++++++------------
 source4/scripting/bin/samba_gpoupdate | 60 +++++++++++++++++++++++------------
 2 files changed, 64 insertions(+), 42 deletions(-)

diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index c0ea528ee17..c8f8c79004f 100755
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -29,6 +29,8 @@ 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
 
 class gp_ext(object):
     def list(self, rootpath):
@@ -44,22 +46,27 @@ class inf_to_ldb(object):
     parameter to Samba4. Not registry oriented whatsoever.
     '''
 
-    def __init__(self, ldb, dn, attribute, val):
+    def __init__(self, logger, ldb, dn, attribute, val):
+        self.logger = logger
         self.ldb = ldb
         self.dn = dn
         self.attribute = attribute
         self.val = val
 
     def ch_minPwdAge(self, val):
+        self.logger.info('KDC Minimum Password age was changed from %s to %s' % (self.ldb.get_minPwdAge(), val))
         self.ldb.set_minPwdAge(val)
 
     def ch_maxPwdAge(self, val):
+        self.logger.info('KDC Maximum Password age was changed from %s to %s' % (self.ldb.get_maxPwdAge(), val))
         self.ldb.set_maxPwdAge(val)
 
     def ch_minPwdLength(self, val):
+        self.logger.info('KDC Minimum Password length was changed from %s to %s' % (self.ldb.get_minPwdLength(), val))
         self.ldb.set_minPwdLength(val)
 
     def ch_pwdProperties(self, val):
+        self.logger.info('KDC Password Properties were changed from %s to %s' % (self.ldb.get_pwdProperties(), val))
         self.ldb.set_pwdProperties(val)
 
     def explicit(self):
@@ -97,6 +104,9 @@ class gp_sec_ext(gp_ext):
 
     count = 0
 
+    def __init__(self, logger):
+        self.logger = logger
+
     def __str__(self):
         return "Security GPO extension"
 
@@ -124,7 +134,7 @@ class gp_sec_ext(gp_ext):
         ret = False
         inftable = self.populate_inf()
 
-        policy = conn.loadfile(path).decode('utf-16')
+        policy = conn.loadfile(path.replace('/', '\\')).decode('utf-16')
         current_section = None
         LOG = open(attr_log, "a")
         LOG.write(str(path.split('/')[2]) + '\n')
@@ -135,23 +145,20 @@ class gp_sec_ext(gp_ext):
         # If at any point in time a GPO was applied,
         # then we return that boolean at the end.
 
-        for line in policy.splitlines():
-            line = line.strip()
-            if line[0] == '[':
-                section = line[1: -1]
-                current_section = inftable.get(section.encode('ascii', 'ignore'))
-
-            else:
-                # We must be in a section
-                if not current_section:
-                    continue
-                (key, value) = line.split("=")
-                key = key.strip()
+        inf_conf = ConfigParser()
+        inf_conf.optionxform=str
+        inf_conf.readfp(StringIO(policy))
+
+        for section in inf_conf.sections():
+            current_section = inftable.get(section)
+            if not current_section:
+                continue
+            for key, value in inf_conf.items(section):
                 if current_section.get(key):
                     (att, setter) = current_section.get(key)
                     value = value.encode('ascii', 'ignore')
                     ret = True
-                    setter(self.ldb, self.dn, att, value).update_samba()
+                    setter(self.logger, self.ldb, self.dn, att, value).update_samba()
         return ret
 
     def parse(self, afile, ldb, conn, attr_log):
@@ -176,13 +183,10 @@ class gp_sec_ext(gp_ext):
                     return None
 
 
-def scan_log(sysvol_path):
-    a = open(sysvol_path, "r")
+def scan_log(sysvol_tdb):
     data = {}
-    for line in a.readlines():
-        line = line.strip()
-        (guid, version) = line.split(" ")
-        data[guid] = int(version)
+    for key in sysvol_tdb.iterkeys():
+        data[key] = sysvol_tdb.get(key)
     return data
 
 
diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
index ba83dcf7e91..a5573cece26 100755
--- a/source4/scripting/bin/samba_gpoupdate
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -26,6 +26,7 @@ import fcntl
 import sys
 import tempfile
 import subprocess
+import tdb
 
 sys.path.insert(0, "bin/python")
 
@@ -36,6 +37,7 @@ from samba.gpclass import *
 from samba.net import Net
 from samba.dcerpc import nbt
 from samba import smb
+import logging
 
 
 # Finds all GPO Files ending in inf
@@ -140,19 +142,6 @@ class GPOServiceSetup:
         return self.creds
 
 
-def GetBackLog(sys_log):
-    """Reads BackLog and makes thread aware of which GPO are unchanged or empty
-    :param String sys_log: path to backLog
-    :return Dictionary previous_scanned_version: {Unedited GPO: Version Number}
-    *NOTE on Version below
-    """
-    previous_scanned_version = {}
-    if os.path.isfile(sys_log):
-        previous_scanned_version = scan_log(sys_log)
-        return previous_scanned_version
-    else:
-        return None
-
 # Set up the GPO service
 GPOService = GPOServiceSetup()
 GPOService.InitializeService()
@@ -163,18 +152,35 @@ test_ldb = GPOService.Get_LDB()
 # Get The lp context
 lp = GPOService.Get_lp_Content()
 
+# Set up logging
+logger = logging.getLogger('samba_gpoupdate')
+logger.addHandler(logging.StreamHandler(sys.stdout))
+logger.setLevel(logging.CRITICAL)
+log_level = lp.log_level()
+if log_level == 1:
+    logger.setLevel(logging.ERROR)
+elif log_level == 2:
+    logger.setLevel(logging.WARNING)
+elif log_level == 3:
+    logger.setLevel(logging.INFO)
+elif log_level >= 4:
+    logger.setLevel(logging.DEBUG)
+
 # Get the CREDS
 creds = GPOService.Get_Creds()
 
 # Read the readable backLog into a hashmap
 # then open writable backLog in same location
 BackLoggedGPO = None
-sys_log = '%s/%s' % (lp.get("path", "sysvol"), 'syslog.txt')
+sys_log = '%s/%s' % (lp.get("path", "sysvol"), 'gpo.tdb')
 attr_log = '%s/%s' % (lp.get("path", "sysvol"), 'attrlog.txt')
-BackLoggedGPO = GetBackLog(sys_log)
 
 
-BackLog = open(sys_log, "w")
+if os.path.isfile(sys_log):
+    BackLog = tdb.open(sys_log)
+else:
+    BackLog = tdb.Tdb(sys_log, 0, tdb.DEFAULT, os.O_CREAT|os.O_RDWR)
+BackLoggedGPO = scan_log(BackLog)
 
 
 # We need to know writable DC to setup SMB connection
@@ -215,13 +221,18 @@ if (GPO_Deleted):
     Reset_Defaults(test_ldb)
     GPO_Changed = False
 
+BackLog.transaction_start()
 for guid_eval in hierarchy_gpos:
     guid = guid_eval[0]
-    gp_extensions = [gp_sec_ext()]
+    gp_extensions = [gp_sec_ext(logger)]
     local_path = '%s/Policies' % lp.get("realm").lower() + '/' + guid + '/'
-    version = gpo.gpo_get_sysvol_gpt_version(lp.get("path", "sysvol") + '/' + local_path)[1]
+    version = int(gpo.gpo_get_sysvol_gpt_version(lp.get("path", "sysvol") + '/' + local_path)[1])
+    try:
+        old_version = int(BackLoggedGPO.get(guid))
+    except:
+        old_version = -1
     gpolist = gp_path_list(local_path)
-    if(version != BackLoggedGPO.get(guid)):
+    if version != old_version:
         GPO_Changed = True
     # If the GPO has a dn that is applicable to Samba
     if guid_eval[1]:
@@ -230,6 +241,13 @@ for guid_eval in hierarchy_gpos:
             # If it we have not read it before and is not empty
             # Rewrite entire logfile here
             if  (version != 0) and GPO_Changed == True:
-                change_backlog = gpo_parser(gpolist, test_ldb, conn, attr_log)
+                logger.info('GPO %s has changed' % guid)
+                try:
+                    change_backlog = gpo_parser(gpolist, test_ldb, conn, attr_log)
+                except:
+                    logger.error('Failed to parse gpo %s' % guid)
+                    continue
+    BackLog.store(guid, '%i' % version)
+BackLog.transaction_commit()
+BackLog.close()
 
-    BackLog.write('%s %i\n' % (guid, version))
-- 
2.14.2


>From 4f433b72d8ddd32985b2574688dc14e7792039d4 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Wed, 9 Aug 2017 14:17:09 +1200
Subject: [PATCH 06/30] gpo: Create the gpo update service

Split from "Initial commit for GPO work done by Luke Morrison" by David Mulder

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Signed-off-by: Luke Morrison <luke at hubtrek.com>
Signed-off-by: David Mulder <dmulder at suse.com>

Then adapted to current master

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 docs-xml/smbdotconf/domain/gpoupdatecommand.xml |  17 +++
 lib/param/loadparm.c                            |   1 +
 source3/param/loadparm.c                        |   7 +
 source4/dsdb/gpo/gpo_update.c                   | 188 ++++++++++++++++++++++++
 4 files changed, 213 insertions(+)
 create mode 100644 docs-xml/smbdotconf/domain/gpoupdatecommand.xml
 create mode 100644 source4/dsdb/gpo/gpo_update.c

diff --git a/docs-xml/smbdotconf/domain/gpoupdatecommand.xml b/docs-xml/smbdotconf/domain/gpoupdatecommand.xml
new file mode 100644
index 00000000000..22a42163f27
--- /dev/null
+++ b/docs-xml/smbdotconf/domain/gpoupdatecommand.xml
@@ -0,0 +1,17 @@
+<samba:parameter name="gpo update command"
+                 context="G"
+                 type="list"
+                 advanced="1"
+                 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.
+	</para>
+</description>
+
+<value type="default">&pathconfig.SCRIPTSBINDIR;/samba_gpoupdate</value>
+<value type="example">/usr/local/sbin/gpoupdate</value>
+</samba:parameter>
diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c
index a1adb99b1f4..d788ffbe36f 100644
--- a/lib/param/loadparm.c
+++ b/lib/param/loadparm.c
@@ -2730,6 +2730,7 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx)
 	lpcfg_do_global_parameter(lp_ctx, "require strong key", "True");
 	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, "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 485d3f75b04..d3463246704 100644
--- a/source3/param/loadparm.c
+++ b/source3/param/loadparm.c
@@ -913,6 +913,13 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals)
 	Globals.dns_update_command = str_list_make_v3_const(NULL, s, NULL);
 	TALLOC_FREE(s);
 
+	s = talloc_asprintf(talloc_tos(), "%s/samba_gpoupdate", get_dyn_SCRIPTSBINDIR());
+	if (s == NULL) {
+		smb_panic("init_globals: ENOMEM");
+	}
+	Globals.gpo_update_command = str_list_make_v3_const(NULL, s, NULL);
+	TALLOC_FREE(s);
+
 	s = talloc_asprintf(talloc_tos(), "%s/samba_spnupdate", get_dyn_SCRIPTSBINDIR());
 	if (s == NULL) {
 		smb_panic("init_globals: ENOMEM");
diff --git a/source4/dsdb/gpo/gpo_update.c b/source4/dsdb/gpo/gpo_update.c
new file mode 100644
index 00000000000..6f234b42898
--- /dev/null
+++ b/source4/dsdb/gpo/gpo_update.c
@@ -0,0 +1,188 @@
+/*
+   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)
+{
+	/* For the moment the interval is hard coded to 5 sec */
+	DEBUG(0,
+	      ("calling %s interval = %d\n", __FUNCTION__,
+	       service->sysvscan.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);
+	/* /home/john/samba/samba/source4/scripting/bin/gpoupdate */
+	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);
+}
-- 
2.14.2


>From 8aaf552d42a4a2b6e13b3c6f15d428062c04647a Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 16:02:35 +1300
Subject: [PATCH 07/30] gpoupdate: Do not DEBUG(0) every scan interval

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dsdb/gpo/gpo_update.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/source4/dsdb/gpo/gpo_update.c b/source4/dsdb/gpo/gpo_update.c
index 6f234b42898..ef0f3ff218c 100644
--- a/source4/dsdb/gpo/gpo_update.c
+++ b/source4/dsdb/gpo/gpo_update.c
@@ -95,9 +95,6 @@ static void gpoupdate_sysvscan_handler_te(struct tevent_context *ev,
 static NTSTATUS gpoupdate_sysvscan_schedule(struct gpoupdate_service *service)
 {
 	/* For the moment the interval is hard coded to 5 sec */
-	DEBUG(0,
-	      ("calling %s interval = %d\n", __FUNCTION__,
-	       service->sysvscan.interval));
 	service->sysvscan.te =
 	    tevent_add_timer(service->task->event_ctx, service,
 			     timeval_current_ofs(service->sysvscan.interval, 0),
-- 
2.14.2


>From 9fc19476c889894b32399622f8ad8e1cd849c966 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 16:04:25 +1300
Subject: [PATCH 08/30] gpoupdate: Correct comment about hard-coded 5 second
 runing of the script

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dsdb/gpo/gpo_update.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/source4/dsdb/gpo/gpo_update.c b/source4/dsdb/gpo/gpo_update.c
index ef0f3ff218c..642806b235f 100644
--- a/source4/dsdb/gpo/gpo_update.c
+++ b/source4/dsdb/gpo/gpo_update.c
@@ -94,7 +94,10 @@ static void gpoupdate_sysvscan_handler_te(struct tevent_context *ev,
 
 static NTSTATUS gpoupdate_sysvscan_schedule(struct gpoupdate_service *service)
 {
-	/* For the moment the interval is hard coded to 5 sec */
+	/*
+	 * 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),
-- 
2.14.2


>From eef39cd73774e0635be54bfe30114030a60bad3e Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 16:06:05 +1300
Subject: [PATCH 09/30] gpoupdate: Remove developer path from the comment

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dsdb/gpo/gpo_update.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/source4/dsdb/gpo/gpo_update.c b/source4/dsdb/gpo/gpo_update.c
index 642806b235f..2a5bba91b05 100644
--- a/source4/dsdb/gpo/gpo_update.c
+++ b/source4/dsdb/gpo/gpo_update.c
@@ -111,7 +111,6 @@ 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);
-	/* /home/john/samba/samba/source4/scripting/bin/gpoupdate */
 	TALLOC_FREE(service->sysvscan.subreq);
 	DEBUG(3, ("Calling GPO update script\n"));
 	service->sysvscan.subreq = samba_runcmd_send(service,
-- 
2.14.2


>From 7046612978b8e1f841a74ae37ac15fa91eb51d0e Mon Sep 17 00:00:00 2001
From: Luke Morrison <luc785 at hotmail.com>
Date: Fri, 7 Feb 2014 15:57:14 +1300
Subject: [PATCH 10/30] gpo: enable gpo update with addition to build system

Split from "Initial commit for GPO work done by Luke Morrison" by Garming Sam

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Signed-off-by: Luke Morrison <luke at hubtrek.com>
---
 source3/wscript_build      | 19 -------------------
 source4/dsdb/wscript_build |  9 +++++++++
 wscript_build              |  1 +
 3 files changed, 10 insertions(+), 19 deletions(-)

diff --git a/source3/wscript_build b/source3/wscript_build
index 22ea9fc869d..6d5cea7b877 100644
--- a/source3/wscript_build
+++ b/source3/wscript_build
@@ -76,25 +76,6 @@ bld.SAMBA3_LIBRARY('msrpc3',
                    ''',
                    private_library=True)
 
-bld.SAMBA3_LIBRARY('gpo',
-                   source='''
-                          ../libgpo/gpo_ldap.c
-                          ../libgpo/gpo_ini.c
-                          ../libgpo/gpo_util.c
-                          ../libgpo/gpo_fetch.c
-                          ../libgpo/gpo_filesync.c
-                          ../libgpo/gpo_sec.c
-                          ../libgpo/gpo_reg.c
-                          ''',
-                   deps='''
-                        talloc
-                        ads
-                        TOKEN_UTIL
-                        gpext
-                        auth
-                        ''',
-                   private_library=True)
-
 bld.SAMBA3_SUBSYSTEM('AVAHI',
                     source='''
                            lib/avahi.c
diff --git a/source4/dsdb/wscript_build b/source4/dsdb/wscript_build
index 29c6f0e4a70..328497c8590 100644
--- a/source4/dsdb/wscript_build
+++ b/source4/dsdb/wscript_build
@@ -62,6 +62,15 @@ 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
diff --git a/wscript_build b/wscript_build
index 8758b6d5789..52f8de1c13b 100644
--- a/wscript_build
+++ b/wscript_build
@@ -87,6 +87,7 @@ bld.RECURSE('lib/tdr')
 bld.RECURSE('lib/tsocket')
 bld.RECURSE('lib/crypto')
 bld.RECURSE('lib/torture')
+bld.RECURSE('libgpo')
 bld.RECURSE('source4/lib/com')
 bld.RECURSE('source4/dns_server')
 bld.RECURSE('source4/echo_server')
-- 
2.14.2


>From e7cafb6f5beaeb132f10b1d5380a38f1549dc336 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Wed, 5 Feb 2014 17:18:23 +1300
Subject: [PATCH 11/30] gpo: fix the building of gpext to only once

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
---
 libgpo/wscript_build               | 13 ++++++-------
 source3/libgpo/gpext/wscript_build |  4 ----
 source3/utils/wscript_build        |  2 +-
 3 files changed, 7 insertions(+), 12 deletions(-)

diff --git a/libgpo/wscript_build b/libgpo/wscript_build
index 598cfcb327c..7d1d32628f0 100644
--- a/libgpo/wscript_build
+++ b/libgpo/wscript_build
@@ -1,13 +1,12 @@
 #!/usr/bin/env python
 
-LIBGPO_SRC = '''gpo_ldap.c gpo_ini.c gpo_util.c gpo_fetch.c gpo_filesync.c
-                gpo_sec.c gpo_reg.c gpext/gpext.c'''
-
-bld.SAMBA3_LIBRARY('gpo',
-                   source='${LIBGPO_SRC}',
+bld.SAMBA3_LIBRARY('gpext',
+                   source='''gpext/gpext.c gpo_util.c gpo_ldap.c gpo_ini.c
+                           gpo_fetch.c gpo_filesync.c
+                           gpo_sec.c gpo_reg.c''',
                    deps='talloc ads TOKEN_UTIL auth',
-                   vars=locals(),
                    private_library=True)
+
 bld.SAMBA3_PYTHON('python_samba_libgpo', 'pygpo.c',
-                 deps='pyparam_util gpo talloc ads TOKEN_UTIL auth',
+                 deps='pyparam_util gpext talloc ads TOKEN_UTIL auth',
                  realname='samba/gpo.so')
diff --git a/source3/libgpo/gpext/wscript_build b/source3/libgpo/gpext/wscript_build
index 3a120a5aff0..365b4203f91 100644
--- a/source3/libgpo/gpext/wscript_build
+++ b/source3/libgpo/gpext/wscript_build
@@ -1,9 +1,5 @@
 #!/usr/bin/env python
 
-bld.SAMBA3_SUBSYSTEM('gpext',
-                    source='../../../libgpo/gpext/gpext.c',
-                    deps='samba-util samba3core gpo')
-
 bld.SAMBA3_MODULE('gpext_registry',
                  subsystem='gpext',
                  source='registry.c',
diff --git a/source3/utils/wscript_build b/source3/utils/wscript_build
index 8d3378f0f0d..04eaf077f67 100644
--- a/source3/utils/wscript_build
+++ b/source3/utils/wscript_build
@@ -223,7 +223,7 @@ bld.SAMBA3_BINARY('net',
                  KRBCLIENT
                  ndr-standard
                  msrpc3
-                 gpo
+                 gpext
                  ads
                  smbd_base
                  LIBADS_SERVER
-- 
2.14.2


>From fc5fad20ea030b9f6e77af6c25fb941fef0c49f3 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Wed, 10 May 2017 13:30:17 -0600
Subject: [PATCH 12/30] libgpo: Add libgpo python bindings

Create libgpo python bindings for GROUP_POLICY_OBJECT, ADS_STRUCT, gpo_get_unix_path, ads_connect, and ads_get_gpo_list.

Signed-off-by: David Mulder <dmulder at suse.com>
---
 libgpo/pygpo.c | 389 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 387 insertions(+), 2 deletions(-)

diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c
index c42811f94d2..3a31c59d6cc 100644
--- a/libgpo/pygpo.c
+++ b/libgpo/pygpo.c
@@ -22,6 +22,9 @@
 #include "param/pyparam.h"
 #include "gpo.h"
 #include "ads.h"
+#include "secrets.h"
+#include "../libds/common/flags.h"
+#include "auth/credentials/pycredentials.h"
 
 /* A Python C API module to use LIBGPO */
 
@@ -29,6 +32,251 @@
 #define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
 #endif
 
+typedef struct {
+	PyObject_HEAD
+	TALLOC_CTX *frame;
+	struct GROUP_POLICY_OBJECT *gpo_ptr;
+	struct GROUP_POLICY_OBJECT *head;
+} GPO;
+
+static void py_gpo_dealloc(GPO* self)
+{
+	talloc_free(self->frame);
+	Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+static PyObject* py_gpo_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+	GPO *self;
+	PyObject *c_obj;
+	PyObject *talloc_obj;
+	static const char *kwlist[] = {"gpo_ptr", "talloc_ctx", NULL};
+	self = (GPO*)type->tp_alloc(type, 0);
+	if (PyArg_ParseTupleAndKeywords(args, kwds, "|OO", discard_const_p(char *, kwlist),
+					&c_obj, &talloc_obj)) {
+		self->gpo_ptr = PyCapsule_GetPointer(c_obj, NULL);
+		self->head = self->gpo_ptr;
+		self->frame = PyCapsule_GetPointer(talloc_obj, NULL);
+	} else
+		self->gpo_ptr = NULL;
+		self->frame = NULL;
+	return (PyObject*)self;
+}
+
+static int py_gpo_init(GPO *self, PyObject *args, PyObject *kwds)
+{
+	return 0;
+}
+
+#define GPO_getter(ATTR) \
+static PyObject* GPO_get_##ATTR(GPO *self, void *closure) \
+{ \
+	if (self->gpo_ptr->ATTR) \
+		return PyString_FromString(self->gpo_ptr->ATTR); \
+	else \
+		return Py_None; \
+}
+GPO_getter(ds_path)
+GPO_getter(file_sys_path)
+GPO_getter(display_name)
+GPO_getter(name)
+GPO_getter(link)
+GPO_getter(user_extensions)
+GPO_getter(machine_extensions)
+
+static PyGetSetDef GPO_setters[] = {
+	{discard_const_p(char, "ds_path"), (getter)GPO_get_ds_path, NULL, NULL, NULL},
+	{discard_const_p(char, "file_sys_path"), (getter)GPO_get_file_sys_path, NULL, NULL, NULL},
+	{discard_const_p(char, "display_name"), (getter)GPO_get_display_name, NULL, NULL, NULL},
+	{discard_const_p(char, "name"), (getter)GPO_get_name, NULL, NULL, NULL},
+	{discard_const_p(char, "link"), (getter)GPO_get_link, NULL, NULL, NULL},
+	{discard_const_p(char, "user_extensions"), (getter)GPO_get_user_extensions, NULL, NULL, NULL},
+	{discard_const_p(char, "machine_extensions"), (getter)GPO_get_machine_extensions, NULL, NULL, NULL},
+	{NULL}
+};
+
+static PyObject *py_gpo_get_unix_path(GPO *self, PyObject *args, PyObject *kwds)
+{
+	NTSTATUS status;
+	const char *cache_dir = NULL;
+	PyObject *ret = Py_None;
+	char *unix_path = NULL;
+
+	static const char *kwlist[] = {"cache_dir", NULL};
+	if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s", discard_const_p(char *, kwlist), &cache_dir)) {
+		PyErr_SetString(PyExc_SystemError, "Failed to parse arguments to gpo_get_unix_path()");
+		goto out;
+	}
+
+	if (!cache_dir) {
+		cache_dir = cache_path(GPO_CACHE_DIR);
+		if (!cache_dir) {
+			PyErr_SetString(PyExc_MemoryError, "Failed to determine gpo cache dir");
+			goto out;
+		}
+	}
+
+	status = gpo_get_unix_path(self->frame, cache_dir, self->gpo_ptr, &unix_path);
+	if (!NT_STATUS_IS_OK(status)) {
+		PyErr_SetString(PyExc_SystemError, "Failed to determine gpo unix path");
+		goto out;
+	}
+
+	ret = PyString_FromString(unix_path);
+
+out:
+	return ret;
+}
+
+static PyMethodDef GPO_methods[] = {
+	{"get_unix_path", (PyCFunction)py_gpo_get_unix_path, METH_KEYWORDS, NULL },
+	{NULL}
+};
+
+static PyTypeObject GPOType;
+static PyObject* py_gpo_iternext(GPO *self)
+{
+	if (self->gpo_ptr && self->gpo_ptr->next) {
+		self->gpo_ptr = self->gpo_ptr->next;
+		return (PyObject *)self;
+	} else {
+		self->gpo_ptr = self->head;
+		PyErr_SetNone(PyExc_StopIteration);
+		return NULL;
+	}
+}
+
+static PyObject* py_gpo_iter(PyObject *self)
+{
+	Py_INCREF(self);
+	return self;
+}
+
+static PyTypeObject GPOType = {
+	.tp_name = "gpo.GROUP_POLICY_OBJECT",
+	.tp_basicsize = sizeof(GPO),
+	.tp_new = py_gpo_new,
+	.tp_free = PyObject_Del,
+	.tp_init = (initproc)py_gpo_init,
+	.tp_alloc = PyType_GenericAlloc,
+	.tp_dealloc = (destructor)py_gpo_dealloc,
+	.tp_doc = "GROUP_POLICY_OBJECT",
+	.tp_getset = GPO_setters,
+	.tp_methods = GPO_methods,
+	.tp_iter = py_gpo_iter,
+	.tp_iternext = (iternextfunc)py_gpo_iternext,
+	.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER,
+};
+
+typedef struct {
+	PyObject_HEAD
+	TALLOC_CTX *frame;
+	ADS_STRUCT *ads_ptr;
+	struct cli_credentials *cli_creds;
+} ADS;
+
+static void py_ads_dealloc(ADS* self)
+{
+	ads_destroy(&(self->ads_ptr));
+	talloc_free(self->frame);
+	Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+static PyObject* py_ads_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+	ADS *self;
+	self = (ADS*)type->tp_alloc(type, 0);
+	return (PyObject*)self;
+}
+
+static PyObject* py_ads_connect(ADS *self);
+static int py_ads_init(ADS *self, PyObject *args, PyObject *kwds)
+{
+	const char *realm = NULL;
+	const char *workgroup = NULL;
+	const char *ldap_server = NULL;
+	PyObject *creds = NULL;
+	PyObject *lp_obj = NULL;
+	struct loadparm_context *lp_ctx = NULL;
+
+	static const char *kwlist[] = {"ldap_server", "loadparm_context", "credentials", NULL};
+	if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO|O", discard_const_p(char *, kwlist), &ldap_server, &lp_obj, &creds))
+		return -1;
+
+	self->frame = talloc_stackframe();
+
+	if (creds) self->cli_creds = pytalloc_get_type(creds, struct cli_credentials);
+
+	if (lp_obj) {
+		lp_ctx = pytalloc_get_type(lp_obj, struct loadparm_context);
+		if (lp_ctx == NULL) {
+			return -1;
+		}
+	}
+	if (!lp_load_initial_only(lp_ctx->szConfigFile)) return -1;
+
+	if (self->cli_creds) {
+		realm = cli_credentials_get_realm(self->cli_creds);
+		workgroup = cli_credentials_get_domain(self->cli_creds);
+	} else {
+		realm = lp_realm();
+		workgroup = lp_workgroup();
+		if (!ldap_server) return -1;
+	}
+
+	if ( !(self->ads_ptr = ads_init(realm, workgroup, ldap_server)) )
+		return -1;
+
+	return 0;
+}
+
+static PyObject* py_ads_connect(ADS *self)
+{
+	ADS_STATUS status;
+	if (self->cli_creds) {
+		self->ads_ptr->auth.user_name = SMB_STRDUP(cli_credentials_get_username(self->cli_creds));
+self->ads_ptr->auth.flags |= ADS_AUTH_USER_CREDS;
+		self->ads_ptr->auth.password = SMB_STRDUP(cli_credentials_get_password(self->cli_creds));
+		self->ads_ptr->auth.realm = SMB_STRDUP(cli_credentials_get_realm(self->cli_creds));
+
+		status = ads_connect_user_creds(self->ads_ptr);
+		if (!ADS_ERR_OK(status)) {
+			PyErr_SetString(PyExc_SystemError, "ads_connect() failed");
+			Py_RETURN_FALSE;
+		}
+	} else {
+		char *passwd;
+
+		if (asprintf(&(self->ads_ptr->auth.user_name), "%s$", lp_netbios_name()) == -1) {
+			PyErr_SetString(PyExc_SystemError, "Failed to asprintf");
+			Py_RETURN_FALSE;
+		} else
+			self->ads_ptr->auth.flags |= ADS_AUTH_USER_CREDS;
+		if (!secrets_init()) {
+			PyErr_SetString(PyExc_SystemError, "secrets_init() failed");
+			Py_RETURN_FALSE;
+		}
+		if (!(passwd = secrets_fetch_machine_password(self->ads_ptr->server.workgroup, NULL, NULL))) {
+			PyErr_SetString(PyExc_SystemError, "Failed to fetch the machine account password");
+			Py_RETURN_FALSE;
+		}
+		self->ads_ptr->auth.password = smb_xstrdup(passwd);
+		self->ads_ptr->auth.realm = smb_xstrdup(self->ads_ptr->server.realm);
+		if (!strupper_m(self->ads_ptr->auth.realm)) {
+			PyErr_SetString(PyExc_SystemError, "Failed to strdup");
+			Py_RETURN_FALSE;
+		}
+
+		status = ads_connect(self->ads_ptr);
+		if (!ADS_ERR_OK(status)) {
+			PyErr_SetString(PyExc_SystemError, "ads_connect() failed");
+			Py_RETURN_FALSE;
+		}
+	}
+
+	Py_RETURN_TRUE;
+}
+
 /* Parameter mapping and functions for the GP_EXT struct */
 void initgpo(void);
 
@@ -52,6 +300,138 @@ static PyObject *py_gpo_get_sysvol_gpt_version(PyObject * self, PyObject * args)
 	return result;
 }
 
+static ADS_STATUS find_samaccount(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, const char *samaccountname, uint32_t *uac_ret, const char **dn_ret)
+{
+	ADS_STATUS status;
+	const char *attrs[] = { "userAccountControl", NULL };
+	const char *filter;
+	LDAPMessage *res = NULL;
+	char *dn = NULL;
+	uint32_t uac = 0;
+
+	filter = talloc_asprintf(mem_ctx, "(sAMAccountName=%s)", samaccountname);
+	if (filter == NULL) {
+		status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+		goto out;
+	}
+
+	status = ads_do_search_all(ads, ads->config.bind_path, LDAP_SCOPE_SUBTREE, filter, attrs, &res);
+
+	if (!ADS_ERR_OK(status)) {
+		goto out;
+	}
+
+	if (ads_count_replies(ads, res) != 1) {
+		status = ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+		goto out;
+	}
+
+	dn = ads_get_dn(ads, talloc_tos(), res);
+	if (dn == NULL) {
+		status = ADS_ERROR(LDAP_NO_MEMORY);
+		goto out;
+	}
+
+	if (!ads_pull_uint32(ads, res, "userAccountControl", &uac)) {
+		status = ADS_ERROR(LDAP_NO_SUCH_ATTRIBUTE);
+		goto out;
+	}
+
+	if (uac_ret) {
+		*uac_ret = uac;
+	}
+
+	if (dn_ret) {
+		*dn_ret = talloc_strdup(mem_ctx, dn);
+		if (!*dn_ret) {
+			status = ADS_ERROR(LDAP_NO_MEMORY);
+			goto out;
+		}
+	}
+out:
+	TALLOC_FREE(dn);
+	ads_msgfree(ads, res);
+
+	return status;
+}
+
+static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
+{
+	TALLOC_CTX *mem_ctx = NULL;
+	struct GROUP_POLICY_OBJECT *gpo_list = NULL;
+	ADS_STATUS status;
+	const char *samaccountname = NULL;
+	const char *dn = NULL;
+	uint32_t uac = 0;
+	uint32_t flags = 0;
+	struct security_token *token = NULL;
+	PyObject *ret = Py_None;
+	TALLOC_CTX *gpo_ctx;
+	PyObject * t_args;
+
+	static const char *kwlist[] = {"samaccountname", NULL};
+	if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", discard_const_p(char *, kwlist), &samaccountname)) {
+		PyErr_SetString(PyExc_SystemError, "Failed to parse arguments to py_ads_get_gpo_list()");
+		goto out;
+	}
+
+	mem_ctx = talloc_new(self->frame);
+
+	status = find_samaccount(self->ads_ptr, mem_ctx, samaccountname, &uac, &dn);
+	if (!ADS_ERR_OK(status)) {
+		PyErr_SetString(PyExc_SystemError, "Failed to find samAccountName");
+		goto out;
+	}
+
+	if (uac & UF_WORKSTATION_TRUST_ACCOUNT || uac & UF_SERVER_TRUST_ACCOUNT) {
+		flags |= GPO_LIST_FLAG_MACHINE;
+		status = gp_get_machine_token(self->ads_ptr, mem_ctx, dn, &token);
+	} else {
+		status = ads_get_sid_token(self->ads_ptr, mem_ctx, dn, &token);
+	}
+	if (!ADS_ERR_OK(status)) {
+		PyErr_SetString(PyExc_SystemError, "Failed to get token");
+		goto out;
+	}
+
+	gpo_ctx = talloc_new(NULL);
+	status = ads_get_gpo_list(self->ads_ptr, gpo_ctx, dn, flags, token, &gpo_list);
+	if (!ADS_ERR_OK(status)) {
+		PyErr_SetString(PyExc_SystemError, "Failed to fetch GPO list");
+		goto out;
+	}
+
+	t_args = PyTuple_New(2);
+	PyTuple_SetItem(t_args, 0, PyCapsule_New(gpo_list, NULL, NULL));
+	PyTuple_SetItem(t_args, 1, PyCapsule_New(gpo_ctx, NULL, NULL));
+	ret = PyObject_CallObject((PyObject *)&GPOType, t_args);
+
+out:
+	talloc_free(mem_ctx);
+	if (!ret) {
+		PyErr_Print();
+		return Py_None;
+	}
+	return ret;
+}
+
+static PyMethodDef ADS_methods[] = {
+	{ "connect", (PyCFunction)py_ads_connect, METH_NOARGS, "Connect to the LDAP server" },
+	{ "get_gpo_list", (PyCFunction)py_ads_get_gpo_list, METH_KEYWORDS, NULL },
+	{ NULL }
+};
+
+static PyTypeObject ads_ADSType = {
+	.tp_name = "gpo.ADS_STRUCT",
+	.tp_basicsize = sizeof(ADS),
+	.tp_dealloc = (destructor)py_ads_dealloc,
+	.tp_flags = Py_TPFLAGS_DEFAULT,
+	.tp_doc = "ADS struct",
+	.tp_methods = ADS_methods,
+	.tp_init = (initproc)py_ads_init,
+	.tp_new = py_ads_new,
+};
+
 static PyMethodDef py_gpo_methods[] = {
 	{"gpo_get_sysvol_gpt_version", (PyCFunction) py_gpo_get_sysvol_gpt_version, METH_VARARGS, NULL},
 	{NULL}
@@ -65,7 +445,12 @@ void initgpo(void)
 	debug_setup_talloc_log();
 	/* Instantiate the types */
 	m = Py_InitModule3("gpo", py_gpo_methods, "libgpo python bindings");
-	if (m == NULL)
-		return;
+	if (m == NULL) return;
 	PyModule_AddObject(m, "version", PyString_FromString(SAMBA_VERSION_STRING));
+	if (PyType_Ready(&ads_ADSType) < 0)
+		return;
+	PyModule_AddObject(m, "ADS_STRUCT", (PyObject *)&ads_ADSType);
+	if (PyType_Ready(&GPOType) < 0)
+		return;
+	PyModule_AddObject(m, "GROUP_POLICY_OBJECT", (PyObject *)&GPOType);
 }
-- 
2.14.2


>From 08a3b3dd9752e2e4beeeebd82b17d06611da06b2 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Fri, 24 Feb 2017 14:19:48 -0700
Subject: [PATCH 13/30] gpo: Make the gpclass more easily extensible

Signed-off-by: David Mulder <dmulder at suse.com>
---
 python/samba/gpclass.py               | 52 +++++++++++++++++++++++------------
 source4/scripting/bin/samba_gpoupdate |  8 +++---
 2 files changed, 39 insertions(+), 21 deletions(-)

diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index c8f8c79004f..a8b44c45dc2 100755
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -31,27 +31,51 @@ import codecs
 from samba import NTSTATUSError
 from ConfigParser import ConfigParser
 from StringIO import StringIO
+from abc import ABCMeta, abstractmethod
 
 class gp_ext(object):
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
     def list(self, rootpath):
-        return None
+        pass
+
+    @abstractmethod
+    def parse(self, afile, ldb, conn, attr_log, lp):
+        pass
 
+    @abstractmethod
     def __str__(self):
-        return "default_gp_ext"
+        pass
 
 
-class inf_to_ldb(object):
-    '''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.
-    '''
+class inf_to():
+    __metaclass__ = ABCMeta
 
-    def __init__(self, logger, ldb, dn, attribute, val):
+    def __init__(self, logger, ldb, dn, lp, attribute, val):
         self.logger = logger
         self.ldb = ldb
         self.dn = dn
         self.attribute = attribute
         self.val = val
+        self.lp = lp
+
+    def explicit(self):
+        return self.val
+
+    def update_samba(self):
+        (upd_sam, value) = self.mapper().get(self.attribute)
+        upd_sam(value())
+
+    @abstractmethod
+    def mapper(self):
+        pass
+
+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):
         self.logger.info('KDC Minimum Password age was changed from %s to %s' % (self.ldb.get_minPwdAge(), val))
@@ -69,9 +93,6 @@ class inf_to_ldb(object):
         self.logger.info('KDC Password Properties were changed from %s to %s' % (self.ldb.get_pwdProperties(), val))
         self.ldb.set_pwdProperties(val)
 
-    def explicit(self):
-        return self.val
-
     def nttime2unix(self):
         seconds = 60
         minutes = 60
@@ -91,10 +112,6 @@ class inf_to_ldb(object):
 
                }
 
-    def update_samba(self):
-        (upd_sam, value) = self.mapper().get(self.attribute)
-        upd_sam(value())     # or val = value() then update(val)
-
 
 class gp_sec_ext(gp_ext):
     '''This class does the following two things:
@@ -158,11 +175,12 @@ class gp_sec_ext(gp_ext):
                     (att, setter) = current_section.get(key)
                     value = value.encode('ascii', 'ignore')
                     ret = True
-                    setter(self.logger, self.ldb, self.dn, att, value).update_samba()
+                    setter(self.logger, self.ldb, self.dn, self.lp, att, value).update_samba()
         return ret
 
-    def parse(self, afile, ldb, conn, attr_log):
+    def parse(self, afile, ldb, conn, attr_log, lp):
         self.ldb = ldb
+        self.lp = lp
         self.dn = ldb.get_default_basedn()
 
         # Fixing the bug where only some Linux Boxes capitalize MACHINE
diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
index a5573cece26..3a6ed99d7b8 100755
--- a/source4/scripting/bin/samba_gpoupdate
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -49,7 +49,7 @@ def gp_path_list(path):
     return GPO_LIST
 
 
-def gpo_parser(GPO_LIST, ldb, conn, attr_log):
+def gpo_parser(GPO_LIST, ldb, conn, attr_log, lp):
     '''The API method to parse the GPO
     :param GPO_LIST:
     :param ldb: Live instance of an LDB object AKA Samba
@@ -62,9 +62,9 @@ def gpo_parser(GPO_LIST, ldb, conn, attr_log):
     for entry in GPO_LIST:
         (ext, thefile) = entry
         if ret == False:
-            ret = ext.parse(thefile, ldb, conn, attr_log)
+            ret = ext.parse(thefile, ldb, conn, attr_log, lp)
         else:
-            temp = ext.parse(thefile, ldb, conn, attr_log)
+            temp = ext.parse(thefile, ldb, conn, attr_log, lp)
     return ret
 
 
@@ -243,7 +243,7 @@ for guid_eval in hierarchy_gpos:
             if  (version != 0) and GPO_Changed == True:
                 logger.info('GPO %s has changed' % guid)
                 try:
-                    change_backlog = gpo_parser(gpolist, test_ldb, conn, attr_log)
+                    change_backlog = gpo_parser(gpolist, test_ldb, conn, attr_log, lp)
                 except:
                     logger.error('Failed to parse gpo %s' % guid)
                     continue
-- 
2.14.2


>From 8e3f8c52c1d69ca99270f2f7499f56e07b95453e Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Thu, 25 May 2017 07:27:27 -0600
Subject: [PATCH 14/30] gpoupdate: Rewrite samba_gpoupdate

Use new python bindings and remove obsoleted code

Signed-off-by: David Mulder <dmulder at suse.com>
---
 python/samba/gpclass.py               | 189 ++++-----------------
 source4/scripting/bin/samba_gpoupdate | 301 ++++++++++------------------------
 2 files changed, 122 insertions(+), 368 deletions(-)

diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index a8b44c45dc2..31c9fc2e240 100755
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -19,6 +19,7 @@
 
 import sys
 import os
+import tdb
 sys.path.insert(0, "bin/python")
 import samba.gpo as gpo
 import optparse
@@ -33,6 +34,30 @@ from ConfigParser import ConfigParser
 from StringIO import StringIO
 from abc import ABCMeta, abstractmethod
 
+class Backlog:
+    def __init__(self, sysvol_log):
+        if os.path.isfile(sysvol_log):
+            self.backlog = tdb.open(sysvol_log)
+        else:
+            self.backlog = tdb.Tdb(sysvol_log, 0, tdb.DEFAULT, os.O_CREAT|os.O_RDWR)
+        self.backlog.transaction_start()
+
+    def version(self, guid):
+        try:
+            old_version = int(self.backlog.get(guid))
+        except TypeError:
+            old_version = -1
+        return old_version
+
+    def store(self, guid, version):
+        self.backlog.store(guid, '%i' % version)
+
+    def commit(self):
+        self.backlog.transaction_commit()
+
+    def __del__(self):
+        self.backlog.close()
+
 class gp_ext(object):
     __metaclass__ = ABCMeta
 
@@ -41,7 +66,7 @@ class gp_ext(object):
         pass
 
     @abstractmethod
-    def parse(self, afile, ldb, conn, attr_log, lp):
+    def parse(self, afile, ldb, conn, lp):
         pass
 
     @abstractmethod
@@ -52,10 +77,9 @@ class gp_ext(object):
 class inf_to():
     __metaclass__ = ABCMeta
 
-    def __init__(self, logger, ldb, dn, lp, attribute, val):
+    def __init__(self, logger, ldb, lp, attribute, val):
         self.logger = logger
         self.ldb = ldb
-        self.dn = dn
         self.attribute = attribute
         self.val = val
         self.lp = lp
@@ -128,16 +152,13 @@ class gp_sec_ext(gp_ext):
         return "Security GPO extension"
 
     def list(self, rootpath):
-        path = "%s%s" % (rootpath, "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
-        return path
+        return os.path.join(rootpath, "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
 
     def listmachpol(self, rootpath):
-        path = "%s%s" % (rootpath, "Machine/Registry.pol")
-        return path
+        return os.path.join(rootpath, "Machine/Registry.pol")
 
     def listuserpol(self, rootpath):
-        path = "%s%s" % (rootpath, "User/Registry.pol")
-        return path
+        return os.path.join(rootpath, "User/Registry.pol")
 
     def populate_inf(self):
         return {"System Access": {"MinimumPasswordAge": ("minPwdAge", inf_to_ldb),
@@ -147,14 +168,12 @@ class gp_sec_ext(gp_ext):
                                  }
                }
 
-    def read_inf(self, path, conn, attr_log):
+    def read_inf(self, path, conn):
         ret = False
         inftable = self.populate_inf()
 
         policy = conn.loadfile(path.replace('/', '\\')).decode('utf-16')
         current_section = None
-        LOG = open(attr_log, "a")
-        LOG.write(str(path.split('/')[2]) + '\n')
 
         # So here we would declare a boolean,
         # that would get changed to TRUE.
@@ -175,13 +194,12 @@ class gp_sec_ext(gp_ext):
                     (att, setter) = current_section.get(key)
                     value = value.encode('ascii', 'ignore')
                     ret = True
-                    setter(self.logger, self.ldb, self.dn, self.lp, att, value).update_samba()
+                    setter(self.logger, self.ldb, self.lp, att, value).update_samba()
         return ret
 
-    def parse(self, afile, ldb, conn, attr_log, lp):
+    def parse(self, afile, ldb, conn, lp):
         self.ldb = ldb
         self.lp = lp
-        self.dn = ldb.get_default_basedn()
 
         # Fixing the bug where only some Linux Boxes capitalize MACHINE
         if afile.endswith('inf'):
@@ -191,149 +209,12 @@ class gp_sec_ext(gp_ext):
                 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, attr_log)
+                        return self.read_inf(bfile, conn)
                     except NTSTATUSError:
                         continue
             except ValueError:
                 try:
-                    return self.read_inf(afile, conn, attr_log)
+                    return self.read_inf(afile, conn)
                 except:
                     return None
 
-
-def scan_log(sysvol_tdb):
-    data = {}
-    for key in sysvol_tdb.iterkeys():
-        data[key] = sysvol_tdb.get(key)
-    return data
-
-
-def Reset_Defaults(test_ldb):
-    test_ldb.set_minPwdAge(str(-25920000000000))
-    test_ldb.set_maxPwdAge(str(-38016000000000))
-    test_ldb.set_minPwdLength(str(7))
-    test_ldb.set_pwdProperties(str(1))
-
-
-def check_deleted(guid_list, backloggpo):
-    if backloggpo is None:
-        return False
-    for guid in backloggpo:
-        if guid not in guid_list:
-            return True
-    return False
-
-
-# The hierarchy is as per MS http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
-#
-# It does not care about local GPO, because GPO and snap-ins are not made in Linux yet.
-# It follows the linking order and children GPO are last written format.
-#
-# Also, couple further testing with call scripts entitled informant and informant2.
-# They explicitly show the returned hierarchically sorted list.
-
-
-def container_indexes(GUID_LIST):
-    '''So the original list will need to be seperated into containers.
-    Returns indexed list of when the container changes after hierarchy
-    '''
-    count = 0
-    container_indexes = []
-    while count < (len(GUID_LIST)-1):
-        if GUID_LIST[count][2] != GUID_LIST[count+1][2]:
-            container_indexes.append(count+1)
-        count += 1
-    container_indexes.append(len(GUID_LIST))
-    return container_indexes
-
-
-def sort_linked(SAMDB, guid_list, start, end):
-    '''So GPO in same level need to have link level.
-    This takes a container and sorts it.
-
-    TODO:  Small small problem, it is backwards
-    '''
-    containers = gpo_user.get_gpo_containers(SAMDB, guid_list[start][0])
-    for right_container in containers:
-        if right_container.get('dn') == guid_list[start][2]:
-            break
-    gplink = str(right_container.get('gPLink'))
-    gplink_split = gplink.split('[')
-    linked_order = []
-    ret_list = []
-    for ldap_guid in gplink_split:
-        linked_order.append(str(ldap_guid[10:48]))
-    count = len(linked_order) - 1
-    while count > 0:
-        ret_list.append([linked_order[count], guid_list[start][1], guid_list[start][2]])
-        count -= 1
-    return ret_list
-
-
-def establish_hierarchy(SamDB, GUID_LIST, DC_OU, global_dn):
-    '''Takes a list of GUID from gpo, and sorts them based on OU, and realm.
-    See http://msdn.microsoft.com/en-us/library/windows/desktop/aa374155%28v=vs.85%29.aspx
-    '''
-    final_list = []
-    count_unapplied_GPO = 0
-    for GUID in GUID_LIST:
-
-        container_iteration = 0
-        # Assume first it is not applied
-        applied = False
-        # Realm only written on last call, if the GPO is linked to multiple places
-        gpo_realm = False
-
-        # A very important call. This gets all of the linked information.
-        GPO_CONTAINERS = gpo_user.get_gpo_containers(SamDB, GUID)
-        for GPO_CONTAINER in GPO_CONTAINERS:
-
-            container_iteration += 1
-
-            if DC_OU == str(GPO_CONTAINER.get('dn')):
-                applied = True
-                insert_gpo = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
-                final_list.append(insert_gpo)
-                break
-
-            if global_dn == str(GPO_CONTAINER.get('dn')) and (len(GPO_CONTAINERS) == 1):
-                gpo_realm = True
-                applied = True
-
-
-            if global_dn == str(GPO_CONTAINER.get('dn')) and (len(GPO_CONTAINERS) > 1):
-                gpo_realm = True
-                applied = True
-
-
-            if container_iteration == len(GPO_CONTAINERS):
-                if gpo_realm == False:
-                    insert_dud = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
-                    final_list.insert(0, insert_dud)
-                    count_unapplied_GPO += 1
-                else:
-                    REALM_GPO = [GUID, applied, str(GPO_CONTAINER.get('dn'))]
-                    final_list.insert(count_unapplied_GPO, REALM_GPO)
-
-    # After GPO are sorted into containers, let's sort the containers themselves.
-    # But first we can get the GPO that we don't care about, out of the way.
-    indexed_places = container_indexes(final_list)
-    count = 0
-    unapplied_gpo = []
-    # Sorted by container
-    sorted_gpo_list = []
-
-    # Unapplied GPO live at start of list, append them to final list
-    while final_list[0][1] == False:
-        unapplied_gpo.append(final_list[count])
-        count += 1
-    count = 0
-    sorted_gpo_list += unapplied_gpo
-
-    # A single container call gets the linked order for all GPO in container.
-    # So we need one call per container - > index of the Original list
-    indexed_places.insert(0, 0)
-    while count < (len(indexed_places)-1):
-        sorted_gpo_list += (sort_linked(SamDB, final_list, indexed_places[count], indexed_places[count+1]))
-        count += 1
-    return sorted_gpo_list
diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
index 3a6ed99d7b8..bba5398571a 100755
--- a/source4/scripting/bin/samba_gpoupdate
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -3,6 +3,7 @@
 # Co-Edited by Matthieu Pattou July 2013 from original August 2013
 # Edited by Garming Sam Feb. 2014
 # Edited by Luke Morrison April 2014
+# Edited by David Mulder May 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
@@ -22,15 +23,10 @@ and sorts them by container. Then, it applies the ones that haven't been
 applied, have changed, or is in the right container'''
 
 import os
-import fcntl
 import sys
-import tempfile
-import subprocess
-import tdb
 
 sys.path.insert(0, "bin/python")
 
-import samba
 import optparse
 from samba import getopt as options
 from samba.gpclass import *
@@ -39,215 +35,92 @@ from samba.dcerpc import nbt
 from samba import smb
 import logging
 
-
-# Finds all GPO Files ending in inf
-def gp_path_list(path):
-
-    GPO_LIST = []
-    for ext in gp_extensions:
-        GPO_LIST.append((ext, ext.list(path)))
-    return GPO_LIST
-
-
-def gpo_parser(GPO_LIST, ldb, conn, attr_log, lp):
-    '''The API method to parse the GPO
-    :param GPO_LIST:
-    :param ldb: Live instance of an LDB object AKA Samba
-    :param conn: Live instance of a CIFS connection
-    :param attr_log: backlog path for GPO and attribute to be written
-    no return except a newly updated Samba
-    '''
-
-    ret = False
-    for entry in GPO_LIST:
-        (ext, thefile) = entry
-        if ret == False:
-            ret = ext.parse(thefile, ldb, conn, attr_log, lp)
-        else:
-            temp = ext.parse(thefile, ldb, conn, attr_log, lp)
-    return ret
-
-
-class GPOServiceSetup:
-    def __init__(self):
-        """Initialize all components necessary to return instances of
-        a Samba lp context (smb.conf) and Samba LDB context
-        """
-
-        self.parser = optparse.OptionParser("samba_gpoupdate [options]")
-        self.sambaopts = options.SambaOptions(self.parser)
-        self.credopts = None
-        self.opts = None
-        self.args = None
-        self.lp = None
-        self.smbconf = None
-        self.creds = None
-        self.url = None
-
-    # Setters or Initializers
-    def init_parser(self):
-        '''Get the command line options'''
-        self.parser.add_option_group(self.sambaopts)
-        self.parser.add_option_group(options.VersionOptions(self.parser))
-        self.init_credopts()
-        self.parser.add_option("-H", dest="url", help="URL for the samdb")
-        self.parser.add_option_group(self.credopts)
-
-    def init_argsopts(self):
-        '''Set the options and the arguments'''
-        (opts, args) = self.parser.parse_args()
-
-        self.opts = opts
-        self.args = args
-
-    def init_credopts(self):
-        '''Set Credential operations'''
-        self.credopts = options.CredentialsOptions(self.parser)
-
-    def init_lp(self):
-        '''Set the loadparm context'''
-        self.lp = self.sambaopts.get_loadparm()
-        self.smbconf = self.lp.configfile
-        if (not self.opts.url):
-            self.url = self.lp.samdb_url()
-        else:
-            self.url = self.opts.url
-
-    def init_session(self):
-        '''Initialize the session'''
-        self.creds = self.credopts.get_credentials(self.lp,
-            fallback_machine=True)
-        self.session = system_session()
-
-    def InitializeService(self):
-        '''Inializer for the thread'''
-        self.init_parser()
-        self.init_argsopts()
-        self.init_lp()
-        self.init_session()
-
-    # Getters
-    def Get_LDB(self):
-        '''Return a live instance of Samba'''
-        SambaDB = SamDB(self.url, session_info=self.session,
-            credentials=self.creds, lp=self.lp)
-        return SambaDB
-
-    def Get_lp_Content(self):
-        '''Return an instance of a local lp context'''
-        return self.lp
-
-    def Get_Creds(self):
-        '''Return an instance of a local creds'''
-        return self.creds
-
-
-# Set up the GPO service
-GPOService = GPOServiceSetup()
-GPOService.InitializeService()
-
-# Get the Samba Instance
-test_ldb = GPOService.Get_LDB()
-
-# Get The lp context
-lp = GPOService.Get_lp_Content()
-
-# Set up logging
-logger = logging.getLogger('samba_gpoupdate')
-logger.addHandler(logging.StreamHandler(sys.stdout))
-logger.setLevel(logging.CRITICAL)
-log_level = lp.log_level()
-if log_level == 1:
-    logger.setLevel(logging.ERROR)
-elif log_level == 2:
-    logger.setLevel(logging.WARNING)
-elif log_level == 3:
-    logger.setLevel(logging.INFO)
-elif log_level >= 4:
-    logger.setLevel(logging.DEBUG)
-
-# Get the CREDS
-creds = GPOService.Get_Creds()
-
-# Read the readable backLog into a hashmap
-# then open writable backLog in same location
-BackLoggedGPO = None
-sys_log = '%s/%s' % (lp.get("path", "sysvol"), 'gpo.tdb')
-attr_log = '%s/%s' % (lp.get("path", "sysvol"), 'attrlog.txt')
-
-
-if os.path.isfile(sys_log):
-    BackLog = tdb.open(sys_log)
-else:
-    BackLog = tdb.Tdb(sys_log, 0, tdb.DEFAULT, os.O_CREAT|os.O_RDWR)
-BackLoggedGPO = scan_log(BackLog)
-
-
-# We need to know writable DC to setup SMB connection
-net = Net(creds=creds, lp=lp)
-cldap_ret = net.finddc(domain=lp.get('realm'), flags=(nbt.NBT_SERVER_LDAP |
-    nbt.NBT_SERVER_DS))
-dc_hostname = cldap_ret.pdc_dns_name
-
-try:
-    conn = smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds)
-except Exception, e:
-    raise Exception("Error connecting to '%s' using SMB" % dc_hostname, e)
-
-# Get the dn of the domain, and the dn of readable/writable DC
-global_dn = test_ldb.domain_dn()
-DC_OU = "OU=Domain Controllers" + ',' + global_dn
-
-# Set up a List of the GUID for all GPO's
-guid_list = [x['name'] for x in conn.list('%s/Policies' % lp.get("realm").lower())]
-SYSV_PATH = '%s/%s/%s' % (lp.get("path", "sysvol"), lp.get("realm"), 'Policies')
-
-hierarchy_gpos = establish_hierarchy(test_ldb, guid_list, DC_OU, global_dn)
-change_backlog = False
-
-# Take a local list of all current GPO list and run it against previous GPO's
-# to see if something has changed. If so reset default and re-apply GPO.
-Applicable_GPO = []
-for i in hierarchy_gpos:
-    Applicable_GPO += i
-
-# Flag gets set when
-GPO_Changed = False
-GPO_Deleted = check_deleted(Applicable_GPO, BackLoggedGPO)
-if (GPO_Deleted):
-    # Null the backlog
-    BackLoggedGPO = {}
-    # Reset defaults then overwrite them
-    Reset_Defaults(test_ldb)
-    GPO_Changed = False
-
-BackLog.transaction_start()
-for guid_eval in hierarchy_gpos:
-    guid = guid_eval[0]
-    gp_extensions = [gp_sec_ext(logger)]
-    local_path = '%s/Policies' % lp.get("realm").lower() + '/' + guid + '/'
-    version = int(gpo.gpo_get_sysvol_gpt_version(lp.get("path", "sysvol") + '/' + local_path)[1])
+''' Fetch the hostname of a writable DC '''
+def get_dc_hostname():
+    net = Net(creds=creds, lp=lp)
+    cldap_ret = net.finddc(domain=lp.get('realm'), flags=(nbt.NBT_SERVER_LDAP |
+        nbt.NBT_SERVER_DS))
+    return cldap_ret.pdc_dns_name
+
+''' Fetch a list of GUIDs for applicable GPOs '''
+def get_gpo_list(dc_hostname, creds, lp):
+    gpos = []
+    ads = gpo.ADS_STRUCT(dc_hostname, lp, creds)
+    if ads.connect():
+        gpos = ads.get_gpo_list(creds.get_username())
+    return gpos
+
+if __name__ == "__main__":
+    parser = optparse.OptionParser('samba_gpoupdate [options]')
+    sambaopts = options.SambaOptions(parser)
+
+    # Get the command line options
+    parser.add_option_group(sambaopts)
+    parser.add_option_group(options.VersionOptions(parser))
+    credopts = options.CredentialsOptions(parser)
+    parser.add_option('-H', '--url', dest='url', help='URL for the samdb')
+    parser.add_option_group(credopts)
+
+    # Set the options and the arguments
+    (opts, args) = parser.parse_args()
+
+    # Set the loadparm context
+    lp = sambaopts.get_loadparm()
+    if not opts.url:
+        url = lp.samdb_url()
+    else:
+        url = opts.url
+
+    # Initialize the session
+    creds = credopts.get_credentials(lp, fallback_machine=True)
+    session = system_session()
+
+    # Set up logging
+    logger = logging.getLogger('samba_gpoupdate')
+    logger.addHandler(logging.StreamHandler(sys.stdout))
+    logger.setLevel(logging.CRITICAL)
+    log_level = lp.log_level()
+    if log_level == 1:
+        logger.setLevel(logging.ERROR)
+    elif log_level == 2:
+        logger.setLevel(logging.WARNING)
+    elif log_level == 3:
+        logger.setLevel(logging.INFO)
+    elif log_level >= 4:
+        logger.setLevel(logging.DEBUG)
+
+    '''Return a live instance of Samba'''
+    test_ldb = SamDB(url, session_info=session, credentials=creds, lp=lp)
+
+    # Read the readable backLog into a hashmap
+    # then open writable backLog in same location
+    sysvol_log = os.path.join(lp.get('cache directory'), 'gpo.tdb')
+
+    backlog = Backlog(sysvol_log)
+
+    dc_hostname = get_dc_hostname()
     try:
-        old_version = int(BackLoggedGPO.get(guid))
+        conn =  smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds)
     except:
-        old_version = -1
-    gpolist = gp_path_list(local_path)
-    if version != old_version:
-        GPO_Changed = True
-    # If the GPO has a dn that is applicable to Samba
-    if guid_eval[1]:
-        # If it has a GPO file that could apply to Samba
-        if gpolist[0][1]:
-            # If it we have not read it before and is not empty
-            # Rewrite entire logfile here
-            if  (version != 0) and GPO_Changed == True:
-                logger.info('GPO %s has changed' % guid)
-                try:
-                    change_backlog = gpo_parser(gpolist, test_ldb, conn, attr_log, lp)
-                except:
-                    logger.error('Failed to parse gpo %s' % guid)
-                    continue
-    BackLog.store(guid, '%i' % version)
-BackLog.transaction_commit()
-BackLog.close()
+        logger.error('Error connecting to \'%s\' using SMB' % dc_hostname)
+        raise
+    gpos = get_gpo_list(dc_hostname, creds, lp)
+
+    for gpo_obj in gpos:
+        guid = gpo_obj.name
+        if guid == 'Local Policy':
+            continue
+        gp_extensions = [gp_sec_ext(logger)]
+        local_path = os.path.join(lp.get('realm').lower(), 'Policies', guid)
+        version = int(gpo.gpo_get_sysvol_gpt_version(os.path.join(lp.get("path", "sysvol"), local_path))[1])
+        if version != backlog.version(guid):
+            logger.info('GPO %s has changed' % guid)
+            try:
+                for ext in gp_extensions:
+                    ext.parse(ext.list(local_path), test_ldb, conn, lp)
+            except:
+                logger.error('Failed to parse gpo %s' % guid)
+                continue
+        backlog.store(guid, version)
+    backlog.commit()
 
-- 
2.14.2


>From 56e8a5b04d585478ae3916c6b123c1e7491cad0f Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Fri, 10 Feb 2017 10:33:29 -0700
Subject: [PATCH 15/30] gpo: Install the samba_gpoupdate script

The samba_gpoupdate script was not being installed by waf.
Added samba_gpoupdate to the wscripts so it gets installed as part of a make install.

Signed-off-by: David Mulder <dmulder at suse.com>
---
 source4/scripting/bin/wscript_build | 1 +
 source4/scripting/wscript_build     | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/source4/scripting/bin/wscript_build b/source4/scripting/bin/wscript_build
index d572f5a3ab2..737f1bce411 100644
--- a/source4/scripting/bin/wscript_build
+++ b/source4/scripting/bin/wscript_build
@@ -7,5 +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='.')
diff --git a/source4/scripting/wscript_build b/source4/scripting/wscript_build
index 39408ba85b1..2b9f3b6b2ee 100644
--- a/source4/scripting/wscript_build
+++ b/source4/scripting/wscript_build
@@ -4,7 +4,7 @@ from samba_utils import MODE_755
 
 sbin_files = None
 if bld.CONFIG_SET('AD_DC_BUILD_IS_ENABLED'):
-    sbin_files = 'bin/samba_dnsupdate bin/samba_spnupdate bin/samba_upgradedns bin/samba_kcc'
+    sbin_files = 'bin/samba_dnsupdate bin/samba_spnupdate bin/samba_upgradedns bin/samba_kcc bin/samba_gpoupdate'
 
 if sbin_files:
     bld.INSTALL_FILES('${SBINDIR}',
-- 
2.14.2


>From 9fc84a1528e2d519c751a449f8ba9afde6557a5f Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Fri, 3 Mar 2017 12:54:30 -0700
Subject: [PATCH 16/30] gpo: Add gpo tests

Lays down a sysvol gpttmpl.inf with password policies, then runs the samba_gpoupdate command. Verifies policies are applied to the samdb.

Signed-off-by: David Mulder <dmulder at suse.com>
---
 python/samba/gpclass.py           |   7 +-
 selftest/target/Samba4.pm         |   1 +
 source4/selftest/tests.py         |   4 +
 source4/torture/gpo/apply.c       | 177 ++++++++++++++++++++++++++++++++++++++
 source4/torture/gpo/gpo.c         |  35 ++++++++
 source4/torture/gpo/wscript_build |  13 +++
 source4/torture/wscript_build     |   1 +
 7 files changed, 236 insertions(+), 2 deletions(-)
 create mode 100644 source4/torture/gpo/apply.c
 create mode 100644 source4/torture/gpo/gpo.c
 create mode 100644 source4/torture/gpo/wscript_build

diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index 31c9fc2e240..df9cf261f24 100755
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -172,7 +172,7 @@ class gp_sec_ext(gp_ext):
         ret = False
         inftable = self.populate_inf()
 
-        policy = conn.loadfile(path.replace('/', '\\')).decode('utf-16')
+        policy = conn.loadfile(path.replace('/', '\\'))
         current_section = None
 
         # So here we would declare a boolean,
@@ -183,7 +183,10 @@ class gp_sec_ext(gp_ext):
 
         inf_conf = ConfigParser()
         inf_conf.optionxform=str
-        inf_conf.readfp(StringIO(policy))
+        try:
+            inf_conf.readfp(StringIO(policy))
+        except:
+            inf_conf.readfp(StringIO(policy.decode('utf-16')))
 
         for section in inf_conf.sections():
             current_section = inftable.get(section)
diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm
index 8b2af7ff063..68feb6b4673 100755
--- a/selftest/target/Samba4.pm
+++ b/selftest/target/Samba4.pm
@@ -616,6 +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
 	dreplsrv:periodic_startup_interval = 0
 	dsdb:schema update allowed = yes
 
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 7362ec78e54..8d3d5261266 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -238,6 +238,10 @@ for env in ["ad_dc_ntvfs", "nt4_dc"]:
 plantestsuite("samba.blackbox.pdbtest.s4winbind(ad_dc_ntvfs)", "ad_dc_ntvfs:local", [os.path.join(bbdir, "test_pdbtest.sh"), '$SERVER', "$PREFIX", "pdbtest3", smbclient4, '$SMB_CONF_PATH', configuration + " --option='authmethods=winbind'"])
 plantestsuite("samba.blackbox.pdbtest.s4winbind_wbclient(ad_dc_ntvfs)", "ad_dc_ntvfs:local", [os.path.join(bbdir, "test_pdbtest.sh"), '$SERVER', "$PREFIX", "pdbtest4", smbclient4, '$SMB_CONF_PATH', configuration + " --option='authmethods=winbind_wbclient'"])
 
+gpo = smbtorture4_testsuites("gpo.")
+for t in gpo:
+    plansmbtorture4testsuite(t, 'ad_dc:local', ['//$SERVER/sysvol', '-U$USERNAME%$PASSWORD'])
+
 transports = ["ncacn_np", "ncacn_ip_tcp"]
 
 #Kerberos varies between functional levels, so it is important to check this on all of them
diff --git a/source4/torture/gpo/apply.c b/source4/torture/gpo/apply.c
new file mode 100644
index 00000000000..b6b9b0ee76a
--- /dev/null
+++ b/source4/torture/gpo/apply.c
@@ -0,0 +1,177 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   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 "torture/smbtorture.h"
+#include "lib/util/mkdir_p.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/session.h"
+#include "lib/ldb/include/ldb.h"
+#include "torture/gpo/proto.h"
+#include <unistd.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_gpo_system_access_policies);
+
+	suite->description = talloc_strdup(suite, "Group Policy apply tests");
+
+	return suite;
+}
+
+static int exec_wait(const char **cmd)
+{
+	int ret;
+	pid_t pid = fork();
+	switch (pid) {
+		case 0:
+			execv(cmd[0], discard_const_p(char *, &(cmd[1])));
+			ret = -1;
+			break;
+		case -1:
+			ret = errno;
+			break;
+		default:
+			if (waitpid(pid, &ret, 0) < 0)
+				ret = errno;
+			break;
+	}
+	return ret;
+}
+
+static int unix2nttime(char *sval)
+{
+	return (strtoll(sval, NULL, 10) * -1 / 60 / 60 / 24 / 10000000);
+}
+
+#define GPODIR "addom.samba.example.com/Policies/{31B2F340-016D-11D2-945F-00C04FB984F9}/MACHINE/Microsoft/Windows NT/SecEdit"
+#define GPOFILE "GptTmpl.inf"
+#define GPTTMPL "[System Access]\n\
+MinimumPasswordAge = %d\n\
+MaximumPasswordAge = %d\n\
+MinimumPasswordLength = %d\n\
+PasswordComplexity = %d\n\
+"
+#define GPTINI "addom.samba.example.com/Policies/{31B2F340-016D-11D2-945F-00C04FB984F9}/GPT.INI"
+
+bool torture_gpo_system_access_policies(struct torture_context *tctx)
+{
+	int ret, vers = 0, i;
+	const char *sysvol_path = NULL, *gpo_dir = NULL, *gpo_file = NULL, *gpt_file = NULL;
+	struct ldb_context *samdb = NULL;
+	struct ldb_result *result;
+	const char *attrs[] = {
+		"minPwdAge",
+		"maxPwdAge",
+		"minPwdLength",
+		"pwdProperties",
+		NULL
+	};
+	const struct ldb_val *val;
+	FILE *fp = NULL;
+	const char **gpo_update_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;
+
+	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(tctx, "%s/%s", sysvol_path, GPODIR);
+	mkdir_p(gpo_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+	gpo_file = talloc_asprintf(tctx, "%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(tctx, 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, tctx, &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];
+
+	for (i = 0; i < 3; i++) {
+		/* Write out the sysvol */
+		if ( (fp = fopen(gpo_file, "w")) ) {
+			fputs(talloc_asprintf(tctx, GPTTMPL, minpwdcases[i], maxpwdcases[i], pwdlencases[i], pwdpropcases[i]), fp);
+			fclose(fp);
+		}
+
+		/* Update the version in the GPT.INI */
+		gpt_file = talloc_asprintf(tctx, "%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(tctx, "[General]\nVersion=%d\n", ++vers);
+			fputs(data, fp);
+			fclose(fp);
+		}
+
+		/* Run the gpo update command */
+		ret = exec_wait(gpo_update_cmd);
+		torture_assert(tctx, ret == 0, "Failed to execute the gpo update command");
+
+		ret = ldb_search(samdb, tctx, &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 */
+		val = ldb_msg_find_ldb_val(result->msgs[0], attrs[0]);
+		torture_assert(tctx, unix2nttime((char*)val->data) == minpwdcases[i], "The minPwdAge was not applied");
+
+		/* maxPwdAge */
+		val = ldb_msg_find_ldb_val(result->msgs[0], attrs[1]);
+		torture_assert(tctx, unix2nttime((char*)val->data) == maxpwdcases[i], "The maxPwdAge was not applied");
+
+		/* minPwdLength */
+		val = ldb_msg_find_ldb_val(result->msgs[0], attrs[2]);
+		torture_assert(tctx, atoi((char*)val->data) == pwdlencases[i], "The minPwdLength was not applied");
+
+		/* pwdProperties */
+		val = ldb_msg_find_ldb_val(result->msgs[0], attrs[3]);
+		torture_assert(tctx, atoi((char*)val->data) == pwdpropcases[i], "The pwdProperties were not applied");
+	}
+
+	for (i = 0; i < old_message->num_elements; i++) {
+		old_message->elements[i].flags = LDB_FLAG_MOD_REPLACE;
+	}
+
+	ret = ldb_modify(samdb, old_message);
+	torture_assert(tctx, ret == 0, "Failed to reset password settings.");
+
+	return true;
+}
diff --git a/source4/torture/gpo/gpo.c b/source4/torture/gpo/gpo.c
new file mode 100644
index 00000000000..4a06809588e
--- /dev/null
+++ b/source4/torture/gpo/gpo.c
@@ -0,0 +1,35 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   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 "torture/smbtorture.h"
+#include "torture/gpo/proto.h"
+
+NTSTATUS torture_gpo_init(TALLOC_CTX *ctx)
+{
+    struct torture_suite *suite = torture_suite_create(ctx, "gpo");
+
+    torture_suite_add_suite(suite, gpo_apply_suite(suite));
+
+    suite->description = talloc_strdup(suite, "Group Policy tests");
+
+    torture_register_suite(ctx, suite);
+
+    return NT_STATUS_OK;
+}
diff --git a/source4/torture/gpo/wscript_build b/source4/torture/gpo/wscript_build
new file mode 100644
index 00000000000..d7b131f8095
--- /dev/null
+++ b/source4/torture/gpo/wscript_build
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+bld.SAMBA_MODULE('TORTURE_GPO',
+	source='''
+        gpo.c
+        apply.c
+        ''',
+	subsystem='smbtorture',
+	deps='torture samba-util-core ldb',
+	internal_module=True,
+	autoproto='proto.h',
+	init_function='torture_gpo_init'
+	)
diff --git a/source4/torture/wscript_build b/source4/torture/wscript_build
index fe066299ad3..aceededc9d8 100644
--- a/source4/torture/wscript_build
+++ b/source4/torture/wscript_build
@@ -33,6 +33,7 @@ bld.RECURSE('smb2')
 bld.RECURSE('winbind')
 bld.RECURSE('libnetapi')
 bld.RECURSE('libsmbclient')
+bld.RECURSE('gpo')
 
 ntvfs_specific = dict(source='', deps='')
 
-- 
2.14.2


>From a60e3146b4811cf20e541a810fca55b8347d7cb6 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Thu, 8 Jun 2017 11:47:57 -0600
Subject: [PATCH 17/30] gpo: Add GPO unapply

Keep a log of applied settings, and add an option to samba_gpoupdate to allow unapply. An unapply will revert settings to a state prior to any policy application.

Signed-off-by: David Mulder <dmulder at suse.com>
---
 python/samba/gpclass.py               | 241 ++++++++++++++++++++++++++++++----
 source4/scripting/bin/samba_gpoupdate |  90 ++++++++-----
 2 files changed, 275 insertions(+), 56 deletions(-)

diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index df9cf261f24..9972840b90a 100755
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -33,30 +33,200 @@ from samba import NTSTATUSError
 from ConfigParser import ConfigParser
 from StringIO import StringIO
 from abc import ABCMeta, abstractmethod
+import xml.etree.ElementTree as etree
+
+class gp_log:
+    ''' Log settings overwritten by gpo apply
+    The gp_log is an xml file that stores a history of gpo changes (and the original setting value).
+
+    The log is organized like so:
+
+<gp>
+    <user name="KDC-1$">
+        <applylog>
+            <guid count="0" value="{31B2F340-016D-11D2-945F-00C04FB984F9}" />
+        </applylog>
+        <guid value="{31B2F340-016D-11D2-945F-00C04FB984F9}">
+            <gp_ext name="System Access">
+                <attribute name="minPwdAge">-864000000000</attribute>
+                <attribute name="maxPwdAge">-36288000000000</attribute>
+                <attribute name="minPwdLength">7</attribute>
+                <attribute name="pwdProperties">1</attribute>
+            </gp_ext>
+            <gp_ext name="Kerberos Policy">
+                <attribute name="ticket_lifetime">1d</attribute>
+                <attribute name="renew_lifetime" />
+                <attribute name="clockskew">300</attribute>
+            </gp_ext>
+        </guid>
+    </user>
+</gp>
+
+    Each guid value contains a list of extensions, which contain a list of attributes. The guid value
+    represents a GPO. The attributes are the values of those settings prior to the application of
+    the GPO.
+    The list of guids is enclosed within a user name, which represents the user the settings were
+    applied to. This user may be the samaccountname of the local computer, which implies that these
+    are machine policies.
+    The applylog keeps track of the order in which the GPOs were applied, so that they can be rolled
+    back in reverse, returning the machine to the state prior to policy application.
+    '''
+    def __init__(self, user, gpostore, db_log=None):
+        ''' Initialize the gp_log
+        param user          - the username (or machine name) that policies are being applied to
+        param gpostore      - the GPOStorage obj which references the tdb which contains gp_logs
+        param db_log        - (optional) a string to initialize the gp_log
+        '''
+        self.gpostore = gpostore
+        self.username = user
+        if db_log:
+            self.gpdb = etree.fromstring(db_log)
+        else:
+            self.gpdb = etree.Element('gp')
+        self.user = self.gpdb.find('user[@name="%s"]' % user)
+        if self.user is None:
+            self.user = etree.SubElement(self.gpdb, 'user')
+            self.user.attrib['name'] = user
+
+    def set_guid(self, guid):
+        ''' Log to a different GPO guid
+        param guid          - guid value of the GPO from which we're applying policy
+        '''
+        self.guid = self.user.find('guid[@value="%s"]' % guid)
+        if self.guid is None:
+            self.guid = etree.SubElement(self.user, 'guid')
+            self.guid.attrib['value'] = guid
+        apply_log = self.user.find('applylog')
+        if apply_log is None:
+            apply_log = etree.SubElement(self.user, 'applylog')
+        item = etree.SubElement(apply_log, 'guid')
+        item.attrib['count'] = '%d' % (len(apply_log)-1)
+        item.attrib['value'] = guid
+
+    def apply_log_pop(self):
+        ''' Pop a GPO guid from the applylog
+        return              - last applied GPO guid
+
+        Removes the GPO guid last added to the list, which is the most recently applied GPO.
+        '''
+        apply_log = self.user.find('applylog')
+        if apply_log is not None:
+            ret = apply_log.find('guid[@count="%d"]' % (len(apply_log)-1))
+            if ret is not None:
+                apply_log.remove(ret)
+                return ret.attrib['value']
+            if len(apply_log) == 0 and apply_log in self.user:
+                self.user.remove(apply_log)
+        return None
+
+    def store(self, gp_ext_name, attribute, old_val):
+        ''' Store an attribute in the gp_log
+        param gp_ext_name   - Name of the extension applying policy
+        param attribute     - The attribute being modified
+        param old_val       - The value of the attribute prior to policy application
+        '''
+        assert self.guid is not None, "gpo guid was not set"
+        ext = self.guid.find('gp_ext[@name="%s"]' % gp_ext_name)
+        if ext is None:
+            ext = etree.SubElement(self.guid, 'gp_ext')
+            ext.attrib['name'] = gp_ext_name
+        attr = ext.find('attribute[@name="%s"]' % attribute)
+        if attr is None:
+            attr = etree.SubElement(ext, 'attribute')
+            attr.attrib['name'] = attribute
+        attr.text = old_val
+
+    def retrieve(self, gp_ext_name, attribute):
+        ''' Retrieve a stored attribute from the gp_log
+        param gp_ext_name   - Name of the extension which applied policy
+        param attribute     - The attribute being retrieved
+        return              - The value of the attribute prior to policy application
+        '''
+        assert self.guid is not None, "gpo guid was not set"
+        ext = self.guid.find('gp_ext[@name="%s"]' % gp_ext_name)
+        if ext is not None:
+            attr = ext.find('attribute[@name="%s"]' % attribute)
+            if attr is not None:
+                return attr.text
+        return None
+
+    def list(self, gp_extensions):
+        ''' Return a list of attributes, their previous values, and functions to set them
+        param gp_extensions - list of extension objects, for retrieving attr to func mappings
+        return              - list of (attr, value, apply_func) tuples for unapplying policy
+        '''
+        assert self.guid is not None, "gpo guid was not set"
+        ret = []
+        data_maps = {}
+        for gp_ext in gp_extensions:
+            data_maps.update(gp_ext.apply_map())
+        exts = self.guid.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']]))
+        return ret
 
-class Backlog:
-    def __init__(self, sysvol_log):
-        if os.path.isfile(sysvol_log):
-            self.backlog = tdb.open(sysvol_log)
+    def delete(self, gp_ext_name, attribute):
+        ''' Remove an attribute from the gp_log
+        param gp_ext_name   - name of extension from which to remove the attribute
+        param attribute     - attribute to remove
+        '''
+        assert self.guid is not None, "gpo guid was not set"
+        ext = self.guid.find('gp_ext[@name="%s"]' % gp_ext_name)
+        if ext is not None:
+            attr = ext.find('attribute[@name="%s"]' % attribute)
+            if attr is not None:
+                ext.remove(attr)
+                if len(ext) == 0:
+                    self.guid.remove(ext)
+
+    def commit(self):
+        ''' Write gp_log changes to disk '''
+        if len(self.guid) == 0 and self.guid in self.user:
+            self.user.remove(self.guid)
+        if len(self.user) == 0 and self.user in self.gpdb:
+            self.gpdb.remove(self.user)
+        self.gpostore.store(self.username, etree.tostring(self.gpdb, 'utf-8'))
+
+class GPOStorage:
+    def __init__(self, log_file):
+        if os.path.isfile(log_file):
+            self.log = tdb.open(log_file)
         else:
-            self.backlog = tdb.Tdb(sysvol_log, 0, tdb.DEFAULT, os.O_CREAT|os.O_RDWR)
-        self.backlog.transaction_start()
+            self.log = tdb.Tdb(log_file, 0, tdb.DEFAULT, os.O_CREAT|os.O_RDWR)
 
-    def version(self, guid):
+    def start(self):
+        self.log.transaction_start()
+
+    def get_int(self, key):
         try:
-            old_version = int(self.backlog.get(guid))
+            return int(self.log.get(key))
         except TypeError:
-            old_version = -1
-        return old_version
+            return None
+
+    def get(self, key):
+        return self.log.get(key)
+
+    def get_gplog(self, user):
+        return gp_log(user, self, self.log.get(user))
+
+    def store(self, key, val):
+        self.log.store(key, val)
+
+    def cancel(self):
+        self.log.transaction_cancel()
 
-    def store(self, guid, version):
-        self.backlog.store(guid, '%i' % version)
+    def delete(self, key):
+        self.log.delete(key)
 
     def commit(self):
-        self.backlog.transaction_commit()
+        self.log.transaction_commit()
 
     def __del__(self):
-        self.backlog.close()
+        self.log.close()
 
 class gp_ext(object):
     __metaclass__ = ABCMeta
@@ -66,23 +236,27 @@ class gp_ext(object):
         pass
 
     @abstractmethod
-    def parse(self, afile, ldb, conn, lp):
+    def apply_map(self):
         pass
 
     @abstractmethod
-    def __str__(self):
+    def parse(self, afile, ldb, conn, gp_db, lp):
         pass
 
+    @abstractmethod
+    def __str__(self):
+        pass
 
 class inf_to():
     __metaclass__ = ABCMeta
 
-    def __init__(self, logger, ldb, lp, attribute, val):
+    def __init__(self, logger, ldb, gp_db, lp, attribute, val):
         self.logger = logger
         self.ldb = ldb
         self.attribute = attribute
         self.val = val
         self.lp = lp
+        self.gp_db = gp_db
 
     def explicit(self):
         return self.val
@@ -95,6 +269,10 @@ class inf_to():
     def mapper(self):
         pass
 
+    @abstractmethod
+    def __str__(self):
+        pass
+
 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
@@ -102,19 +280,27 @@ class inf_to_ldb(inf_to):
     '''
 
     def ch_minPwdAge(self, val):
-        self.logger.info('KDC Minimum Password age was changed from %s to %s' % (self.ldb.get_minPwdAge(), 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):
-        self.logger.info('KDC Maximum Password age was changed from %s to %s' % (self.ldb.get_maxPwdAge(), 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):
-        self.logger.info('KDC Minimum Password length was changed from %s to %s' % (self.ldb.get_minPwdLength(), 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):
-        self.logger.info('KDC Password Properties were changed from %s to %s' % (self.ldb.get_pwdProperties(), 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 nttime2unix(self):
@@ -136,6 +322,9 @@ class inf_to_ldb(inf_to):
 
                }
 
+    def __str__(self):
+        return 'System Access'
+
 
 class gp_sec_ext(gp_ext):
     '''This class does the following two things:
@@ -160,7 +349,7 @@ class gp_sec_ext(gp_ext):
     def listuserpol(self, rootpath):
         return os.path.join(rootpath, "User/Registry.pol")
 
-    def populate_inf(self):
+    def apply_map(self):
         return {"System Access": {"MinimumPasswordAge": ("minPwdAge", inf_to_ldb),
                                   "MaximumPasswordAge": ("maxPwdAge", inf_to_ldb),
                                   "MinimumPasswordLength": ("minPwdLength", inf_to_ldb),
@@ -170,7 +359,7 @@ class gp_sec_ext(gp_ext):
 
     def read_inf(self, path, conn):
         ret = False
-        inftable = self.populate_inf()
+        inftable = self.apply_map()
 
         policy = conn.loadfile(path.replace('/', '\\'))
         current_section = None
@@ -197,11 +386,13 @@ class gp_sec_ext(gp_ext):
                     (att, setter) = current_section.get(key)
                     value = value.encode('ascii', 'ignore')
                     ret = True
-                    setter(self.logger, self.ldb, self.lp, att, value).update_samba()
+                    setter(self.logger, self.ldb, self.gp_db, self.lp, att, value).update_samba()
+                    self.gp_db.commit()
         return ret
 
-    def parse(self, afile, ldb, conn, lp):
+    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
diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
index bba5398571a..721d6071bd1 100755
--- a/source4/scripting/bin/samba_gpoupdate
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -50,6 +50,55 @@ def get_gpo_list(dc_hostname, creds, lp):
         gpos = ads.get_gpo_list(creds.get_username())
     return gpos
 
+def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
+    gp_db = store.get_gplog(creds.get_username())
+    dc_hostname = get_dc_hostname()
+    try:
+        conn =  smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds)
+    except:
+        logger.error('Error connecting to \'%s\' using SMB' % dc_hostname)
+        raise
+    gpos = get_gpo_list(dc_hostname, creds, lp)
+
+    for gpo_obj in gpos:
+        guid = gpo_obj.name
+        if guid == 'Local Policy':
+            continue
+        local_path = os.path.join(lp.get('realm').lower(), 'Policies', guid)
+        version = int(gpo.gpo_get_sysvol_gpt_version(os.path.join(lp.get("path", "sysvol"), local_path))[1])
+        if version != store.get_int(guid):
+            logger.info('GPO %s has changed' % guid)
+            gp_db.set_guid(guid)
+            store.start()
+            try:
+                for ext in gp_extensions:
+                    ext.parse(ext.list(local_path), test_ldb, conn, gp_db, lp)
+            except:
+                logger.error('Failed to parse gpo %s' % guid)
+                store.cancel()
+                continue
+            store.store(guid, '%i' % version)
+        store.commit()
+
+def unapply_log(gp_db):
+    while True:
+        item = gp_db.apply_log_pop()
+        if item:
+            yield item
+        else:
+            break
+
+def unapply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
+    gp_db = store.get_gplog(creds.get_username())
+    for gpo_guid in unapply_log(gp_db):
+        gp_db.set_guid(gpo_guid)
+        unapply_attributes = gp_db.list(gp_extensions)
+        for attr in unapply_attributes:
+            attr_obj = attr[-1](logger, test_ldb, gp_db, lp, attr[0], attr[1])
+            attr_obj.mapper()[attr[0]][0](attr[1]) # Set the old value
+            gp_db.delete(str(attr_obj), attr[0])
+        gp_db.commit()
+
 if __name__ == "__main__":
     parser = optparse.OptionParser('samba_gpoupdate [options]')
     sambaopts = options.SambaOptions(parser)
@@ -59,6 +108,7 @@ if __name__ == "__main__":
     parser.add_option_group(options.VersionOptions(parser))
     credopts = options.CredentialsOptions(parser)
     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_group(credopts)
 
     # Set the options and the arguments
@@ -89,38 +139,16 @@ if __name__ == "__main__":
     elif log_level >= 4:
         logger.setLevel(logging.DEBUG)
 
-    '''Return a live instance of Samba'''
-    test_ldb = SamDB(url, session_info=session, credentials=creds, lp=lp)
-
-    # Read the readable backLog into a hashmap
-    # then open writable backLog in same location
-    sysvol_log = os.path.join(lp.get('cache directory'), 'gpo.tdb')
+    cache_dir = lp.get('cache directory')
+    store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb'))
 
-    backlog = Backlog(sysvol_log)
+    gp_extensions = [gp_sec_ext(logger)]
 
-    dc_hostname = get_dc_hostname()
-    try:
-        conn =  smb.SMB(dc_hostname, 'sysvol', lp=lp, creds=creds)
-    except:
-        logger.error('Error connecting to \'%s\' using SMB' % dc_hostname)
-        raise
-    gpos = get_gpo_list(dc_hostname, creds, lp)
+    # Get a live instance of Samba
+    test_ldb = SamDB(url, session_info=session, credentials=creds, lp=lp)
 
-    for gpo_obj in gpos:
-        guid = gpo_obj.name
-        if guid == 'Local Policy':
-            continue
-        gp_extensions = [gp_sec_ext(logger)]
-        local_path = os.path.join(lp.get('realm').lower(), 'Policies', guid)
-        version = int(gpo.gpo_get_sysvol_gpt_version(os.path.join(lp.get("path", "sysvol"), local_path))[1])
-        if version != backlog.version(guid):
-            logger.info('GPO %s has changed' % guid)
-            try:
-                for ext in gp_extensions:
-                    ext.parse(ext.list(local_path), test_ldb, conn, lp)
-            except:
-                logger.error('Failed to parse gpo %s' % guid)
-                continue
-        backlog.store(guid, version)
-    backlog.commit()
+    if not opts.unapply:
+        apply_gp(lp, creds, test_ldb, logger, store, gp_extensions)
+    else:
+        unapply_gp(lp, creds, test_ldb, logger, store, gp_extensions)
 
-- 
2.14.2


>From 1231f1dc1551a61755f9a1abf6317855e4ae1439 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Mon, 12 Jun 2017 16:00:38 -0600
Subject: [PATCH 18/30] gpo: Always enforce policy, even if unchanged

Policies should always be enforced, even if the gpo hasn't changed.

Signed-off-by: David Mulder <dmulder at suse.com>
---
 python/samba/gpclass.py               | 45 ++++++++++++++++++++++++++++++-----
 source4/scripting/bin/samba_gpoupdate | 24 +++++++++++--------
 2 files changed, 53 insertions(+), 16 deletions(-)

diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index 9972840b90a..bed1684dad3 100755
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -35,6 +35,15 @@ from StringIO import StringIO
 from abc import ABCMeta, abstractmethod
 import xml.etree.ElementTree as etree
 
+try:
+    from enum import Enum
+    GPOSTATE = Enum('GPOSTATE', 'APPLY ENFORCE UNAPPLY')
+except ImportError:
+    class GPOSTATE:
+        APPLY = 1
+        ENFORCE = 2
+        UNAPPLY = 3
+
 class gp_log:
     ''' Log settings overwritten by gpo apply
     The gp_log is an xml file that stores a history of gpo changes (and the original setting value).
@@ -77,6 +86,7 @@ class gp_log:
         param gpostore      - the GPOStorage obj which references the tdb which contains gp_logs
         param db_log        - (optional) a string to initialize the gp_log
         '''
+        self._state = GPOSTATE.APPLY
         self.gpostore = gpostore
         self.username = user
         if db_log:
@@ -88,6 +98,26 @@ class gp_log:
             self.user = etree.SubElement(self.gpdb, 'user')
             self.user.attrib['name'] = user
 
+    def state(self, value):
+        ''' Policy application state
+        param value         - APPLY, ENFORCE, or UNAPPLY
+
+        The behavior of the gp_log depends on whether we are applying policy, enforcing policy,
+        or unapplying policy. During an apply, old settings are recorded in the log. During an
+        enforce, settings are being applied but the gp_log does not change. During an unapply,
+        additions to the log should be ignored (since function calls to apply settings are actually
+        reverting policy), but removals from the log are allowed.
+        '''
+        # If we're enforcing, but we've unapplied, apply instead
+        if value == GPOSTATE.ENFORCE:
+            apply_log = self.user.find('applylog')
+            if apply_log is None or len(apply_log) == 0:
+                self._state = GPOSTATE.APPLY
+            else:
+                self._state = value
+        else:
+            self._state = value
+
     def set_guid(self, guid):
         ''' Log to a different GPO guid
         param guid          - guid value of the GPO from which we're applying policy
@@ -96,12 +126,13 @@ class gp_log:
         if self.guid is None:
             self.guid = etree.SubElement(self.user, 'guid')
             self.guid.attrib['value'] = guid
-        apply_log = self.user.find('applylog')
-        if apply_log is None:
-            apply_log = etree.SubElement(self.user, 'applylog')
-        item = etree.SubElement(apply_log, 'guid')
-        item.attrib['count'] = '%d' % (len(apply_log)-1)
-        item.attrib['value'] = guid
+        if self._state == GPOSTATE.APPLY:
+            apply_log = self.user.find('applylog')
+            if apply_log is None:
+                apply_log = etree.SubElement(self.user, 'applylog')
+            item = etree.SubElement(apply_log, 'guid')
+            item.attrib['count'] = '%d' % (len(apply_log)-1)
+            item.attrib['value'] = guid
 
     def apply_log_pop(self):
         ''' Pop a GPO guid from the applylog
@@ -125,6 +156,8 @@ class gp_log:
         param attribute     - The attribute being modified
         param old_val       - The value of the attribute prior to policy application
         '''
+        if self._state == GPOSTATE.UNAPPLY or self._state == GPOSTATE.ENFORCE:
+            return None
         assert self.guid is not None, "gpo guid was not set"
         ext = self.guid.find('gp_ext[@name="%s"]' % gp_ext_name)
         if ext is None:
diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
index 721d6071bd1..568830a7461 100755
--- a/source4/scripting/bin/samba_gpoupdate
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -68,16 +68,19 @@ def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
         version = int(gpo.gpo_get_sysvol_gpt_version(os.path.join(lp.get("path", "sysvol"), local_path))[1])
         if version != store.get_int(guid):
             logger.info('GPO %s has changed' % guid)
-            gp_db.set_guid(guid)
-            store.start()
-            try:
-                for ext in gp_extensions:
-                    ext.parse(ext.list(local_path), test_ldb, conn, gp_db, lp)
-            except:
-                logger.error('Failed to parse gpo %s' % guid)
-                store.cancel()
-                continue
-            store.store(guid, '%i' % version)
+            gp_db.state(GPOSTATE.APPLY)
+        else:
+            gp_db.state(GPOSTATE.ENFORCE)
+        gp_db.set_guid(guid)
+        store.start()
+        try:
+            for ext in gp_extensions:
+                ext.parse(ext.list(local_path), test_ldb, conn, gp_db, lp)
+        except:
+            logger.error('Failed to parse gpo %s' % guid)
+            store.cancel()
+            continue
+        store.store(guid, '%i' % version)
         store.commit()
 
 def unapply_log(gp_db):
@@ -90,6 +93,7 @@ def unapply_log(gp_db):
 
 def unapply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
     gp_db = store.get_gplog(creds.get_username())
+    gp_db.state(GPOSTATE.UNAPPLY)
     for gpo_guid in unapply_log(gp_db):
         gp_db.set_guid(gpo_guid)
         unapply_attributes = gp_db.list(gp_extensions)
-- 
2.14.2


>From d9f52e9b4feda767495be48fcd81b5e9c55c69a7 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Wed, 9 Aug 2017 11:30:00 -0600
Subject: [PATCH 19/30] gpo: Apply kerberos settings

Add kdc kerberos settings to gpo.tdb, then retrieve those settings in
lpcfg_default_kdc_policy.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Signed-off-by: David Mulder <dmulder at suse.com>
---
 lib/param/param.h                   |  3 ++-
 lib/param/util.c                    | 49 +++++++++++++++++++++++++++++++------
 python/samba/gpclass.py             | 32 +++++++++++++++++++++++-
 source4/kdc/db-glue.c               |  3 ++-
 source4/rpc_server/lsa/dcesrv_lsa.c |  9 +++----
 5 files changed, 81 insertions(+), 15 deletions(-)

diff --git a/lib/param/param.h b/lib/param/param.h
index 680c053a6cc..0a3bde6c5cb 100644
--- a/lib/param/param.h
+++ b/lib/param/param.h
@@ -289,7 +289,8 @@ const char *lpcfg_imessaging_path(TALLOC_CTX *mem_ctx,
 const char *lpcfg_sam_name(struct loadparm_context *lp_ctx);
 const char *lpcfg_sam_dnsname(struct loadparm_context *lp_ctx);
 
-void lpcfg_default_kdc_policy(struct loadparm_context *lp_ctx,
+void lpcfg_default_kdc_policy(TALLOC_CTX *mem_ctx,
+				struct loadparm_context *lp_ctx,
 				time_t *svc_tkt_lifetime,
 				time_t *usr_tkt_lifetime,
 				time_t *renewal_lifetime);
diff --git a/lib/param/util.c b/lib/param/util.c
index 52796562ec5..cd8e74b9d8f 100644
--- a/lib/param/util.c
+++ b/lib/param/util.c
@@ -29,6 +29,7 @@
 #include "system/dir.h"
 #include "param/param.h"
 #include "libds/common/roles.h"
+#include "tdb.h"
 
 /**
  * @file
@@ -270,22 +271,56 @@ const char *lpcfg_sam_dnsname(struct loadparm_context *lp_ctx)
 	}
 }
 
-void lpcfg_default_kdc_policy(struct loadparm_context *lp_ctx,
+static long tdb_fetch_lifetime(TALLOC_CTX *mem_ctx, struct tdb_context *tdb, const char *keystr)
+{
+	TDB_DATA key;
+	TDB_DATA ret;
+	char *tmp = NULL;
+	long result;
+
+	key.dptr = discard_const_p(unsigned char, keystr);
+	key.dsize = strlen(keystr);
+
+	if (!key.dptr)
+		return -1;
+
+	ret = tdb_fetch(tdb, key);
+	if (ret.dsize == 0)
+		return -1;
+
+	tmp = talloc_realloc(mem_ctx, tmp, char, ret.dsize+1);
+	memset(tmp, 0, ret.dsize+1);
+	memcpy(tmp, ret.dptr, ret.dsize);
+	free(ret.dptr);
+
+	result = atol(tmp);
+	talloc_free(tmp);
+	return result;
+}
+
+void lpcfg_default_kdc_policy(TALLOC_CTX *mem_ctx,
+				struct loadparm_context *lp_ctx,
 				time_t *svc_tkt_lifetime,
 				time_t *usr_tkt_lifetime,
 				time_t *renewal_lifetime)
 {
 	long val;
+	TDB_CONTEXT *ctx = NULL;
+	const char *kdc_tdb = NULL;
+
+	kdc_tdb = lpcfg_cache_path(mem_ctx, lp_ctx, "gpo.tdb");
+	if (kdc_tdb)
+		ctx = tdb_open(kdc_tdb, 0, TDB_DEFAULT, O_RDWR, 0600);
 
-	val = lpcfg_parm_long(lp_ctx, NULL,
-				"kdc", "service ticket lifetime", 10);
+	if (!ctx || ( val = tdb_fetch_lifetime(mem_ctx, ctx, "kdc:service_ticket_lifetime") ) == -1 )
+		val = lpcfg_parm_long(lp_ctx, NULL, "kdc", "service ticket lifetime", 10);
 	*svc_tkt_lifetime = val * 60 * 60;
 
-	val = lpcfg_parm_long(lp_ctx, NULL,
-				"kdc", "user ticket lifetime", 10);
+	if (!ctx || ( val = tdb_fetch_lifetime(mem_ctx, ctx, "kdc:user_ticket_lifetime") ) == -1 )
+		val = lpcfg_parm_long(lp_ctx, NULL, "kdc", "user ticket lifetime", 10);
 	*usr_tkt_lifetime = val * 60 * 60;
 
-	val = lpcfg_parm_long(lp_ctx, NULL,
-				"kdc", "renewal lifetime", 24 * 7);
+	if (!ctx || ( val = tdb_fetch_lifetime(mem_ctx, ctx, "kdc:renewal_lifetime") ) == -1 )
+		val = lpcfg_parm_long(lp_ctx, NULL, "kdc", "renewal lifetime", 24 * 7);
 	*renewal_lifetime = val * 60 * 60;
 }
diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index bed1684dad3..233cd40f6d4 100755
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -306,6 +306,32 @@ 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
@@ -387,7 +413,11 @@ class gp_sec_ext(gp_ext):
                                   "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):
diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c
index 9ac5a1d38f0..69c54b00c5b 100644
--- a/source4/kdc/db-glue.c
+++ b/source4/kdc/db-glue.c
@@ -2730,7 +2730,8 @@ NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_conte
 	kdc_db_ctx->msg_ctx = base_ctx->msg_ctx;
 
 	/* get default kdc policy */
-	lpcfg_default_kdc_policy(base_ctx->lp_ctx,
+	lpcfg_default_kdc_policy(mem_ctx,
+				 base_ctx->lp_ctx,
 				 &kdc_db_ctx->policy.svc_tkt_lifetime,
 				 &kdc_db_ctx->policy.usr_tkt_lifetime,
 				 &kdc_db_ctx->policy.renewal_lifetime);
diff --git a/source4/rpc_server/lsa/dcesrv_lsa.c b/source4/rpc_server/lsa/dcesrv_lsa.c
index 3cccd57e793..8b212222bc9 100644
--- a/source4/rpc_server/lsa/dcesrv_lsa.c
+++ b/source4/rpc_server/lsa/dcesrv_lsa.c
@@ -4041,7 +4041,8 @@ static NTSTATUS dcesrv_lsa_SetInfoPolicy2(struct dcesrv_call_state *dce_call,
 	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
 }
 
-static void kdc_get_policy(struct loadparm_context *lp_ctx,
+static void kdc_get_policy(TALLOC_CTX *mem_ctx,
+			   struct loadparm_context *lp_ctx,
 			   struct smb_krb5_context *smb_krb5_context,
 			   struct lsa_DomainInfoKerberos *k)
 {
@@ -4049,12 +4050,10 @@ static void kdc_get_policy(struct loadparm_context *lp_ctx,
 	time_t usr_tkt_lifetime;
 	time_t renewal_lifetime;
 
-	/* These should be set and stored via Group Policy, but until then, some defaults are in order */
-
 	/* Our KDC always re-validates the client */
 	k->authentication_options = LSA_POLICY_KERBEROS_VALIDATE_CLIENT;
 
-	lpcfg_default_kdc_policy(lp_ctx, &svc_tkt_lifetime,
+	lpcfg_default_kdc_policy(mem_ctx, lp_ctx, &svc_tkt_lifetime,
 				 &usr_tkt_lifetime, &renewal_lifetime);
 
 	unix_to_nt_time(&k->service_tkt_lifetime, svc_tkt_lifetime);
@@ -4103,7 +4102,7 @@ static NTSTATUS dcesrv_lsa_QueryDomainInformationPolicy(struct dcesrv_call_state
 			*r->out.info = NULL;
 			return NT_STATUS_INTERNAL_ERROR;
 		}
-		kdc_get_policy(dce_call->conn->dce_ctx->lp_ctx,
+		kdc_get_policy(mem_ctx, dce_call->conn->dce_ctx->lp_ctx,
 			       smb_krb5_context,
 			       k);
 		talloc_free(smb_krb5_context);
-- 
2.14.2


>From faf2a08f19e78b50f6cced84a4cbe69dd3a2b153 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Mon, 10 Jul 2017 13:57:21 -0600
Subject: [PATCH 20/30] doc: Add samba_gpoupdate man page, update WHATSNEW

Signed-off-by: David Mulder <dmulder at suse.com>
Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 WHATSNEW.txt                                |  13 ++++
 source4/scripting/man/samba_gpoupdate.8.xml | 117 ++++++++++++++++++++++++++++
 source4/scripting/wscript_build             |   3 +
 3 files changed, 133 insertions(+)
 create mode 100644 source4/scripting/man/samba_gpoupdate.8.xml

diff --git a/WHATSNEW.txt b/WHATSNEW.txt
index cc03f2016c8..c7ddc10b81c 100644
--- a/WHATSNEW.txt
+++ b/WHATSNEW.txt
@@ -35,6 +35,19 @@ external crypto library performance reaches parity.
 The default is to build without setting --accel-aes, which uses the
 existing Samba software AES implementation.
 
+KDC GPO application
+-------------------
+
+Adds Group Policy support for the samba kdc. Applies password policies
+(minimum/maximum password age, minimum password length, and password
+complexity) and kerberos policies (user/service ticket lifetime and
+renew lifetime).
+
+Adds the samba_gpoupdate script for applying and unapplying
+policy. Can be applied automatically by setting
+
+ 'server services = +gpoupdate'.
+
 smb.conf changes
 ================
 
diff --git a/source4/scripting/man/samba_gpoupdate.8.xml b/source4/scripting/man/samba_gpoupdate.8.xml
new file mode 100644
index 00000000000..0c3a0a812a1
--- /dev/null
+++ b/source4/scripting/man/samba_gpoupdate.8.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<refentry id="samba_gpoupdate.8">
+<refentryinfo><date>2017-07-11</date></refentryinfo>
+
+<refmeta>
+	<refentrytitle>SAMBA_GPOUPDATE</refentrytitle>
+	<manvolnum>8</manvolnum>
+	<refmiscinfo class="source">Samba</refmiscinfo>
+	<refmiscinfo class="manual">System Administration tools</refmiscinfo>
+	<refmiscinfo class="version">4.8.0</refmiscinfo>
+</refmeta>
+
+<refnamediv>
+	<refname>samba_gpoupdate</refname>
+	<refpurpose>apply group policy</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+	<cmdsynopsis>
+		<command>samba_gpoupdate</command>
+	</cmdsynopsis>
+
+	<cmdsynopsis>
+		<command>samba_gpoupdate</command>
+		<arg choice="opt">
+		<replaceable>options</replaceable>
+		</arg>
+	</cmdsynopsis>
+
+</refsynopsisdiv>
+
+
+<refsect1>
+	<title>DESCRIPTION</title>
+	<para>This tool is part of the
+	<citerefentry><refentrytitle>samba</refentrytitle>
+	<manvolnum>1</manvolnum></citerefentry> suite.</para>
+
+	<para><command>samba_gpoupdate</command> a script for
+	applying and unapplying Group Policy. Group Policy
+	application is experimental. Currently this applies
+	password policies (minimum/maximum password age,
+	minimum password length, and password complexity) and
+	kerberos policies (user/service ticket lifetime and
+	renew lifetime).</para>
+
+</refsect1>
+
+<refsect1>
+	<title>OPTIONS</title>
+
+<para><option>-h</option>, <option>--help</option>
+       show this help message and exit</para>
+
+<para><option>-H </option>URL, <option>--url</option>=<emphasis remap="I">URL</emphasis>
+       URL for the samdb</para>
+
+<para><option>-X</option>, <option>--unapply</option>
+       Unapply Group Policy</para>
+
+<para>Samba Common Options:</para>
+
+<para><option>-s </option>FILE, <option>--configfile</option>=<emphasis remap="I">FILE</emphasis>
+       Configuration file</para>
+
+<para><option>-d </option>DEBUGLEVEL, <option>--debuglevel</option>=<emphasis remap="I">DEBUGLEVEL</emphasis>
+       debug level</para>
+
+<para><option>--option</option>=<emphasis remap="I">OPTION</emphasis>
+       set smb.conf option from command line</para>
+
+<para><option>--realm</option>=<emphasis remap="I">REALM</emphasis>
+       set the realm name</para>
+
+<para>Version Options:</para>
+
+<para><option>-V</option>, <option>--version</option>
+       Display version number</para>
+
+<para>Credentials Options:</para>
+
+<para><option>--simple-bind-dn</option>=<emphasis remap="I">DN</emphasis>
+       DN to use for a simple bind</para>
+
+<para><option>--password</option>=<emphasis remap="I">PASSWORD</emphasis>
+       Password</para>
+
+<para><option>-U </option>USERNAME, <option>--username</option>=<emphasis remap="I">USERNAME</emphasis>
+       Username</para>
+
+<para><option>-W </option>WORKGROUP, <option>--workgroup</option>=<emphasis remap="I">WORKGROUP</emphasis>
+       Workgroup</para>
+
+<para><option>-N</option>, <option>--no-pass</option>
+       Don't ask for a password</para>
+
+<para><option>-k </option>KERBEROS, <option>--kerberos</option>=<emphasis remap="I">KERBEROS</emphasis>
+       Use Kerberos</para>
+
+<para><option>--ipaddress</option>=<emphasis remap="I">IPADDRESS</emphasis>
+       IP address of server</para>
+
+<para><option>-P</option>, <option>--machine-pass</option>
+       Use stored machine account password</para>
+
+</refsect1>
+
+<refsect1>
+	<title>AUTHOR</title>
+	<para>The original Samba software and related utilities were
+	created by Andrew Tridgell. Samba is now developed by the
+	Samba Team as an Open Source project similar to the way the
+	Linux kernel is developed.</para>
+</refsect1>
+
+</refentry>
diff --git a/source4/scripting/wscript_build b/source4/scripting/wscript_build
index 2b9f3b6b2ee..eb11c4202fe 100644
--- a/source4/scripting/wscript_build
+++ b/source4/scripting/wscript_build
@@ -5,11 +5,14 @@ from samba_utils import MODE_755
 sbin_files = None
 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'
 
 if sbin_files:
     bld.INSTALL_FILES('${SBINDIR}',
                       sbin_files,
                       chmod=MODE_755, python_fixup=True, flat=True)
+    if 'XSLTPROC_MANPAGES' in bld.env and bld.env['XSLTPROC_MANPAGES']:
+        bld.MANPAGES(man_files, True)
 
 if bld.CONFIG_SET('AD_DC_BUILD_IS_ENABLED'):
     bld.INSTALL_FILES('${BINDIR}',
-- 
2.14.2


>From 9bdb3cfb998eddcf99ea5a67e8e06f23b04e68f1 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 15:58:45 +1300
Subject: [PATCH 21/30] pygpo: Check for errors in
 gpo.gpo_get_sysvol_gpt_version()

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 libgpo/pygpo.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c
index 3a31c59d6cc..757c713b027 100644
--- a/libgpo/pygpo.c
+++ b/libgpo/pygpo.c
@@ -25,6 +25,7 @@
 #include "secrets.h"
 #include "../libds/common/flags.h"
 #include "auth/credentials/pycredentials.h"
+#include "libcli/util/pyerrors.h"
 
 /* A Python C API module to use LIBGPO */
 
@@ -288,13 +289,22 @@ static PyObject *py_gpo_get_sysvol_gpt_version(PyObject * self, PyObject * args)
 	char *display_name = NULL;
 	uint32_t sysvol_version = 0;
 	PyObject *result;
+	NTSTATUS status;
 
 	tmp_ctx = talloc_new(NULL);
 
 	if (!PyArg_ParseTuple(args, "s", &unix_path)) {
 		return NULL;
 	}
-	gpo_get_sysvol_gpt_version(tmp_ctx, unix_path, &sysvol_version, &display_name);
+	status = gpo_get_sysvol_gpt_version(tmp_ctx, unix_path,
+					    &sysvol_version,
+					    &display_name);
+	if (!NT_STATUS_IS_OK(status)) {
+		PyErr_SetNTSTATUS(status);
+		TALLOC_FREE(tmp_ctx);
+		return NULL;
+	}
+
 	talloc_free(tmp_ctx);
 	result = Py_BuildValue("[s,i]", display_name, sysvol_version);
 	return result;
-- 
2.14.2


>From c6d1b25a5bf4ed2d72fa9a0cfc1b14a6e4b8b5b3 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 15:59:37 +1300
Subject: [PATCH 22/30] python: This fucntion converts days to a relative (ie
 negative) NTTIME

It is not nttime2unix as it claimed.

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/gpclass.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index 233cd40f6d4..83141077475 100755
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -362,7 +362,7 @@ class inf_to_ldb(inf_to):
         self.gp_db.store(str(self), self.attribute, old_val)
         self.ldb.set_pwdProperties(val)
 
-    def nttime2unix(self):
+    def days2rel_nttime(self):
         seconds = 60
         minutes = 60
         hours = 24
@@ -373,8 +373,8 @@ class inf_to_ldb(inf_to):
 
     def mapper(self):
         '''ldap value : samba setter'''
-        return { "minPwdAge" : (self.ch_minPwdAge, self.nttime2unix),
-                 "maxPwdAge" : (self.ch_maxPwdAge, self.nttime2unix),
+        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),
-- 
2.14.2


>From 9958b231de3ae55d381c6772e1c1821fb10dcf5f Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 16:09:17 +1300
Subject: [PATCH 23/30] gpoupdate: Move closer to 80 columns

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dsdb/gpo/gpo_update.c | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/source4/dsdb/gpo/gpo_update.c b/source4/dsdb/gpo/gpo_update.c
index 2a5bba91b05..997e97ec14d 100644
--- a/source4/dsdb/gpo/gpo_update.c
+++ b/source4/dsdb/gpo/gpo_update.c
@@ -154,17 +154,23 @@ static void gpoupdate_task_init(struct task_server *task)
 	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",
+				      "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 */
+	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)),
+		task_server_terminate(task,
+				      talloc_asprintf(task,
+						      "gpoupdate: Failed to update "
+						      "sysvol scan schedule: %s\n",
+						      nt_errstr(status)),
 				      true);
 		return;
 	}
-- 
2.14.2


>From 98c00aeb2b5cdf933bf00a1709cf24fe2c9712f8 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 16:40:02 +1300
Subject: [PATCH 24/30] python: Use py_check_dcerpc_type() to safely check for
 credentials

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 libgpo/pygpo.c       | 17 ++++++++++++++---
 libgpo/wscript_build |  3 ++-
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c
index 757c713b027..09e03af9650 100644
--- a/libgpo/pygpo.c
+++ b/libgpo/pygpo.c
@@ -24,6 +24,7 @@
 #include "ads.h"
 #include "secrets.h"
 #include "../libds/common/flags.h"
+#include "librpc/rpc/pyrpc_util.h"
 #include "auth/credentials/pycredentials.h"
 #include "libcli/util/pyerrors.h"
 
@@ -196,17 +197,27 @@ static int py_ads_init(ADS *self, PyObject *args, PyObject *kwds)
 	const char *realm = NULL;
 	const char *workgroup = NULL;
 	const char *ldap_server = NULL;
-	PyObject *creds = NULL;
+	PyObject *py_creds = NULL;
 	PyObject *lp_obj = NULL;
 	struct loadparm_context *lp_ctx = NULL;
 
 	static const char *kwlist[] = {"ldap_server", "loadparm_context", "credentials", NULL};
-	if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO|O", discard_const_p(char *, kwlist), &ldap_server, &lp_obj, &creds))
+	if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO|O", discard_const_p(char *, kwlist), &ldap_server, &lp_obj, &py_creds))
 		return -1;
 
 	self->frame = talloc_stackframe();
 
-	if (creds) self->cli_creds = pytalloc_get_type(creds, struct cli_credentials);
+	if (py_creds) {
+		if (!py_check_dcerpc_type(py_creds, "samba.credentials",
+					  "Credentials")) {
+			PyErr_Format(PyExc_TypeError,
+				     "Expected samba.credentaials "
+				     "for credentials argument");
+			return -1;
+		}
+		self->cli_creds
+			= PyCredentials_AsCliCredentials(py_creds);
+	}
 
 	if (lp_obj) {
 		lp_ctx = pytalloc_get_type(lp_obj, struct loadparm_context);
diff --git a/libgpo/wscript_build b/libgpo/wscript_build
index 7d1d32628f0..2ef66f7fa9d 100644
--- a/libgpo/wscript_build
+++ b/libgpo/wscript_build
@@ -8,5 +8,6 @@ bld.SAMBA3_LIBRARY('gpext',
                    private_library=True)
 
 bld.SAMBA3_PYTHON('python_samba_libgpo', 'pygpo.c',
-                 deps='pyparam_util gpext talloc ads TOKEN_UTIL auth',
+                 deps='''pyparam_util gpext talloc ads TOKEN_UTIL
+                 auth pyrpc_util''',
                  realname='samba/gpo.so')
-- 
2.14.2


>From f6e7fe462ca8e92722854c05e8ae4db939105cf5 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 16:46:19 +1300
Subject: [PATCH 25/30] python: Remove Python 2.4 compat macro

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 libgpo/pygpo.c | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c
index 09e03af9650..28f41f754b8 100644
--- a/libgpo/pygpo.c
+++ b/libgpo/pygpo.c
@@ -30,10 +30,6 @@
 
 /* A Python C API module to use LIBGPO */
 
-#ifndef Py_RETURN_NONE
-#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
-#endif
-
 typedef struct {
 	PyObject_HEAD
 	TALLOC_CTX *frame;
-- 
2.14.2


>From 90905a245ae4c59ebf6fef2b9eb5c7be1c480610 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 16:48:13 +1300
Subject: [PATCH 26/30] python: Correct misleading else

Thankfully found by the compiler!

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 libgpo/pygpo.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c
index 28f41f754b8..f971dfbcc6e 100644
--- a/libgpo/pygpo.c
+++ b/libgpo/pygpo.c
@@ -55,9 +55,10 @@ static PyObject* py_gpo_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 		self->gpo_ptr = PyCapsule_GetPointer(c_obj, NULL);
 		self->head = self->gpo_ptr;
 		self->frame = PyCapsule_GetPointer(talloc_obj, NULL);
-	} else
+	} else {
 		self->gpo_ptr = NULL;
 		self->frame = NULL;
+	}
 	return (PyObject*)self;
 }
 
-- 
2.14.2


>From 46054132e886d5db95d646c4cdff0f324cc8e1c1 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 17:17:23 +1300
Subject: [PATCH 27/30] python: Convert gop.GROUP_POLICY_OBJECT to pytalloc

This avoids PyCapsule calls not available in Python 2.6

We remove the __init__ function as it is useless, the
object is created by py_ads_get_gpo_list()

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 libgpo/pygpo.c | 49 +++++++------------------------------------------
 1 file changed, 7 insertions(+), 42 deletions(-)

diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c
index f971dfbcc6e..6cb3bfc7983 100644
--- a/libgpo/pygpo.c
+++ b/libgpo/pygpo.c
@@ -37,36 +37,6 @@ typedef struct {
 	struct GROUP_POLICY_OBJECT *head;
 } GPO;
 
-static void py_gpo_dealloc(GPO* self)
-{
-	talloc_free(self->frame);
-	Py_TYPE(self)->tp_free((PyObject*)self);
-}
-
-static PyObject* py_gpo_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
-{
-	GPO *self;
-	PyObject *c_obj;
-	PyObject *talloc_obj;
-	static const char *kwlist[] = {"gpo_ptr", "talloc_ctx", NULL};
-	self = (GPO*)type->tp_alloc(type, 0);
-	if (PyArg_ParseTupleAndKeywords(args, kwds, "|OO", discard_const_p(char *, kwlist),
-					&c_obj, &talloc_obj)) {
-		self->gpo_ptr = PyCapsule_GetPointer(c_obj, NULL);
-		self->head = self->gpo_ptr;
-		self->frame = PyCapsule_GetPointer(talloc_obj, NULL);
-	} else {
-		self->gpo_ptr = NULL;
-		self->frame = NULL;
-	}
-	return (PyObject*)self;
-}
-
-static int py_gpo_init(GPO *self, PyObject *args, PyObject *kwds)
-{
-	return 0;
-}
-
 #define GPO_getter(ATTR) \
 static PyObject* GPO_get_##ATTR(GPO *self, void *closure) \
 { \
@@ -153,12 +123,6 @@ static PyObject* py_gpo_iter(PyObject *self)
 
 static PyTypeObject GPOType = {
 	.tp_name = "gpo.GROUP_POLICY_OBJECT",
-	.tp_basicsize = sizeof(GPO),
-	.tp_new = py_gpo_new,
-	.tp_free = PyObject_Del,
-	.tp_init = (initproc)py_gpo_init,
-	.tp_alloc = PyType_GenericAlloc,
-	.tp_dealloc = (destructor)py_gpo_dealloc,
 	.tp_doc = "GROUP_POLICY_OBJECT",
 	.tp_getset = GPO_setters,
 	.tp_methods = GPO_methods,
@@ -419,10 +383,7 @@ static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
 		goto out;
 	}
 
-	t_args = PyTuple_New(2);
-	PyTuple_SetItem(t_args, 0, PyCapsule_New(gpo_list, NULL, NULL));
-	PyTuple_SetItem(t_args, 1, PyCapsule_New(gpo_ctx, NULL, NULL));
-	ret = PyObject_CallObject((PyObject *)&GPOType, t_args);
+	pytalloc_steal_ex(&GPOType, gpo_ctx, gpo_list);
 
 out:
 	talloc_free(mem_ctx);
@@ -465,10 +426,14 @@ void initgpo(void)
 	m = Py_InitModule3("gpo", py_gpo_methods, "libgpo python bindings");
 	if (m == NULL) return;
 	PyModule_AddObject(m, "version", PyString_FromString(SAMBA_VERSION_STRING));
+
 	if (PyType_Ready(&ads_ADSType) < 0)
 		return;
 	PyModule_AddObject(m, "ADS_STRUCT", (PyObject *)&ads_ADSType);
-	if (PyType_Ready(&GPOType) < 0)
+
+	if (pytalloc_BaseObject_PyType_Ready(&GPOType) < 0)
 		return;
-	PyModule_AddObject(m, "GROUP_POLICY_OBJECT", (PyObject *)&GPOType);
+	PyModule_AddObject(m, "GROUP_POLICY_OBJECT",
+			   (PyObject *)&GPOType);
+
 }
-- 
2.14.2


>From 386546c37d9987e01b99858c52455b47217ec696 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 24 Oct 2017 17:24:38 +1300
Subject: [PATCH 28/30] python: Remove talloc_stackframe() held in an object

talloc_stackframe() must not be held after the return from a function.

If this causes warnings (talloc_tos() use without a stackframe), this
must be fixed in each function.

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 libgpo/pygpo.c | 26 +++++++++++++++-----------
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c
index 6cb3bfc7983..855a278b63e 100644
--- a/libgpo/pygpo.c
+++ b/libgpo/pygpo.c
@@ -70,6 +70,7 @@ static PyObject *py_gpo_get_unix_path(GPO *self, PyObject *args, PyObject *kwds)
 	const char *cache_dir = NULL;
 	PyObject *ret = Py_None;
 	char *unix_path = NULL;
+	TALLOC_CTX *frame = NULL;
 
 	static const char *kwlist[] = {"cache_dir", NULL};
 	if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s", discard_const_p(char *, kwlist), &cache_dir)) {
@@ -85,7 +86,12 @@ static PyObject *py_gpo_get_unix_path(GPO *self, PyObject *args, PyObject *kwds)
 		}
 	}
 
+	frame = talloc_stackframe();
+
 	status = gpo_get_unix_path(self->frame, cache_dir, self->gpo_ptr, &unix_path);
+
+	TALLOC_FREE(frame);
+
 	if (!NT_STATUS_IS_OK(status)) {
 		PyErr_SetString(PyExc_SystemError, "Failed to determine gpo unix path");
 		goto out;
@@ -133,7 +139,6 @@ static PyTypeObject GPOType = {
 
 typedef struct {
 	PyObject_HEAD
-	TALLOC_CTX *frame;
 	ADS_STRUCT *ads_ptr;
 	struct cli_credentials *cli_creds;
 } ADS;
@@ -141,7 +146,6 @@ typedef struct {
 static void py_ads_dealloc(ADS* self)
 {
 	ads_destroy(&(self->ads_ptr));
-	talloc_free(self->frame);
 	Py_TYPE(self)->tp_free((PyObject*)self);
 }
 
@@ -166,8 +170,6 @@ static int py_ads_init(ADS *self, PyObject *args, PyObject *kwds)
 	if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO|O", discard_const_p(char *, kwlist), &ldap_server, &lp_obj, &py_creds))
 		return -1;
 
-	self->frame = talloc_stackframe();
-
 	if (py_creds) {
 		if (!py_check_dcerpc_type(py_creds, "samba.credentials",
 					  "Credentials")) {
@@ -339,7 +341,7 @@ out:
 
 static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
 {
-	TALLOC_CTX *mem_ctx = NULL;
+	TALLOC_CTX *frame = NULL;
 	struct GROUP_POLICY_OBJECT *gpo_list = NULL;
 	ADS_STATUS status;
 	const char *samaccountname = NULL;
@@ -349,7 +351,6 @@ static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
 	struct security_token *token = NULL;
 	PyObject *ret = Py_None;
 	TALLOC_CTX *gpo_ctx;
-	PyObject * t_args;
 
 	static const char *kwlist[] = {"samaccountname", NULL};
 	if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", discard_const_p(char *, kwlist), &samaccountname)) {
@@ -357,21 +358,23 @@ static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
 		goto out;
 	}
 
-	mem_ctx = talloc_new(self->frame);
+	frame = talloc_stackframe();
 
-	status = find_samaccount(self->ads_ptr, mem_ctx, samaccountname, &uac, &dn);
+	status = find_samaccount(self->ads_ptr, frame, samaccountname, &uac, &dn);
 	if (!ADS_ERR_OK(status)) {
+		TALLOC_FREE(frame);
 		PyErr_SetString(PyExc_SystemError, "Failed to find samAccountName");
 		goto out;
 	}
 
 	if (uac & UF_WORKSTATION_TRUST_ACCOUNT || uac & UF_SERVER_TRUST_ACCOUNT) {
 		flags |= GPO_LIST_FLAG_MACHINE;
-		status = gp_get_machine_token(self->ads_ptr, mem_ctx, dn, &token);
+		status = gp_get_machine_token(self->ads_ptr, frame, dn, &token);
 	} else {
-		status = ads_get_sid_token(self->ads_ptr, mem_ctx, dn, &token);
+		status = ads_get_sid_token(self->ads_ptr, frame, dn, &token);
 	}
 	if (!ADS_ERR_OK(status)) {
+		TALLOC_FREE(frame);
 		PyErr_SetString(PyExc_SystemError, "Failed to get token");
 		goto out;
 	}
@@ -379,6 +382,7 @@ static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
 	gpo_ctx = talloc_new(NULL);
 	status = ads_get_gpo_list(self->ads_ptr, gpo_ctx, dn, flags, token, &gpo_list);
 	if (!ADS_ERR_OK(status)) {
+		TALLOC_FREE(frame);
 		PyErr_SetString(PyExc_SystemError, "Failed to fetch GPO list");
 		goto out;
 	}
@@ -386,7 +390,7 @@ static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
 	pytalloc_steal_ex(&GPOType, gpo_ctx, gpo_list);
 
 out:
-	talloc_free(mem_ctx);
+	TALLOC_FREE(frame);
 	if (!ret) {
 		PyErr_Print();
 		return Py_None;
-- 
2.14.2


>From 89eba4f653aba6c83586fb51a783c5dd4f9709e7 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Thu, 26 Oct 2017 16:06:27 +1300
Subject: [PATCH 29/30] Use talloc_stackframe() not talloc_tos() in namequery.c

The pygpo code calls these functions but there was not stackframe set up so
tallos_tos() fails.

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source3/libsmb/namequery.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/source3/libsmb/namequery.c b/source3/libsmb/namequery.c
index e39d76176cc..b5c364d9fd3 100644
--- a/source3/libsmb/namequery.c
+++ b/source3/libsmb/namequery.c
@@ -2862,12 +2862,15 @@ bool resolve_name(const char *name,
 	char *sitename = NULL;
 	int count = 0;
 	NTSTATUS status;
+	TALLOC_CTX *frame = NULL;
 
 	if (is_ipaddress(name)) {
 		return interpret_string_addr(return_ss, name, AI_NUMERICHOST);
 	}
 
-	sitename = sitename_fetch(talloc_tos(), lp_realm()); /* wild guess */
+	frame = talloc_stackframe();
+
+	sitename = sitename_fetch(frame, lp_realm()); /* wild guess */
 
 	status = internal_resolve_name(name, name_type, sitename,
 				       &ss_list, &count,
@@ -2901,7 +2904,7 @@ bool resolve_name(const char *name,
 	}
 
 	SAFE_FREE(ss_list);
-	TALLOC_FREE(sitename);
+	TALLOC_FREE(frame);
 	return False;
 }
 
@@ -3092,16 +3095,12 @@ static NTSTATUS get_dc_list(const char *domain,
 	bool done_auto_lookup = false;
 	int auto_count = 0;
 	NTSTATUS status;
-	TALLOC_CTX *ctx = talloc_init("get_dc_list");
+	TALLOC_CTX *ctx = talloc_stackframe();
 	int auto_name_type = 0x1C;
 
 	*ip_list = NULL;
 	*count = 0;
 
-	if (!ctx) {
-		return NT_STATUS_NO_MEMORY;
-	}
-
 	*ordered = False;
 
 	/* if we are restricted to solely using DNS for looking
-- 
2.14.2


>From d217acf826392fb5a812713b7bacf3bdb071fef9 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Thu, 26 Oct 2017 16:06:46 +1300
Subject: [PATCH 30/30] python: GPO use pytalloc and gpo_get_unix_path list

Have gpo_get_unix_path return a list instead of an
iterator.

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
Signed-off-by: David Mulder <dmulder at suse.com>
---
 libgpo/pygpo.c | 87 ++++++++++++++++++++++++++++++----------------------------
 1 file changed, 45 insertions(+), 42 deletions(-)

diff --git a/libgpo/pygpo.c b/libgpo/pygpo.c
index 855a278b63e..38844e5a43e 100644
--- a/libgpo/pygpo.c
+++ b/libgpo/pygpo.c
@@ -30,18 +30,14 @@
 
 /* A Python C API module to use LIBGPO */
 
-typedef struct {
-	PyObject_HEAD
-	TALLOC_CTX *frame;
-	struct GROUP_POLICY_OBJECT *gpo_ptr;
-	struct GROUP_POLICY_OBJECT *head;
-} GPO;
-
 #define GPO_getter(ATTR) \
-static PyObject* GPO_get_##ATTR(GPO *self, void *closure) \
+static PyObject* GPO_get_##ATTR(PyObject *self, void *closure) \
 { \
-	if (self->gpo_ptr->ATTR) \
-		return PyString_FromString(self->gpo_ptr->ATTR); \
+	struct GROUP_POLICY_OBJECT *gpo_ptr \
+		= pytalloc_get_ptr(self); \
+	\
+	if (gpo_ptr->ATTR) \
+		return PyString_FromString(gpo_ptr->ATTR); \
 	else \
 		return Py_None; \
 }
@@ -64,15 +60,18 @@ static PyGetSetDef GPO_setters[] = {
 	{NULL}
 };
 
-static PyObject *py_gpo_get_unix_path(GPO *self, PyObject *args, PyObject *kwds)
+static PyObject *py_gpo_get_unix_path(PyObject *self, PyObject *args,
+				      PyObject *kwds)
 {
 	NTSTATUS status;
 	const char *cache_dir = NULL;
 	PyObject *ret = Py_None;
 	char *unix_path = NULL;
 	TALLOC_CTX *frame = NULL;
-
 	static const char *kwlist[] = {"cache_dir", NULL};
+	struct GROUP_POLICY_OBJECT *gpo_ptr \
+		= pytalloc_get_type(self, struct GROUP_POLICY_OBJECT);
+
 	if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s", discard_const_p(char *, kwlist), &cache_dir)) {
 		PyErr_SetString(PyExc_SystemError, "Failed to parse arguments to gpo_get_unix_path()");
 		goto out;
@@ -88,7 +87,7 @@ static PyObject *py_gpo_get_unix_path(GPO *self, PyObject *args, PyObject *kwds)
 
 	frame = talloc_stackframe();
 
-	status = gpo_get_unix_path(self->frame, cache_dir, self->gpo_ptr, &unix_path);
+	status = gpo_get_unix_path(frame, cache_dir, gpo_ptr, &unix_path);
 
 	TALLOC_FREE(frame);
 
@@ -108,33 +107,13 @@ static PyMethodDef GPO_methods[] = {
 	{NULL}
 };
 
-static PyTypeObject GPOType;
-static PyObject* py_gpo_iternext(GPO *self)
-{
-	if (self->gpo_ptr && self->gpo_ptr->next) {
-		self->gpo_ptr = self->gpo_ptr->next;
-		return (PyObject *)self;
-	} else {
-		self->gpo_ptr = self->head;
-		PyErr_SetNone(PyExc_StopIteration);
-		return NULL;
-	}
-}
-
-static PyObject* py_gpo_iter(PyObject *self)
-{
-	Py_INCREF(self);
-	return self;
-}
-
 static PyTypeObject GPOType = {
+	PyVarObject_HEAD_INIT(NULL, 0)
 	.tp_name = "gpo.GROUP_POLICY_OBJECT",
 	.tp_doc = "GROUP_POLICY_OBJECT",
 	.tp_getset = GPO_setters,
 	.tp_methods = GPO_methods,
-	.tp_iter = py_gpo_iter,
-	.tp_iternext = (iternextfunc)py_gpo_iternext,
-	.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER,
+	.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
 };
 
 typedef struct {
@@ -342,7 +321,7 @@ out:
 static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
 {
 	TALLOC_CTX *frame = NULL;
-	struct GROUP_POLICY_OBJECT *gpo_list = NULL;
+	struct GROUP_POLICY_OBJECT *gpo = NULL, *gpo_list = NULL;
 	ADS_STATUS status;
 	const char *samaccountname = NULL;
 	const char *dn = NULL;
@@ -351,6 +330,8 @@ static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
 	struct security_token *token = NULL;
 	PyObject *ret = Py_None;
 	TALLOC_CTX *gpo_ctx;
+	size_t list_size;
+	size_t i;
 
 	static const char *kwlist[] = {"samaccountname", NULL};
 	if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", discard_const_p(char *, kwlist), &samaccountname)) {
@@ -379,7 +360,7 @@ static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
 		goto out;
 	}
 
-	gpo_ctx = talloc_new(NULL);
+	gpo_ctx = talloc_new(frame);
 	status = ads_get_gpo_list(self->ads_ptr, gpo_ctx, dn, flags, token, &gpo_list);
 	if (!ADS_ERR_OK(status)) {
 		TALLOC_FREE(frame);
@@ -387,14 +368,34 @@ static PyObject *py_ads_get_gpo_list(ADS *self, PyObject *args, PyObject *kwds)
 		goto out;
 	}
 
-	pytalloc_steal_ex(&GPOType, gpo_ctx, gpo_list);
+	/* Convert the C linked list into a python list */
+	list_size = 0;
+	for (gpo = gpo_list; gpo != NULL; gpo = gpo->next) {
+		list_size++;
+	}
+
+	i = 0;
+	ret = PyList_New(list_size);
+	if (ret == NULL) {
+		TALLOC_FREE(frame);
+		goto out;
+	}
+
+	for (gpo = gpo_list; gpo != NULL; gpo = gpo->next) {
+		PyObject *obj = pytalloc_reference_ex(&GPOType,
+						      gpo_ctx, gpo);
+		if (obj == NULL) {
+			TALLOC_FREE(frame);
+			goto out;
+		}
+
+		PyList_SetItem(ret, i, obj);
+		i++;
+	}
 
 out:
+
 	TALLOC_FREE(frame);
-	if (!ret) {
-		PyErr_Print();
-		return Py_None;
-	}
 	return ret;
 }
 
@@ -437,6 +438,8 @@ void initgpo(void)
 
 	if (pytalloc_BaseObject_PyType_Ready(&GPOType) < 0)
 		return;
+
+	Py_INCREF((PyObject *)(void *)&GPOType);
 	PyModule_AddObject(m, "GROUP_POLICY_OBJECT",
 			   (PyObject *)&GPOType);
 
-- 
2.14.2




More information about the samba-technical mailing list