[PATCHES v2] GPO support for client machine policy

David Mulder dmulder at suse.com
Fri Dec 8 21:42:05 UTC 2017


These patches add Group Policy support for client machines. Adds a
winbind event that calls samba_gpoupdate to apply local machine
policies. Adds the option "winbind gpupdate" to smb.conf, which
determines whether group policy will be applied to the client.
To start off, we only have Environment Variable policies.

What this version addresses:
* Makes use of existing helper functions, such as ldb_msg_find_attr_as_string() and ldb_msg_find_attr_as_int() in source4/torture/gpo/apply.c. Also uses torture_assert_int_equal() instead of atoi, etc.
* Removes the creds.machine_account() addition and simply uses the opts.machine option in samba_gpoupdate. Not providing --machine currently causes no policies to apply, since no gp_exts are processed.
* gp_file_append.py now checksums the file at read time, then again at write time to prevent overwriting outside changes.
* Removes the KDC gpupdate service, since this is now handled by winbind.
* Testing for machine environment variable apply and unapply.
* gpupdate is enabled by default (metze suggested this for KDC, but will also effect clients).

The last patch enables gpupdate by default. If we choose not to do this, then simply exclude the last patch.

What this version does not address:
* Eliminating the lockout_policy() and password_policy() hooks from winbindd_methods.
	- I'd like to address this in future patches, since this will likely require extensive work.
* 'wbinfo --gpoupdate-status', 'wbinfo --gpoupdate-force', etc
	- I'd also like to address these in future patches. I think this also requires further discussion.
* Ability to configure which policies the admin wants to be evaluated.
	- Again, should address this in future patches.
* Third party gpo evaluation scripts.
	- Depends on previous.

Of the requests not addresses, I think the most important to implement would be lockout_policy/password_policy and the ability to disable/enable gp_exts (in that order). My only hesitation with doing these now is that we might miss the 4.8 deadline in January.

FYI, the "Fix GPO unapply log" patches are not dependent on these machine policy patches, and definitely should be pulled in before the 4.8 deadline (which is one month from now).
 
 docs-xml/smbdotconf/domain/gpoupdatecommand.xml |  11 ++-
 docs-xml/smbdotconf/winbind/winbindgpupdate.xml |  18 +++++
 lib/param/loadparm.c                            |   1 +
 python/samba/gp_env_var_ext.py                  |  98 +++++++++++++++++++++++++
 python/samba/gp_file_append.py                  |  97 +++++++++++++++++++++++++
 python/samba/gpclass.py                         | 174 ++++++++++++++++++++++++--------------------
 selftest/target/Samba4.pm                       |   2 +-
 source3/param/loadparm.c                        |   2 +
 source3/winbindd/winbindd.c                     |   2 +
 source3/winbindd/winbindd_gpupdate.c            | 116 +++++++++++++++++++++++++++++
 source3/winbindd/winbindd_proto.h               |   3 +
 source3/winbindd/wscript_build                  |   3 +-
 source4/dsdb/gpo/gpo_update.c                   | 193 -------------------------------------------------
 source4/dsdb/wscript_build                      |   9 ---
 source4/scripting/bin/samba_gpoupdate           |  52 ++++++++++---
 source4/scripting/bin/wscript_build             |   2 +-
 source4/scripting/wscript_build                 |   7 +-
 source4/torture/gpo/apply.c                     |  83 ++++++++++++++++++++-
 18 files changed, 569 insertions(+), 304 deletions(-)

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

-------------- next part --------------
From f4369316af705595eb4f32cb2721d1c208175835 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Wed, 9 Aug 2017 09:25:53 -0600
Subject: [PATCH 1/5] gpo: Add machine policies

Add environment variable machine policies

Signed-off-by: David Mulder <dmulder at suse.com>
---
 python/samba/gp_env_var_ext.py        |  98 +++++++++++++++++++
 python/samba/gp_file_append.py        |  97 +++++++++++++++++++
 python/samba/gpclass.py               | 174 ++++++++++++++++++----------------
 source4/scripting/bin/samba_gpoupdate |  52 ++++++++--
 source4/scripting/bin/wscript_build   |   2 +-
 source4/scripting/wscript_build       |   7 +-
 6 files changed, 336 insertions(+), 94 deletions(-)
 create mode 100644 python/samba/gp_env_var_ext.py
 create mode 100644 python/samba/gp_file_append.py

diff --git a/python/samba/gp_env_var_ext.py b/python/samba/gp_env_var_ext.py
new file mode 100644
index 00000000000..ef60ae77c6e
--- /dev/null
+++ b/python/samba/gp_env_var_ext.py
@@ -0,0 +1,98 @@
+from gpclass import gp_ext, file_to
+from gp_file_append import ini_file_append
+import xml.etree.ElementTree as etree
+import re, os, os.path
+
+sysconfdir = '/etc'
+
+class xml_to_env(file_to):
+    @staticmethod
+    def get_local_env():
+        global sysconfdir
+        return ini_file_append(os.path.join(sysconfdir, 'profile'))
+
+    def set_env_var(self, val):
+        global sysconfdir
+        if type(val) is not dict:
+            val = { 'value': val, 'action': 'U' }
+        profile = ini_file_append(os.path.join(sysconfdir, 'profile'))
+
+        old_val = profile[self.attribute]
+        self.logger.info('Environment Variable %s was changed from %s to %s' \
+                         % (self.attribute, old_val, val['value']))
+
+        if val['value'] == None:
+            del profile[self.attribute]
+        # Update, Replace, and Create all really do the same thing
+        elif val['action'] == 'U' or \
+             val['action'] == 'R' or \
+             val['action'] == 'C':
+            if self.attribute == 'PATH' and \
+               'partial' in val and \
+               val['partial'] == '1':
+                profile[self.attribute] = '$PATH:%s' % val['value']
+            else:
+                profile[self.attribute] = val['value']
+        # Remove should blank the variable
+        elif val['action'] == 'D':
+            profile[self.attribute] = ''
+
+        self.gp_db.store(str(self), self.attribute, old_val)
+
+    def mapper(self):
+        return self
+
+    def __getitem__(self, key):
+        return (self.set_env_var, self.explicit)
+
+    def __str__(self):
+        return 'Environment'
+
+class env_var_unapply_map:
+    def __getitem__(self, key):
+        return (key, xml_to_env)
+
+    def __contains__(self, key):
+        return True
+
+class gp_environment_variable_ext(gp_ext):
+    def __init__(self, logger, sysconfdir_in):
+        global sysconfdir
+        super(gp_environment_variable_ext, self).__init__(logger)
+        sysconfdir = sysconfdir_in
+
+    def list(self, rootpath):
+        return os.path.join(rootpath,
+           'MACHINE/Preferences/EnvironmentVariables/EnvironmentVariables.xml')
+
+    def apply_map(self):
+        if not hasattr(self, 'xml_conf'):
+            return { 'Environment' : env_var_unapply_map() }
+        else:
+            return {
+                'Environment' : {
+                    a.attrib['name']: (a.attrib['name'], xml_to_env) \
+                        for a in self.xml_conf.findall('EnvironmentVariable')
+                }
+            }
+
+    def read(self, policy):
+        self.xml_conf = etree.fromstring(policy)
+        apply_map = self.apply_map()['Environment']
+        ret = False
+
+        for env_var in self.xml_conf.findall('EnvironmentVariable'):
+            (att, setter) = apply_map.get(env_var.attrib['name'])
+            ret = True
+            setter(self.logger,
+                   self.ldb,
+                   self.gp_db,
+                   self.lp,
+                   att,
+                   env_var.find('Properties').attrib).update_samba()
+            self.gp_db.commit()
+        return ret
+
+    def __str__(self):
+        return 'Environment GPO extension'
+
diff --git a/python/samba/gp_file_append.py b/python/samba/gp_file_append.py
new file mode 100644
index 00000000000..69baeef701f
--- /dev/null
+++ b/python/samba/gp_file_append.py
@@ -0,0 +1,97 @@
+import re, os.path, hashlib
+
+class gp_file_append:
+    def __init__(self, filename):
+        self.filename = filename
+        self.section = \
+            '#\n# Samba GPO Section\n# These settings are applied via GPO\n#'
+        self.section_end = '#\n# End Samba GPO Section\n#'
+        self.checksum = None
+
+    def __get_file_data(self):
+        if not os.path.exists(os.path.dirname(self.filename)):
+            raise Exception, 'path %s not found' % \
+                os.path.dirname(self.filename)
+        if os.path.exists(self.filename):
+            data = open(self.filename, 'r').read()
+        else:
+            data = ''
+        checksum = hashlib.sha256(data).digest()
+        return (data, checksum)
+
+    def get_section(self):
+        self.data, self.checksum = self.__get_file_data()
+
+        # Get the current applied contents (if any)
+        current = ''
+        try:
+            self.start = re.finditer(self.section, self.data).next()
+            self.end = re.finditer(self.section_end, self.data).next()
+        except:
+            self.start = None
+            self.end = None
+        if self.start and self.end:
+            current = self.data[self.start.end():self.end.start()]
+        return current
+
+    def set_section(self, results):
+        _, checksum = self.__get_file_data()
+        if checksum != self.checksum:
+            raise Exception, '%s was modified elsewhere, checksum failed' % \
+                self.filename
+        if results.strip():
+            results = self.section + '\n' + results + '\n' + self.section_end
+            if self.start and self.end:
+                ndata = self.data[:self.start.start()] + results + \
+                    self.data[self.end.end():]
+            else:
+                ndata = self.data + results
+        else: # Remove the GPO section if all settings are removed
+            if self.start and self.end:
+                ndata = self.data[:self.start.start()] + \
+                    self.data[self.end.end():]
+            else:
+                ndata = self.data
+        open(self.filename, 'w').write(ndata)
+
+class ini_file_append:
+    def __init__(self, filename):
+        self.modified = False
+        self.append = gp_file_append(filename)
+        self.vals = self.__get_section()
+
+    def keys(self):
+        return self.vals.keys()
+
+    def __get_section(self):
+        return {
+            l.split('=')[0]: l.split('=')[-1] \
+                for l in self.append.get_section().strip().split('\n')
+        }
+
+    def __set_section(self):
+        results = '\n'.join([
+            '%s=%s' % (key, val) \
+                for key, val in self.vals.iteritems() if key
+        ])
+        self.append.set_section(results)
+
+    def __getitem__(self, key):
+        if key in self.vals.keys():
+            return self.vals[key]
+        else:
+            return None
+
+    def __setitem__(self, key, val):
+        self.modified = True
+        self.vals[key] = val
+
+    def __delitem__(self, key):
+        self.modified = True
+        if key in self.vals.keys():
+            del self.vals[key]
+
+    def __del__(self):
+        if self.modified:
+            self.__set_section()
+
diff --git a/python/samba/gpclass.py b/python/samba/gpclass.py
index 00330eb5ecb..02750105067 100644
--- a/python/samba/gpclass.py
+++ b/python/samba/gpclass.py
@@ -19,19 +19,12 @@ import sys
 import os
 import tdb
 sys.path.insert(0, "bin/python")
-import samba.gpo as gpo
-import optparse
-import ldb
-from samba.auth import system_session
-import samba.getopt as options
-from samba.samdb import SamDB
-from samba.netcmd import gpo as gpo_user
-import codecs
 from samba import NTSTATUSError
 from ConfigParser import ConfigParser
 from StringIO import StringIO
 from abc import ABCMeta, abstractmethod
 import xml.etree.ElementTree as etree
+import re
 
 try:
     from enum import Enum
@@ -217,12 +210,19 @@ class gp_log:
         exts = guid_obj.findall('gp_ext')
         if exts is not None:
             for ext in exts:
-                ext_map = {val[0]: val[1] for (key, val) in \
-                    data_maps[ext.attrib['name']].items()}
                 attrs = ext.findall('attribute')
                 for attr in attrs:
-                    ret.append((attr.attrib['name'], attr.text,
-                                ext_map[attr.attrib['name']]))
+                    func = None
+                    if attr.attrib['name'] in data_maps[ext.attrib['name']]:
+                        func = data_maps[ext.attrib['name']]\
+                               [attr.attrib['name']][-1]
+                    else:
+                        for dmap in data_maps[ext.attrib['name']].keys():
+                            if data_maps[ext.attrib['name']][dmap][0] == \
+                               attr.attrib['name']:
+                                func = data_maps[ext.attrib['name']][dmap][-1]
+                                break
+                    ret.append((attr.attrib['name'], attr.text, func))
         return ret
 
     def delete(self, gp_ext_name, attribute):
@@ -286,6 +286,9 @@ class GPOStorage:
 class gp_ext(object):
     __metaclass__ = ABCMeta
 
+    def __init__(self, logger):
+        self.logger = logger
+
     @abstractmethod
     def list(self, rootpath):
         pass
@@ -295,14 +298,41 @@ class gp_ext(object):
         pass
 
     @abstractmethod
-    def parse(self, afile, ldb, conn, gp_db, lp):
+    def read(self, policy):
         pass
 
+    def parse(self, afile, ldb, conn, gp_db, lp):
+        self.ldb = ldb
+        self.gp_db = gp_db
+        self.lp = lp
+
+        # Fixing the bug where only some Linux Boxes capitalize MACHINE
+        try:
+            blist = afile.split('/')
+            idx = afile.lower().split('/').index('machine')
+            for case in [
+                            blist[idx].upper(),
+                            blist[idx].capitalize(),
+                            blist[idx].lower()
+                        ]:
+                bfile = '/'.join(blist[:idx]) + '/' + case + '/' + \
+                    '/'.join(blist[idx+1:])
+                try:
+                    return self.read(conn.loadfile(bfile.replace('/', '\\')))
+                except NTSTATUSError:
+                    continue
+        except ValueError:
+            try:
+                return self.read(conn.loadfile(afile.replace('/', '\\')))
+            except Exception as e:
+                self.logger.error(str(e))
+                return None
+
     @abstractmethod
     def __str__(self):
         pass
 
-class inf_to():
+class file_to():
     __metaclass__ = ABCMeta
 
     def __init__(self, logger, ldb, gp_db, lp, attribute, val):
@@ -317,7 +347,7 @@ class inf_to():
         return self.val
 
     def update_samba(self):
-        (upd_sam, value) = self.mapper().get(self.attribute)
+        (upd_sam, value) = self.mapper()[self.attribute]
         upd_sam(value())
 
     @abstractmethod
@@ -328,7 +358,7 @@ class inf_to():
     def __str__(self):
         pass
 
-class inf_to_kdc_tdb(inf_to):
+class inf_to_kdc_tdb(file_to):
     def mins_to_hours(self):
         return '%d' % (int(self.val)/60)
 
@@ -357,7 +387,7 @@ class inf_to_kdc_tdb(inf_to):
     def __str__(self):
         return 'Kerberos Policy'
 
-class inf_to_ldb(inf_to):
+class inf_to_ldb(file_to):
     '''This class takes the .inf file parameter (essentially a GPO file mapped
     to a GUID), hashmaps it to the Samba parameter, which then uses an ldb
     object to update the parameter to Samba4. Not registry oriented whatsoever.
@@ -415,8 +445,53 @@ class inf_to_ldb(inf_to):
     def __str__(self):
         return 'System Access'
 
+class gp_inf_ext(gp_ext):
+    @abstractmethod
+    def list(self, rootpath):
+        pass
+
+    @abstractmethod
+    def apply_map(self):
+        pass
+
+    def read(self, policy):
+        ret = False
+        inftable = self.apply_map()
+
+        current_section = None
 
-class gp_sec_ext(gp_ext):
+        # 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.
+
+        inf_conf = ConfigParser()
+        inf_conf.optionxform=str
+        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)
+            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.logger, self.ldb, self.gp_db, self.lp, att,
+                           value).update_samba()
+                    self.gp_db.commit()
+        return ret
+
+    @abstractmethod
+    def __str__(self):
+        pass
+
+class gp_sec_ext(gp_inf_ext):
     '''This class does the following two things:
         1) Identifies the GPO if it has a certain kind of filepath,
         2) Finally parses it.
@@ -424,15 +499,12 @@ class gp_sec_ext(gp_ext):
 
     count = 0
 
-    def __init__(self, logger):
-        self.logger = logger
-
     def __str__(self):
         return "Security GPO extension"
 
     def list(self, rootpath):
         return os.path.join(rootpath,
-                            "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
+            "MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf")
 
     def listmachpol(self, rootpath):
         return os.path.join(rootpath, "Machine/Registry.pol")
@@ -465,61 +537,3 @@ class gp_sec_ext(gp_ext):
                                    }
                }
 
-    def read_inf(self, path, conn):
-        ret = False
-        inftable = self.apply_map()
-
-        policy = conn.loadfile(path.replace('/', '\\'))
-        current_section = None
-
-        # 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.
-
-        inf_conf = ConfigParser()
-        inf_conf.optionxform=str
-        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)
-            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.logger, self.ldb, self.gp_db, self.lp, att,
-                           value).update_samba()
-                    self.gp_db.commit()
-        return ret
-
-    def parse(self, afile, ldb, conn, gp_db, lp):
-        self.ldb = ldb
-        self.gp_db = gp_db
-        self.lp = lp
-
-        # Fixing the bug where only some Linux Boxes capitalize MACHINE
-        if afile.endswith('inf'):
-            try:
-                blist = afile.split('/')
-                idx = afile.lower().split('/').index('machine')
-                for case in [blist[idx].upper(), blist[idx].capitalize(),
-                             blist[idx].lower()]:
-                    bfile = '/'.join(blist[:idx]) + '/' + case + '/' + \
-                            '/'.join(blist[idx+1:])
-                    try:
-                        return self.read_inf(bfile, conn)
-                    except NTSTATUSError:
-                        continue
-            except ValueError:
-                try:
-                    return self.read_inf(afile, conn)
-                except:
-                    return None
-
diff --git a/source4/scripting/bin/samba_gpoupdate b/source4/scripting/bin/samba_gpoupdate
index f593e473fb1..d84594a6701 100755
--- a/source4/scripting/bin/samba_gpoupdate
+++ b/source4/scripting/bin/samba_gpoupdate
@@ -29,11 +29,19 @@ sys.path.insert(0, "bin/python")
 
 import optparse
 from samba import getopt as options
+from samba.auth import system_session
+try:
+    from samba.samdb import SamDB
+except:
+    SamDB = None
 from samba.gpclass import *
+from samba.gp_env_var_ext import gp_environment_variable_ext
 from samba.net import Net
 from samba.dcerpc import nbt
 from samba import smb
+import samba.gpo as gpo
 import logging
+import chardet
 
 ''' Fetch the hostname of a writable DC '''
 def get_dc_hostname(creds, lp):
@@ -50,6 +58,15 @@ def get_gpo_list(dc_hostname, creds, lp):
         gpos = ads.get_gpo_list(creds.get_username())
     return gpos
 
+def gpo_version(conn, path):
+    local_path = os.path.join(lp.get('cache directory'), 'gpt', path, 'GPT.INI')
+    if not os.path.exists(os.path.dirname(local_path)):
+        os.makedirs(os.path.dirname(local_path))
+    data = conn.loadfile(os.path.join(path, 'GPT.INI').replace('/', '\\'))
+    encoding = chardet.detect(data)
+    open(local_path, 'w').write(data.decode(encoding['encoding']))
+    return int(gpo.gpo_get_sysvol_gpt_version(os.path.dirname(local_path))[1])
+
 def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
     gp_db = store.get_gplog(creds.get_username())
     dc_hostname = get_dc_hostname(creds, lp)
@@ -65,8 +82,7 @@ def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
         if guid == 'Local Policy':
             continue
         path = os.path.join(lp.get('realm').lower(), 'Policies', guid)
-        local_path = os.path.join(lp.get("path", "sysvol"), path)
-        version = int(gpo.gpo_get_sysvol_gpt_version(local_path)[1])
+        version = gpo_version(conn, path)
         if version != store.get_int(guid):
             logger.info('GPO %s has changed' % guid)
             gp_db.state(GPOSTATE.APPLY)
@@ -74,13 +90,15 @@ def apply_gp(lp, creds, test_ldb, logger, store, gp_extensions):
             gp_db.state(GPOSTATE.ENFORCE)
         gp_db.set_guid(guid)
         store.start()
-        try:
-            for ext in gp_extensions:
+        for ext in gp_extensions:
+            try:
                 ext.parse(ext.list(path), test_ldb, conn, gp_db, lp)
-        except:
-            logger.error('Failed to parse gpo %s' % guid)
-            store.cancel()
-            continue
+            except Exception as e:
+                logger.error('Failed to parse gpo %s for extension %s' % \
+                    (guid, str(ext)))
+                logger.error('Message was: ' + str(e))
+                store.cancel()
+                continue
         store.store(guid, '%i' % version)
         store.commit()
 
@@ -115,6 +133,10 @@ if __name__ == "__main__":
     parser.add_option('-H', '--url', dest='url', help='URL for the samdb')
     parser.add_option('-X', '--unapply', help='Unapply Group Policy',
                       action='store_true')
+    parser.add_option('-M', '--machine', help='Apply machine policy',
+                      action='store_true', default=False)
+    parser.add_option('--sysconfdir', help='Location of sysconfdir: ex. /etc/',
+                      default='/etc/')
     parser.add_option_group(credopts)
 
     # Set the options and the arguments
@@ -148,10 +170,20 @@ if __name__ == "__main__":
     cache_dir = lp.get('cache directory')
     store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb'))
 
-    gp_extensions = [gp_sec_ext(logger)]
+    gp_extensions = []
+    if opts.machine:
+        if lp.get('server role') == 'active directory domain controller':
+            gp_extensions.append(gp_sec_ext(logger))
+        gp_extensions.append(gp_environment_variable_ext(logger,
+                                                         opts.sysconfdir))
+    else:
+        pass # User extensions
 
     # Get a live instance of Samba
-    test_ldb = SamDB(url, session_info=session, credentials=creds, lp=lp)
+    if SamDB:
+        test_ldb = SamDB(url, session_info=session, credentials=creds, lp=lp)
+    else:
+        test_ldb = None
 
     if not opts.unapply:
         apply_gp(lp, creds, test_ldb, logger, store, gp_extensions)
diff --git a/source4/scripting/bin/wscript_build b/source4/scripting/bin/wscript_build
index 737f1bce411..043442b3407 100644
--- a/source4/scripting/bin/wscript_build
+++ b/source4/scripting/bin/wscript_build
@@ -7,6 +7,6 @@ if bld.CONFIG_SET('AD_DC_BUILD_IS_ENABLED'):
                    'samba_kcc',
                    'samba_upgradeprovision',
                    'samba_upgradedns',
-                   'samba_gpoupdate',
                    'gen_output.py']:
         bld.SAMBA_SCRIPT(script, pattern=script, installdir='.')
+bld.SAMBA_SCRIPT('samba_gpoupdate', pattern='samba_gpoupdate', installdir='.')
diff --git a/source4/scripting/wscript_build b/source4/scripting/wscript_build
index eb11c4202fe..2f53cce12b7 100644
--- a/source4/scripting/wscript_build
+++ b/source4/scripting/wscript_build
@@ -2,10 +2,11 @@
 
 from samba_utils import MODE_755
 
-sbin_files = None
+sbin_files = ''
 if bld.CONFIG_SET('AD_DC_BUILD_IS_ENABLED'):
-    sbin_files = 'bin/samba_dnsupdate bin/samba_spnupdate bin/samba_upgradedns bin/samba_kcc bin/samba_gpoupdate'
-    man_files = 'man/samba_gpoupdate.8'
+    sbin_files = 'bin/samba_dnsupdate bin/samba_spnupdate bin/samba_upgradedns bin/samba_kcc '
+sbin_files += 'bin/samba_gpoupdate'
+man_files = 'man/samba_gpoupdate.8'
 
 if sbin_files:
     bld.INSTALL_FILES('${SBINDIR}',
-- 
2.13.6


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

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

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


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

Signed-off-by: David Mulder <dmulder at suse.com>
---
 docs-xml/smbdotconf/domain/gpoupdatecommand.xml |  11 ++-
 docs-xml/smbdotconf/winbind/winbindgpupdate.xml |  18 ++++
 lib/param/loadparm.c                            |   1 +
 source3/param/loadparm.c                        |   2 +
 source3/winbindd/winbindd.c                     |   2 +
 source3/winbindd/winbindd_gpupdate.c            | 116 ++++++++++++++++++++++++
 source3/winbindd/winbindd_proto.h               |   3 +
 source3/winbindd/wscript_build                  |   3 +-
 8 files changed, 151 insertions(+), 5 deletions(-)
 create mode 100644 docs-xml/smbdotconf/winbind/winbindgpupdate.xml
 create mode 100644 source3/winbindd/winbindd_gpupdate.c

diff --git a/docs-xml/smbdotconf/domain/gpoupdatecommand.xml b/docs-xml/smbdotconf/domain/gpoupdatecommand.xml
index 22a42163f27..77b596051b7 100644
--- a/docs-xml/smbdotconf/domain/gpoupdatecommand.xml
+++ b/docs-xml/smbdotconf/domain/gpoupdatecommand.xml
@@ -5,10 +5,13 @@
                  xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
 <description>
 	<para>This option sets the command that is called to apply GPO policies.
-        The samba_gpoupdate script applies System Access and Kerberos Policies.
-        System Access policies set minPwdAge, maxPwdAge, minPwdLength, and
-        pwdProperties in the samdb. Kerberos Policies set kdc:service ticket lifetime,
-        kdc:user ticket lifetime, and kdc:renewal lifetime in smb.conf.
+        The samba_gpoupdate script applies System Access and Kerberos Policies
+	to the KDC, or Environment Variable policies to client machines. System
+	Access policies set minPwdAge, maxPwdAge, minPwdLength, and
+	pwdProperties in the samdb. Kerberos Policies set kdc:service ticket
+	lifetime, kdc:user ticket lifetime, and kdc:renewal lifetime in
+	smb.conf. Environment Variable policies apply environment variables,
+	such as PATH, to /etc/profile.
 	</para>
 </description>
 
diff --git a/docs-xml/smbdotconf/winbind/winbindgpupdate.xml b/docs-xml/smbdotconf/winbind/winbindgpupdate.xml
new file mode 100644
index 00000000000..a35ee88d399
--- /dev/null
+++ b/docs-xml/smbdotconf/winbind/winbindgpupdate.xml
@@ -0,0 +1,18 @@
+<samba:parameter name="winbind gpupdate"
+                 context="G"
+                 type="boolean"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+
+	<para>This option controls whether winbind will execute the gpupdate
+	command defined in <smbconfoption name="gpo update command"/> on the
+	Group Policy update interval. The Group	Policy update interval is
+	defined as every 90 minutes, plus a random offset between 0 and	30
+	minutes. This applies Group Policy Machine polices to the client.
+	</para>
+
+</description>
+
+<value type="default">no</value>
+<value type="example">yes</value>
+</samba:parameter>
diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c
index d788ffbe36f..ae0e0d10681 100644
--- a/lib/param/loadparm.c
+++ b/lib/param/loadparm.c
@@ -2731,6 +2731,7 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx)
 	lpcfg_do_global_parameter(lp_ctx, "winbindd socket directory", dyn_WINBINDD_SOCKET_DIR);
 	lpcfg_do_global_parameter(lp_ctx, "ntp signd socket directory", dyn_NTP_SIGND_SOCKET_DIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "gpo update command", "%s/samba_gpoupdate", dyn_SCRIPTSBINDIR);
+	lpcfg_do_global_parameter_var(lp_ctx, "winbind gpupdate", "False");
 	lpcfg_do_global_parameter_var(lp_ctx, "dns update command", "%s/samba_dnsupdate", dyn_SCRIPTSBINDIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "spn update command", "%s/samba_spnupdate", dyn_SCRIPTSBINDIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "samba kcc command",
diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c
index 01c022e2889..96ed41b804f 100644
--- a/source3/param/loadparm.c
+++ b/source3/param/loadparm.c
@@ -926,6 +926,8 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals)
 	Globals.gpo_update_command = str_list_make_v3_const(NULL, s, NULL);
 	TALLOC_FREE(s);
 
+	Globals.winbind_gpupdate = false;
+
 	s = talloc_asprintf(talloc_tos(), "%s/samba_spnupdate", get_dyn_SCRIPTSBINDIR());
 	if (s == NULL) {
 		smb_panic("init_globals: ENOMEM");
diff --git a/source3/winbindd/winbindd.c b/source3/winbindd/winbindd.c
index ceb131e9b32..2e0a45c82b5 100644
--- a/source3/winbindd/winbindd.c
+++ b/source3/winbindd/winbindd.c
@@ -1786,6 +1786,8 @@ int main(int argc, const char **argv)
 		daemon_ready("winbindd");
 	}
 
+	gpupdate_init();
+
 	/* Loop waiting for requests */
 	while (1) {
 		frame = talloc_stackframe();
diff --git a/source3/winbindd/winbindd_gpupdate.c b/source3/winbindd/winbindd_gpupdate.c
new file mode 100644
index 00000000000..79fd5fa79be
--- /dev/null
+++ b/source3/winbindd/winbindd_gpupdate.c
@@ -0,0 +1,116 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Group Policy Update event for winbindd
+ * Copyright (C) David Mulder 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "includes.h"
+#include "param/param.h"
+#include "param/loadparm.h"
+#include "winbindd.h"
+
+/*
+ * gpupdate_interval()
+ * return   Random integer between 5400 and 7200, the group policy update
+ *          interval in seconds
+ *
+ * Group Policy should be updated every 90 minutes in the background,
+ * with a random offset between 0 and 30 minutes. This ensures mutiple
+ * clients will not update at the same time.
+ */
+#define GPUPDATE_INTERVAL       (90*60)
+#define GPUPDATE_RAND_OFFSET    (30*60)
+static uint32_t gpupdate_interval(void)
+{
+	int rand_int_offset = rand() % GPUPDATE_RAND_OFFSET;
+	return GPUPDATE_INTERVAL+rand_int_offset;
+}
+
+struct gpupdate_state {
+	TALLOC_CTX *ctx;
+	struct loadparm_context *lp_ctx;
+};
+
+static void gpupdate_callback(struct tevent_context *ev,
+			      struct tevent_timer *tim,
+			      struct timeval current_time,
+			      void *private_data)
+{
+	struct tevent_timer *time_event;
+	struct timeval schedule;
+	struct tevent_req *req = NULL;
+	struct gpupdate_state *data =
+		talloc_get_type_abort(private_data, struct gpupdate_state);
+	const char *const *gpupdate_cmd =
+		lpcfg_gpo_update_command(data->lp_ctx);
+	const char *smbconf = lp_default_path();
+
+	/* Execute gpupdate */
+	req = samba_runcmd_send(data->ctx, ev, timeval_zero(), 2, 0,
+				gpupdate_cmd,
+				"-s",
+				smbconf,
+				"--machine",
+				"--machine-pass",
+				NULL);
+	if (req == NULL) {
+		DEBUG(0, ("Failed to execute the gpupdate command\n"));
+		return;
+	}
+
+	/* Schedule the next event */
+	schedule = tevent_timeval_current_ofs(gpupdate_interval(), 0);
+	time_event = tevent_add_timer(ev, data->ctx, schedule,
+				      gpupdate_callback, data);
+	if (time_event == NULL) {
+		DEBUG(0, ("Failed scheduling the next gpupdate event\n"));
+	}
+}
+
+void gpupdate_init(void)
+{
+	struct tevent_timer *time_event;
+	struct timeval schedule;
+	TALLOC_CTX * ctx = talloc_new(server_event_context());
+	struct gpupdate_state *data = talloc(ctx, struct gpupdate_state);
+	struct loadparm_context *lp_ctx =
+		loadparm_init_s3(NULL, loadparm_s3_helpers());
+
+	/*
+	 * Check if gpupdate is enabled for winbind, if not
+	 * return without scheduling any events.
+	 */
+	if (!lpcfg_winbind_gpupdate(lp_ctx)) {
+		return;
+	}
+
+	/*
+	 * Execute the first event immediately, future events
+	 * will execute on the gpupdate interval, which is every
+	 * 90 to 120 minutes (at random).
+	 */
+	schedule = tevent_timeval_current_ofs(0, 0);
+	data->ctx = ctx;
+	data->lp_ctx = lp_ctx;
+	if (data->lp_ctx == NULL) {
+		smb_panic("Could not load smb.conf\n");
+	}
+	time_event = tevent_add_timer(server_event_context(), data->ctx,
+				      schedule, gpupdate_callback, data);
+	if (time_event == NULL) {
+		DEBUG(0, ("Failed scheduling the gpupdate event\n"));
+	}
+}
+
diff --git a/source3/winbindd/winbindd_proto.h b/source3/winbindd/winbindd_proto.h
index cf01337aaad..0e59a51ba54 100644
--- a/source3/winbindd/winbindd_proto.h
+++ b/source3/winbindd/winbindd_proto.h
@@ -932,4 +932,7 @@ NTSTATUS wb_irpc_register(void);
 /* The following definitions come from winbindd/winbindd_reconnect.c  */
 bool reconnect_need_retry(NTSTATUS status, struct winbindd_domain *domain);
 
+/* The following definitions come from winbindd/winbindd_gpupdate.c  */
+void gpupdate_init(void);
+
 #endif /*  _WINBINDD_PROTO_H_  */
diff --git a/source3/winbindd/wscript_build b/source3/winbindd/wscript_build
index 51264e9e365..48250ea565e 100644
--- a/source3/winbindd/wscript_build
+++ b/source3/winbindd/wscript_build
@@ -254,7 +254,8 @@ bld.SAMBA3_BINARY('winbindd',
                  winbindd_pam_logoff.c
                  winbindd_pam_chauthtok.c
                  winbindd_pam_auth_crap.c
-                 winbindd_pam_chng_pswd_auth_crap.c''',
+                 winbindd_pam_chng_pswd_auth_crap.c
+                 winbindd_gpupdate.c''',
                  deps='''
                  talloc
                  tevent
-- 
2.13.6


From 16fb16ab9321a06cf02b34f2cb25960ff279ad95 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder at suse.com>
Date: Wed, 6 Dec 2017 14:29:23 -0700
Subject: [PATCH 4/5] gpo: test that machine policies are working

Signed-off-by: David Mulder <dmulder at suse.com>
---
 selftest/target/Samba4.pm   |  2 +-
 source4/torture/gpo/apply.c | 83 +++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 82 insertions(+), 3 deletions(-)

diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm
index 68feb6b4673..45ace7dc5f9 100755
--- a/selftest/target/Samba4.pm
+++ b/selftest/target/Samba4.pm
@@ -616,7 +616,7 @@ sub provision_raw_step1($$)
 	rndc command = true
 	dns update command = $ctx->{samba_dnsupdate}
 	spn update command = $ENV{SRCDIR_ABS}/source4/scripting/bin/samba_spnupdate -s $ctx->{smb_conf}
-	gpo update command = $ENV{SRCDIR_ABS}/source4/scripting/bin/samba_gpoupdate -s $ctx->{smb_conf} -H $ctx->{privatedir}/sam.ldb
+	gpo update command = $ENV{SRCDIR_ABS}/source4/scripting/bin/samba_gpoupdate -s $ctx->{smb_conf} -H $ctx->{privatedir}/sam.ldb --sysconfdir=$ctx->{etcdir} --machine
 	dreplsrv:periodic_startup_interval = 0
 	dsdb:schema update allowed = yes
 
diff --git a/source4/torture/gpo/apply.c b/source4/torture/gpo/apply.c
index ae98767d3f1..b1b460f8500 100644
--- a/source4/torture/gpo/apply.c
+++ b/source4/torture/gpo/apply.c
@@ -27,6 +27,7 @@
 #include "lib/ldb/include/ldb.h"
 #include "torture/gpo/proto.h"
 #include <unistd.h>
+#include <fcntl.h>
 
 struct torture_suite *gpo_apply_suite(TALLOC_CTX *ctx)
 {
@@ -78,12 +79,36 @@ PasswordComplexity = %d\n\
 #define GPTINI "addom.samba.example.com/Policies/"\
 	       "{31B2F340-016D-11D2-945F-00C04FB984F9}/GPT.INI"
 
+#define ENVPATH "addom.samba.example.com/Policies/"\
+		"{31B2F340-016D-11D2-945F-00C04FB984F9}/MACHINE/"\
+		"Preferences/EnvironmentVariables"
+#define ENVXML "EnvironmentVariables.xml"
+#define ENVTMPL "\
+<?xml version=\"1.0\" encoding=\"utf-8\"?>\
+<EnvironmentVariables clsid=\"{BF141A63-327B-438a-B9BF-2C188F13B7AD}\">\
+<EnvironmentVariable clsid=\"{78570023-8373-4a19-BA80-2F150738EA19}\"\
+	name=\"PATH\" status=\"PATH = %s\" image=\"2\"\
+	uid=\"{B048DE7E-9B24-497C-B798-17708F7B33C5}\">\
+<Properties action=\"%s\" name=\"PATH\" value=\"%s\" user=\"0\"\
+	partial=\"%s\"/>\
+</EnvironmentVariable>\
+</EnvironmentVariables>\
+"
+#define RESENV "#\n\
+# Samba GPO Section\n\
+# These settings are applied via GPO\n\
+#\n\
+PATH=%s\n\
+#\n\
+# End Samba GPO Section\n\
+#"
+
 bool torture_gpo_system_access_policies(struct torture_context *tctx)
 {
 	TALLOC_CTX *ctx = talloc_new(tctx);
 	int ret, vers = 0, i;
-	const char *sysvol_path = NULL, *gpo_dir = NULL;
-	const char *gpo_file = NULL, *gpt_file = NULL;
+	const char *sysvol_path = NULL, *gpo_dir = NULL, *env_dir = NULL;
+	const char *gpo_file = NULL, *gpt_file = NULL, *env_xml = NULL;
 	struct ldb_context *samdb = NULL;
 	struct ldb_result *result;
 	const char *attrs[] = {
@@ -100,9 +125,26 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 	int maxpwdcases[] = { 0, 1, 999 };
 	int pwdlencases[] = { 0, 1, 14 };
 	int pwdpropcases[] = { 0, 1, 1 };
+	const char *smbconf = NULL;
+	char *profile = NULL;
+	const char *envcases[][4] = {
+		{ "", "D", "", "0" },
+		{ "/bin", "U", "/bin", "0" },
+		{ "/bin", "U", "/bin", "1" },
+		NULL,
+	};
+	const char *envres[] = {
+		"",
+		"/bin",
+		"$PATH:/bin",
+		NULL,
+	};
+	char *envexpected = NULL, *envreturned = NULL;
+	size_t envlen = 0;
 	struct ldb_message *old_message = NULL;
 	const char **itr;
 	int gpo_update_len = 0;
+	int fd;
 
 	sysvol_path = lpcfg_path(lpcfg_service(tctx->lp_ctx, "sysvol"),
 				 lpcfg_default_service(tctx->lp_ctx), tctx);
@@ -111,7 +153,10 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 	/* Ensure the sysvol path exists */
 	gpo_dir = talloc_asprintf(ctx, "%s/%s", sysvol_path, GPODIR);
 	mkdir_p(gpo_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+	env_dir = talloc_asprintf(ctx, "%s/%s", sysvol_path, ENVPATH);
+	mkdir_p(env_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
 	gpo_file = talloc_asprintf(ctx, "%s/%s", gpo_dir, GPOFILE);
+	env_xml = talloc_asprintf(ctx, "%s/%s", env_dir, ENVXML);
 
 	/* Get the gpo update command */
 	gpo_update_cmd = lpcfg_gpo_update_command(tctx->lp_ctx);
@@ -130,6 +175,10 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 
 	old_message = result->msgs[0];
 
+	smbconf = lpcfg_configfile(tctx->lp_ctx);
+	profile = talloc_strndup(ctx, smbconf, strlen(smbconf)-8);
+	profile = talloc_strdup_append(profile, "profile");
+
 	for (i = 0; i < 3; i++) {
 		/* Write out the sysvol */
 		if ( (fp = fopen(gpo_file, "w")) ) {
@@ -138,6 +187,12 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 					      pwdpropcases[i]), fp);
 			fclose(fp);
 		}
+		if ( (fp = fopen(env_xml, "w")) ) {
+			fputs(talloc_asprintf(ctx, ENVTMPL, envcases[i][0],
+					      envcases[i][1], envcases[i][2],
+					      envcases[i][3]), fp);
+			fclose(fp);
+		}
 
 		/* Update the version in the GPT.INI */
 		gpt_file = talloc_asprintf(ctx, "%s/%s", sysvol_path, GPTINI);
@@ -199,6 +254,20 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 							-1),
 					       pwdpropcases[i],
 			       "The pwdProperties were not applied");
+
+		/* Machine env var policy */
+		envexpected = talloc_asprintf(ctx, RESENV, envres[i]);
+		fd = open(profile, O_RDONLY);
+		envlen = strlen(envexpected);
+		torture_assert(tctx, lseek(fd, -envlen,
+					   SEEK_END) != -1,
+			       "Failed to seek to start position in profile");
+		envreturned = talloc_array(ctx, char, envlen);
+		torture_assert(tctx, read(fd, envreturned, envlen) == envlen,
+				"Failed to read from profile");
+		torture_assert(tctx, strncmp(envexpected, envreturned, envlen)\
+				     == 0,
+				"Environment variable policy was not applied");
 	}
 
 	/* Unapply the settings and verify they are removed */
@@ -260,6 +329,16 @@ bool torture_gpo_system_access_policies(struct torture_context *tctx)
 						-2),
 			"The pwdProperties were not unapplied");
 
+	/* Machine env var policy */
+	fd = open(profile, O_RDONLY);
+	if (lseek(fd, -envlen, SEEK_END) != -1) {
+		if (read(fd, envreturned, envlen) == envlen) {
+			torture_assert(tctx, strncmp(envexpected, envreturned,
+						     envlen) != 0,
+			    "Environment variable policy was not unapplied");
+		}
+	}
+
 	talloc_free(ctx);
 	return true;
 }
-- 
2.13.6


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

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

diff --git a/docs-xml/smbdotconf/winbind/winbindgpupdate.xml b/docs-xml/smbdotconf/winbind/winbindgpupdate.xml
index a35ee88d399..0179c7b7bd3 100644
--- a/docs-xml/smbdotconf/winbind/winbindgpupdate.xml
+++ b/docs-xml/smbdotconf/winbind/winbindgpupdate.xml
@@ -13,6 +13,6 @@
 
 </description>
 
-<value type="default">no</value>
-<value type="example">yes</value>
+<value type="default">yes</value>
+<value type="example">no</value>
 </samba:parameter>
diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c
index ae0e0d10681..ecf72e48991 100644
--- a/lib/param/loadparm.c
+++ b/lib/param/loadparm.c
@@ -2731,7 +2731,7 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx)
 	lpcfg_do_global_parameter(lp_ctx, "winbindd socket directory", dyn_WINBINDD_SOCKET_DIR);
 	lpcfg_do_global_parameter(lp_ctx, "ntp signd socket directory", dyn_NTP_SIGND_SOCKET_DIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "gpo update command", "%s/samba_gpoupdate", dyn_SCRIPTSBINDIR);
-	lpcfg_do_global_parameter_var(lp_ctx, "winbind gpupdate", "False");
+	lpcfg_do_global_parameter_var(lp_ctx, "winbind gpupdate", "True");
 	lpcfg_do_global_parameter_var(lp_ctx, "dns update command", "%s/samba_dnsupdate", dyn_SCRIPTSBINDIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "spn update command", "%s/samba_spnupdate", dyn_SCRIPTSBINDIR);
 	lpcfg_do_global_parameter_var(lp_ctx, "samba kcc command",
diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c
index 96ed41b804f..abe34e39ca5 100644
--- a/source3/param/loadparm.c
+++ b/source3/param/loadparm.c
@@ -926,7 +926,7 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals)
 	Globals.gpo_update_command = str_list_make_v3_const(NULL, s, NULL);
 	TALLOC_FREE(s);
 
-	Globals.winbind_gpupdate = false;
+	Globals.winbind_gpupdate = true;
 
 	s = talloc_asprintf(talloc_tos(), "%s/samba_spnupdate", get_dyn_SCRIPTSBINDIR());
 	if (s == NULL) {
-- 
2.13.6



More information about the samba-technical mailing list