Hello community,
here is the log from the commit of package yast-gpmc for openSUSE:Factory checked in at 2018-05-30 12:23:51
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/yast-gpmc (Old)
and /work/SRC/openSUSE:Factory/.yast-gpmc.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "yast-gpmc"
Wed May 30 12:23:51 2018 rev:2 rq:612934 version:1.3.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/yast-gpmc/yast-gpmc.changes 2017-11-29 10:53:51.482042237 +0100
+++ /work/SRC/openSUSE:Factory/.yast-gpmc.new/yast-gpmc.changes 2018-05-30 12:42:50.758178382 +0200
@@ -1,0 +2,5 @@
+Tue May 29 20:12:50 UTC 2018 - dmulder@suse.com
+
+- v1.3.1: Lots of improvements and fixes, py3 compatability, etc.
+
+-------------------------------------------------------------------
Old:
----
yast-gpmc-1.0.tar.gz
New:
----
yast-gpmc-1.3.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ yast-gpmc.spec ++++++
--- /var/tmp/diff_new_pack.tqNUCk/_old 2018-05-30 12:42:51.522162074 +0200
+++ /var/tmp/diff_new_pack.tqNUCk/_new 2018-05-30 12:42:51.526161989 +0200
@@ -1,7 +1,7 @@
#
# spec file for package yast-gpmc
#
-# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -17,7 +17,7 @@
Name: yast-gpmc
-Version: 1.0
+Version: 1.3.1
Release: 0
Summary: Group Policy Management Console for YaST
License: GPL-3.0
@@ -26,6 +26,7 @@
Source: %{name}-%{version}.tar.gz
BuildArch: noarch
Requires: krb5-client
+Requires: python-configparser
Requires: python-ldap
Requires: samba-client
Requires: samba-python
++++++ yast-gpmc-1.0.tar.gz -> yast-gpmc-1.3.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/.gitignore new/yast-gpmc-1.3.1/.gitignore
--- old/yast-gpmc-1.0/.gitignore 2017-11-08 19:20:04.000000000 +0100
+++ new/yast-gpmc-1.3.1/.gitignore 2018-05-29 20:44:25.000000000 +0200
@@ -9,3 +9,4 @@
autom4te.cache
*.pyc
src/gpmc.desktop
+src/gpmc.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/configure.ac new/yast-gpmc-1.3.1/configure.ac
--- old/yast-gpmc-1.0/configure.ac 2017-11-08 19:20:04.000000000 +0100
+++ new/yast-gpmc-1.3.1/configure.ac 2018-05-29 20:44:25.000000000 +0200
@@ -5,6 +5,7 @@
AC_INIT([yast2-gpmc],[1.0],[http://bugs.opensuse.org/],[yast2-gpmc])
AM_INIT_AUTOMAKE
+AM_PATH_PYTHON([2.7])
dnl Checks for programs.
AC_PROG_INSTALL
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/package/yast-gpmc.spec new/yast-gpmc-1.3.1/package/yast-gpmc.spec
--- old/yast-gpmc-1.0/package/yast-gpmc.spec 1970-01-01 01:00:00.000000000 +0100
+++ new/yast-gpmc-1.3.1/package/yast-gpmc.spec 2018-05-29 20:44:25.000000000 +0200
@@ -0,0 +1,59 @@
+#
+# spec file for package yast-gpmc
+#
+
+Name: yast-gpmc
+Version: 1.0
+Release: 1
+License: GPL-3.0
+Summary: Group Policy Management Console for YaST
+Url: http://www.github.com/dmulder/yast-gpmc
+Group: Productivity/Networking/Samba
+Source: %{name}-%{version}.tar.gz
+BuildArch: noarch
+Requires: yast2-python-bindings >= 4.0.0
+Requires: samba-client
+Requires: python-ldap
+Requires: samba-python
+Requires: krb5-client
+Requires: yast2
+BuildRequires: autoconf
+BuildRequires: automake
+BuildRequires: python
+BuildRequires: perl-XML-Writer
+BuildRequires: update-desktop-files
+BuildRequires: yast2
+BuildRequires: yast2-devtools
+BuildRequires: yast2-testsuite
+
+%description
+The Group Policy Management console for YaST provides tools for creating and
+modifying Group Policy Objects in Active Directory.
+
+%prep
+%setup -q
+
+%build
+autoreconf -if
+%configure --prefix=%{_prefix}
+make
+
+%install
+make DESTDIR=$RPM_BUILD_ROOT install
+
+%clean
+%{__rm} -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root)
+%dir %{_datadir}/YaST2/include/gpmc
+%{_datadir}/YaST2/clients/gpmc.py
+%{_datadir}/YaST2/include/gpmc/complex.py
+%{_datadir}/YaST2/include/gpmc/dialogs.py
+%{_datadir}/YaST2/include/gpmc/wizards.py
+%{_datadir}/YaST2/include/gpmc/defaults.py
+%{_datadir}/applications/YaST2/gpmc.desktop
+%dir %{_datadir}/doc/yast2-gpmc
+%{_datadir}/doc/yast2-gpmc/COPYING
+
+%changelog
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/src/Makefile.am new/yast-gpmc-1.3.1/src/Makefile.am
--- old/yast-gpmc-1.0/src/Makefile.am 2017-11-08 19:20:04.000000000 +0100
+++ new/yast-gpmc-1.3.1/src/Makefile.am 2018-05-29 20:44:25.000000000 +0200
@@ -22,9 +22,13 @@
moduledir = ${prefix}/share/YaST2/modules
+gpmc.py: gpmc.py.in
+ sed -e 's;[@]INCLUDEDIR[@];$(yncludedir);g' < $(srcdir)/gpmc.py.in > $(srcdir)/gpmc.py
+
# create a symlink for local build, #145327
gpmc:
ln -sf . $@
ycpchook = gpmc
EXTRA_DIST = $(client_DATA) $(ynclude_DATA) $(module_DATA) $(desktop_DATA)
+CLEANFILES = gpmc.py gpmc.desktop
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/src/complex.py new/yast-gpmc-1.3.1/src/complex.py
--- old/yast-gpmc-1.0/src/complex.py 2017-11-17 16:10:53.000000000 +0100
+++ new/yast-gpmc-1.3.1/src/complex.py 2018-05-29 20:44:25.000000000 +0200
@@ -1,7 +1,8 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
import ldap, ldap.modlist, ldap.sasl
from samba import smb
-from ConfigParser import ConfigParser
-from StringIO import StringIO
+from configparser import ConfigParser
+from io import StringIO
import xml.etree.ElementTree as etree
import os.path, sys
from samba.net import Net
@@ -11,30 +12,185 @@
from ldap.modlist import addModlist as addlist
from ldap.modlist import modifyModlist as modlist
import re
+import traceback
+import ldb
+from samba.dcerpc import security
+from samba.ndr import ndr_unpack
+import samba.security
+from samba.ntacls import dsacl2fsacl
+
+PY3 = sys.version_info[0] == 3
+PY2 = sys.version_info[0] == 2
+
+class LdapException(Exception):
+ def __init__(self, *args, **kwargs):
+ Exception.__init__(self, *args, **kwargs)
+ if len(self.args) > 0:
+ self.msg = self.args[0]
+ else:
+ self.msg = None
+ if len(self.args) > 1:
+ self.info = self.args[1]
+ else:
+ self.info = None
+
+def _ldap_exc_msg(e):
+ if len(e.args) > 0 and \
+ type(e.args[-1]) is dict and \
+ 'desc' in e.args[-1]:
+ return e.args[-1]['desc']
+ else:
+ return str(e)
+
+def _ldap_exc_info(e):
+ if len(e.args) > 0 and \
+ type(e.args[-1]) is dict and \
+ 'info' in e.args[-1]:
+ return e.args[-1]['info']
+ else:
+ return ''
+
+def ldap_search(l, *args):
+ try:
+ return l.search_s(*args)
+ except Exception as e:
+ traceback.print_exc(file=sys.stderr)
+ sys.stderr.write('ldap.search_s: %s\n' % _ldap_exc_msg(e))
+
+def ldap_add(l, *args):
+ try:
+ return l.add_s(*args)
+ except Exception as e:
+ raise LdapException(_ldap_exc_msg(e), _ldap_exc_info(e))
+
+def ldap_modify(l, *args):
+ try:
+ return l.modify(*args)
+ except Exception as e:
+ traceback.print_exc(file=sys.stderr)
+ sys.stderr.write('ldap.modify: %s\n' % _ldap_exc_msg(e))
+
+def ldap_delete(l, *args):
+ try:
+ return l.delete_s(*args)
+ except Exception as e:
+ traceback.print_exc(file=sys.stderr)
+ sys.stderr.write('ldap.delete_s: %s\n' % _ldap_exc_msg(e))
+
+def open_bytes(filename):
+ if PY3:
+ return open(filename, errors='ignore')
+ else:
+ return open(filename, 'rb')
+
+def dict_to_bytes(d):
+ for key in d.keys():
+ if type(d[key]) is dict:
+ d[key] = dict_to_bytes(d[key])
+ elif type(d[key]) is list:
+ vals = []
+ for val in d[key]:
+ if type(val) is str:
+ vals.append(val.encode('utf-8'))
+ else:
+ vals.append(val)
+ d[key] = vals
+ elif type(d[key]) is str:
+ d[key] = d[key].encode('utf-8')
+ return d
+
+def stringify_ldap(data):
+ if type(data) == dict:
+ for key, value in data.items():
+ data[key] = stringify_ldap(value)
+ return data
+ elif type(data) == list:
+ new_list = []
+ for item in data:
+ new_list.append(stringify_ldap(item))
+ return new_list
+ elif type(data) == tuple:
+ new_tuple = []
+ for item in data:
+ new_tuple.append(stringify_ldap(item))
+ return tuple(new_tuple)
+ elif PY2 and type(data) == unicode:
+ return str(data)
+ elif PY3 and type(data) == bytes:
+ try:
+ return data.decode('utf-8')
+ except UnicodeDecodeError:
+ return data
+ else:
+ return data
+
+def parse_unc(unc):
+ '''Parse UNC string into a hostname, a service, and a filepath'''
+ if unc.startswith('\\\\') and unc.startswith('//'):
+ raise ValueError("UNC doesn't start with \\\\ or //")
+ tmp = unc[2:].split('/', 2)
+ if len(tmp) == 3:
+ return tmp
+ tmp = unc[2:].split('\\', 2)
+ if len(tmp) == 3:
+ return tmp
+ raise ValueError("Invalid UNC string: %s" % unc)
+
+def dn_to_path(realm, dn):
+ base_dn = (','.join(['DC=%s' % part for part in realm.lower().split('.')])).encode('utf-8')
+ parts = [p.split(b'=')[-1].title() for p in dn.lower().replace(base_dn.lower(), b'').split(b',') if p]
+ parts.append(realm.encode('utf-8'))
+ return b'/'.join(reversed(parts))
+
+def parse_gplink(gplink):
+ '''parse a gPLink into an array of dn and options'''
+ ret = {}
+ a = gplink.split(b']')
+ for g in a:
+ if not g:
+ continue
+ d = g.split(b';')
+ if len(d) != 2 or not d[0].startswith(b"[LDAP://"):
+ raise RuntimeError("Badly formed gPLink '%s'" % g)
+ options = bin(int(d[1]))[2:].zfill(2)
+ name = d[0][8:].split(b',')[0][3:].decode()
+ ret[name] = {'enforced' : 'Yes' if int(options[-2]) else 'No', 'enabled' : 'No' if int(options[-1]) else 'Yes', 'dn' : d[0][8:].decode(), 'options' : int(d[1])}
+ return ret
+
+def encode_gplink(gplist):
+ '''Encode an array of dn and options into gPLink string'''
+ ret = ''
+ for g in gplist:
+ ret += "[LDAP://%s;%d]" % (g['dn'], g['options'])
+ return ret
class GPConnection:
def __init__(self, lp, creds):
self.lp = lp
self.creds = creds
- self.realm = lp.get('realm')
net = Net(creds=creds, lp=lp)
- cldap_ret = net.finddc(domain=self.realm, flags=(nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS))
+ cldap_ret = net.finddc(domain=lp.get('realm'), flags=(nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE))
+ self.realm = cldap_ret.dns_domain
+ self.dc_hostname = cldap_ret.pdc_dns_name
self.l = ldap.initialize('ldap://%s' % cldap_ret.pdc_dns_name)
if self.__kinit_for_gssapi():
auth_tokens = ldap.sasl.gssapi('')
self.l.sasl_interactive_bind_s('', auth_tokens)
else:
self.l.bind_s('%s@%s' % (creds.get_username(), self.realm) if not self.realm in creds.get_username() else creds.get_username(), creds.get_password())
+ self.l.set_option(ldap.OPT_REFERRALS,0)
def __kinit_for_gssapi(self):
p = Popen(['kinit', '%s@%s' % (self.creds.get_username(), self.realm) if not self.realm in self.creds.get_username() else self.creds.get_username()], stdin=PIPE, stdout=PIPE)
- p.stdin.write('%s\n' % self.creds.get_password())
+ p.stdin.write(('%s\n'%self.creds.get_password()).encode())
+ p.stdin.flush()
return p.wait() == 0
def realm_to_dn(self, realm):
return ','.join(['DC=%s' % part for part in realm.lower().split('.')])
def __well_known_container(self, container):
+ res = None
if container == 'system':
wkguiduc = 'AB1D30F3768811D1ADED00C04FD8D5CD'
elif container == 'computers':
@@ -43,51 +199,207 @@
wkguiduc = 'A361B2FFFFD211D1AA4B00C04FD7D83A'
elif container == 'users':
wkguiduc = 'A9D1CA15768811D1ADED00C04FD8D5CD'
- result = self.l.search_s('' % (wkguiduc, self.realm_to_dn(self.realm)), ldap.SCOPE_SUBTREE, '(objectClass=container)', ['distinguishedName'])
+ result = ldap_search(self.l, '' % (wkguiduc, self.realm_to_dn(self.realm)), ldap.SCOPE_SUBTREE, '(objectClass=container)', stringify_ldap(['distinguishedName']))
+ result = stringify_ldap(result)
if result and len(result) > 0 and len(result[0]) > 1 and 'distinguishedName' in result[0][1] and len(result[0][1]['distinguishedName']) > 0:
- return result[0][1]['distinguishedName'][-1]
+ res = result[0][1]['distinguishedName'][-1]
+
+ return stringify_ldap(res)
- def gpo_list(self):
- return self.l.search_s(self.__well_known_container('system'), ldap.SCOPE_SUBTREE, '(objectCategory=groupPolicyContainer)', [])
+ def user_from_sid(self, sid, attrs=[]):
+ res = ldap_search(self.l, self.__well_known_container('users'), ldap.SCOPE_SUBTREE, '(objectSID=%s)' % sid, stringify_ldap(attrs))
+ return res[0][1]
+
+ def get_domain_sid(self):
+ res = ldap_search(self.l, self.realm_to_dn(self.realm), ldap.SCOPE_BASE, "(objectClass=*)", [])
+ return ndr_unpack(security.dom_sid, res[0][1]["objectSid"][0])
+
+ def gpo_list(self, displayName=None, attrs=[]):
+ result = None
+ res = self.__well_known_container('system')
+ search_expr = '(objectClass=groupPolicyContainer)'
+ if displayName is not None:
+ search_expr = '(&(objectClass=groupPolicyContainer)(displayname=%s))' % ldb.binary_encode(displayName)
+ result = ldap_search(self.l, res, ldap.SCOPE_SUBTREE, search_expr, stringify_ldap(attrs))
+ result = stringify_ldap(result)
+ return result
def set_attr(self, dn, key, value):
- self.l.modify(dn, [(1, key, None), (0, key, value)])
+ ldap_modify(self.l, dn, stringify_ldap([(1, key, None), (0, key, value)]))
+
+ def create_gpo(self, displayName, container=None):
+ msg = self.gpo_list(displayName)
+ if len(msg) > 0:
+ print("A GPO already existing with name '%s'" % displayName)
+ return
- def create_gpo(self, displayName):
gpouuid = uuid.uuid4()
realm_dn = self.realm_to_dn(self.realm)
name = '{%s}' % str(gpouuid).upper()
dn = 'CN=%s,CN=Policies,CN=System,%s' % (name, realm_dn)
- ldap_mod = { 'displayName': [displayName], 'gPCFileSysPath': ['\\\\%s\\SysVol\\%s\\Policies\\%s' % (self.realm, self.realm, name)], 'objectClass': ['top', 'container', 'groupPolicyContainer'], 'gPCFunctionalityVersion': ['2'], 'flags': ['0'], 'versionNumber': ['0'] }
+ unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (self.realm, self.realm, name)
+ ldap_mod = { 'displayName': [displayName.encode('utf-8')], 'gPCFileSysPath': [unc_path.encode('utf-8')], 'objectClass': [b'groupPolicyContainer'], 'gPCFunctionalityVersion': [b'2'], 'flags': [b'0'], 'versionNumber': [b'0'] }
# gPCMachineExtensionNames MUST be assigned as gpos are modified (currently not doing this!)
machine_dn = 'CN=Machine,%s' % dn
user_dn = 'CN=User,%s' % dn
- sub_ldap_mod = { 'objectClass': ['top', 'container'] }
+ sub_ldap_mod = { 'objectClass': [b'container'] }
- gpo = GPOConnection(self.lp, self.creds, ldap_mod['gPCFileSysPath'][-1])
+ gpo = GPOConnection(self.lp, self.creds, unc_path)
try:
- self.l.add_s(dn, addlist(ldap_mod))
- self.l.add_s(machine_dn, addlist(sub_ldap_mod))
- self.l.add_s(user_dn, addlist(sub_ldap_mod))
+ ldap_add(self.l, dn, addlist(stringify_ldap(ldap_mod)))
+ ldap_add(self.l, machine_dn, addlist(stringify_ldap(sub_ldap_mod)))
+ ldap_add(self.l, user_dn, addlist(stringify_ldap(sub_ldap_mod)))
+ except LdapException as e:
+ traceback.print_exc(file=sys.stderr)
+ sys.stderr.write('ldap.add_s: %s\n' % e.info if e.info else e.msg)
+ gpo.initialize_empty_gpo(displayName)
+ if container:
+ self.set_link(dn, container)
+
+ def set_link(self, gpo_dn, container_dn, disabled=False, enforced=False):
+ gplink_options = 0
+ if disabled:
+ gplink_options |= (1 << 0)
+ if enforced:
+ gplink_options |= (1 << 1)
+
+ # Check if valid Container DN
+ msg = ldap_search(self.l,
+ container_dn, ldap.SCOPE_BASE,
+ "(objectClass=*)",
+ stringify_ldap(['gPLink']))[0][1]
+
+ # Update existing GPlinks or Add new one
+ existing_gplink = False
+ if 'gPLink' in msg:
+ gplist = parse_gplink(msg['gPLink'][0])
+ gplist = [gplist[k] for k in gplist]
+ existing_gplink = True
+ found = False
+ for g in gplist:
+ if g['dn'].lower() == gpo_dn.lower():
+ found = True
+ break
+ if found:
+ print("GPO '%s' already linked to this container" % gpo)
+ return
+ else:
+ gplist.insert(0, { 'dn' : gpo_dn, 'options' : gplink_options })
+ else:
+ gplist = []
+ gplist.append({ 'dn' : gpo_dn, 'options' : gplink_options })
+
+ gplink_str = encode_gplink(gplist)
- gpo.initialize_empty_gpo()
- # TODO: GPO links
+ if existing_gplink:
+ ldap_modify(self.l, container_dn, stringify_ldap([(1, 'gPLink', None), (0, 'gPLink', [gplink_str.encode('utf-8')])]))
+ else:
+ ldap_modify(self.l, container_dn, stringify_ldap([(0, 'gPLink', [gplink_str.encode('utf-8')])]))
+
+ def delete_link(self, gpo_dn, container_dn):
+ # Check if valid Container DN
+ msg = ldap_search(self.l,
+ container_dn, ldap.SCOPE_BASE,
+ "(objectClass=*)",
+ stringify_ldap(['gPLink']))[0][1]
+
+ found = False
+ if 'gPLink' in msg:
+ gplist = parse_gplink(msg['gPLink'][0])
+ gplist = [gplist[k] for k in gplist]
+ for g in gplist:
+ if g['dn'].lower() == gpo_dn.lower():
+ gplist.remove(g)
+ found = True
+ break
+ else:
+ raise Exception("No GPO(s) linked to this container")
+
+ if not found:
+ raise Exception("GPO '%s' not linked to this container" % gpo_dn)
+
+ if gplist:
+ gplink_str = encode_gplink(gplist)
+ ldap_modify(self.l, container_dn, stringify_ldap([(ldap.MOD_DELETE, 'gPLink', None), (ldap.MOD_ADD, 'gPLink', [gplink_str.encode('utf-8')])]))
+ else:
+ ldap_modify(self.l, container_dn, stringify_ldap([(ldap.MOD_DELETE, 'gPLink', None)]))
+
+ def delete_gpo(self, displayName):
+ msg = self.gpo_list(displayName)
+ if len(msg) == 0:
+ raise Exception("GPO '%s' does not exist" % displayName)
+
+ unc_path = msg[0][1]['gPCFileSysPath'][0]
+ gpo_dn = msg[0][1]['distinguishedName'][0]
+
+ # Remove links before deleting
+ linked_containers = self.get_gpo_containers(gpo_dn)
+ for container in linked_containers:
+ self.delete_link(gpo_dn, container['distinguishedName'][0].decode())
+
+ # Remove LDAP entries
+ ldap_delete(self.l, "CN=User,%s" % str(gpo_dn))
+ ldap_delete(self.l, "CN=Machine,%s" % str(gpo_dn))
+ ldap_delete(self.l, gpo_dn)
+ try:
+ # Remove GPO files
+ gpo = GPOConnection(self.lp, self.creds, unc_path)
+ gpo.cleanup_gpo()
except Exception as e:
- print str(e)
+ print(str(e))
+ traceback.print_exc(file=sys.stdout)
+
+ def get_gpo_containers(self, gpo):
+ '''lists dn of containers for a GPO'''
+
+ search_expr = "(&(objectClass=*)(gPLink=*%s*))" % gpo
+ msg = ldap_search(self.l, self.realm_to_dn(self.realm), ldap.SCOPE_SUBTREE, search_expr, [])
+ if not msg:
+ return []
+
+ return [res[1] for res in msg if type(res[1]) is dict]
+
+ def get_gpos_for_container(self, container_dn):
+ search_expr = '(distinguishedName=%s)' % container_dn
+ msg = ldap_search(self.l, self.realm_to_dn(self.realm), ldap.SCOPE_SUBTREE, search_expr, [])
+ if not msg:
+ return None
+
+ results = []
+ if 'gPLink' in msg[0][1]:
+ gpos = parse_gplink(msg[0][1]['gPLink'][-1])
+ else:
+ gpos = []
+ for gpo in gpos:
+ search_expr = '(distinguishedName=%s)' % gpos[gpo]['dn']
+ msg = ldap_search(self.l, self.realm_to_dn(self.realm), ldap.SCOPE_SUBTREE, search_expr, [])
+ results.append(msg[0])
+
+ return results
+
+ def get_containers_with_gpos(self):
+ search_expr = "(|(objectClass=organizationalUnit)(objectClass=domain))"
+ msg = ldap_search(self.l, self.realm_to_dn(self.realm), ldap.SCOPE_SUBTREE, search_expr, [])
+ if not msg:
+ return []
+
+ return [res[1] for res in msg if type(res[1]) is dict]
class GPOConnection(GPConnection):
def __init__(self, lp, creds, gpo_path):
GPConnection.__init__(self, lp, creds)
+ [dom_name, service, self.path] = parse_unc(gpo_path)
path_parts = [n for n in gpo_path.split('\\') if n]
- self.path_start = '\\\\' + '\\'.join(path_parts[:2])
- self.path = '\\'.join(path_parts[2:])
- self.name = path_parts[-1]
+ self.path_start = '\\\\' + '\\'.join([dom_name, service])
+ self.name = gpo_path.split('\\')[-1]
self.realm_dn = self.realm_to_dn(self.realm)
self.gpo_dn = 'CN=%s,CN=Policies,CN=System,%s' % (self.name, self.realm_dn)
try:
- self.conn = smb.SMB(path_parts[0], path_parts[1], lp=self.lp, creds=self.creds)
- except:
+ self.conn = smb.SMB(self.dc_hostname, service, lp=self.lp, creds=self.creds)
+ except Exception as e:
+ print ("Exception %s"%str(e))
+ traceback.print_exc(file=sys.stdout)
self.conn = None
def update_machine_gpe_ini(self, extension):
@@ -97,6 +409,8 @@
machine_extension_versions = ''
if ini_conf.has_option('General', 'MachineExtensionVersions'):
machine_extension_versions = ini_conf.get('General', 'MachineExtensionVersions').encode('ascii')
+ if type(machine_extension_versions) is bytes:
+ machine_extension_versions = machine_extension_versions.decode('utf-8')
itr = re.finditer('\[%s:\d+]' % extension, machine_extension_versions)
try:
new_ext_str = machine_extension_versions[:m.start()] + machine_extension_versions[m.end():]
@@ -109,11 +423,35 @@
ini_conf.set('General', 'MachineExtensionVersions', machine_extension_versions)
self.write('Group Policy\\GPE.INI', ini_conf)
- def initialize_empty_gpo(self):
+ def initialize_empty_gpo(self, displayName):
+ # Get new security descriptor
+ ds_sd_flags = ( security.SECINFO_OWNER |
+ security.SECINFO_GROUP |
+ security.SECINFO_DACL )
+ msg = self.gpo_list(displayName, attrs=stringify_ldap(['nTSecurityDescriptor']))
+ ds_sd_ndr = msg[0][1]['nTSecurityDescriptor'][0]
+ ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
+
+ # Create a file system security descriptor
+ domain_sid = self.get_domain_sid()
+ sddl = dsacl2fsacl(ds_sd, domain_sid)
+ fs_sd = security.descriptor.from_sddl(sddl, domain_sid)
+
self.__smb_mkdir_p('\\'.join([self.path, 'MACHINE']))
self.__smb_mkdir_p('\\'.join([self.path, 'USER']))
+
+ # Set ACL
+ sio = ( security.SECINFO_OWNER |
+ security.SECINFO_GROUP |
+ security.SECINFO_DACL |
+ security.SECINFO_PROTECTED_DACL )
+ self.conn.set_acl(self.path, fs_sd, sio)
+
self.__increment_gpt_ini()
+ def cleanup_gpo(self):
+ self.conn.deltree(self.path)
+
def __get_gpo_version(self, ini_conf=None):
if not ini_conf:
ini_conf = self.parse('GPT.INI')
@@ -137,7 +475,7 @@
if not ini_conf.has_section('General'):
ini_conf.add_section('General')
- ini_conf.set('General', 'Version', current)
+ ini_conf.set('General', 'Version', str(current))
self.write('GPT.INI', ini_conf)
self.set_attr(self.gpo_dn, 'versionNumber', current)
@@ -170,73 +508,77 @@
def __parse_dn(self, dn):
dn = dn % self.gpo_dn
- try:
- resp = self.l.search_s(dn, ldap.SCOPE_SUBTREE, '(objectCategory=packageRegistration)', [])
+ resp = ldap_search(self.l, dn, ldap.SCOPE_SUBTREE, '(objectCategory=packageRegistration)', [])
+ resp = stringify_ldap(resp)
+ if resp:
keys = ['objectClass', 'msiFileList', 'msiScriptPath', 'displayName', 'versionNumberHi', 'versionNumberLo']
results = {a[-1]['name'][-1]: {k: a[-1][k] for k in a[-1].keys() if k in keys} for a in resp}
- except Exception as e:
- if 'No such object' in str(e):
- results = {}
- else:
- raise
+ else:
+ results = {}
return results
def __mkdn_p(self, dn):
- attrs = { 'objectClass' : ['top', 'container'] }
+ attrs = { 'objectClass' : [b'top', b'container'] }
try:
- self.l.add_s(dn, addlist(attrs))
- except Exception as e:
- if e.args[-1]['desc'] == 'No such object':
+ ldap_add(self.l, dn, addlist(stringify_ldap(attrs)))
+ except LdapException as e:
+ if e.msg == 'No such object':
self.__mkdn_p(','.join(dn.split(',')[1:]))
- elif e.args[-1]['desc'] == 'Already exists':
+ elif e.msg == 'Already exists':
return
else:
- sys.stderr.write(e.args[-1]['info'])
+ traceback.print_exc(file=sys.stderr)
+ sys.stderr.write('ldap.add_s: %s\n' % e.info if e.info else e.msg)
try:
- self.l.add_s(dn, addlist(attrs))
- except Exception as e:
- if e.args[-1]['desc'] != 'Already exists':
- sys.stderr.write(e.args[-1]['info'])
+ ldap_add(self.l, dn, addlist(stringify_ldap(attrs)))
+ except LdapException as e:
+ if e.msg != 'Already exists':
+ traceback.print_exc(file=sys.stderr)
+ sys.stderr.write('ldap.add_s: %s\n' % e.info if e.info else e.msg)
def __write_dn(self, dn, ldap_config):
for cn in ldap_config.keys():
obj_dn = 'CN=%s,%s' % (cn, dn % self.gpo_dn)
if 'objectClass' not in ldap_config[cn]:
- ldap_config[cn]['objectClass'] = ['top', 'packageRegistration']
+ ldap_config[cn]['objectClass'] = [b'top', b'packageRegistration']
if 'msiFileList' not in ldap_config[cn]:
- ldap_config[cn]['msiFileList'] = os.path.splitext(ldap_config[cn]['msiScriptPath'][-1])[0] + '.zap'
+ ldap_config[cn]['msiFileList'] = [os.path.splitext(ldap_config[cn]['msiScriptPath'][-1])[0] + '.zap']
self.__mkdn_p(','.join(obj_dn.split(',')[1:]))
+ ldap_config[cn] = dict_to_bytes(ldap_config[cn])
try:
- self.l.add_s(obj_dn, addlist(ldap_config[cn]))
- except Exception as e:
- if e.args[-1]['desc'] == 'Already exists':
- try:
- self.l.modify_s(obj_dn, modlist({}, ldap_config[cn]))
- except Exception as e:
- sys.stderr.write(e.args[-1]['info'])
+ ldap_add(self.l, obj_dn, addlist(stringify_ldap(ldap_config[cn])))
+ except LdapException as e:
+ if e.msg == 'Already exists':
+ ldap_modify(self.l, obj_dn, modlist({}, stringify_ldap(ldap_config[cn])))
else:
- sys.stderr.write(e.args[-1]['info'])
+ traceback.print_exc(file=sys.stderr)
+ sys.stderr.write('ldap.add_s: %s\n' % e.info if e.info else e.msg)
- if os.path.splitext(ldap_config[cn]['msiFileList'][-1])[-1] == '.zap':
+ if os.path.splitext(ldap_config[cn]['msiFileList'][-1])[-1] == b'.zap':
inf_conf = self.__parse_inf(ldap_config[cn]['msiFileList'][-1])
if not inf_conf.has_section('Application'):
inf_conf.add_section('Application')
- inf_conf.set('Application', 'FriendlyName', ldap_config[cn]['displayName'][-1])
- inf_conf.set('Application', 'SetupCommand', 'rpm -i "%s"' % ldap_config[cn]['msiScriptPath'][-1])
- self.__write_inf(ldap_config[cn]['msiFileList'][-1], inf_conf)
+ inf_conf.set('Application', 'FriendlyName', ldap_config[cn]['displayName'][-1].decode('utf-8'))
+ inf_conf.set('Application', 'SetupCommand', 'rpm -i "%s"' % ldap_config[cn]['msiScriptPath'][-1].decode('utf-8'))
+ filename = ldap_config[cn]['msiFileList'][-1].split(self.path.encode('utf-8'))[-1]
+ self.__write_inf(filename, inf_conf)
def __parse_inf(self, filename):
inf_conf = ConfigParser()
if self.conn:
try:
policy = self.conn.loadfile('\\'.join([self.path, filename]))
- except:
+ except Exception as e:
+ sys.stderr.write(str(e))
policy = ''
inf_conf.optionxform=str
- try:
+ if PY3 and type(policy) is str:
inf_conf.readfp(StringIO(policy))
- except:
- inf_conf.readfp(StringIO(policy.decode('utf-16')))
+ else:
+ try:
+ inf_conf.readfp(StringIO(policy.decode('utf-8')))
+ except:
+ inf_conf.readfp(StringIO(policy.decode('utf-16')))
return inf_conf
def __parse_xml(self, filename):
@@ -254,31 +596,35 @@
try:
self.conn.mkdir(directory)
except Exception as e:
- if e[0] == -1073741766: # 0xC000003A: STATUS_OBJECT_PATH_NOT_FOUND
+ if e.args[0] == 0xC000003A: # STATUS_OBJECT_PATH_NOT_FOUND
self.__smb_mkdir_p(directory)
- elif e[0] == -1073741771: # 0xC0000035: STATUS_OBJECT_NAME_COLLISION
+ elif e.args[0] == 0xC0000035: # STATUS_OBJECT_NAME_COLLISION
pass
else:
- print e[1]
+ print(e.args[1])
try:
self.conn.mkdir(path)
except Exception as e:
- if e[0] == -1073741771: # 0xC0000035: STATUS_OBJECT_NAME_COLLISION
+ if e.args[0] == 0xC0000035: # STATUS_OBJECT_NAME_COLLISION
pass
else:
- print e[1]
+ print(e.args[1])
def __write(self, filename, text):
+ if type(filename) is bytes:
+ filename = filename.decode('utf-8')
path = '\\'.join([self.path, filename])
filedir = os.path.dirname((path).replace('\\', '/')).replace('/', '\\')
self.__smb_mkdir_p(filedir)
+ if PY3 and type(text) is str:
+ text = text.encode('utf-8')
try:
self.conn.savefile(path, text)
except Exception as e:
- if e[0] == -1073741766: # 0xC000003A: STATUS_OBJECT_PATH_NOT_FOUND
- print e[1] % (path)
+ if e.args[0] == 0xC000003A: # STATUS_OBJECT_PATH_NOT_FOUND
+ print(e.args[1] % (path))
else:
- print e[1]
+ print(e)
def __write_inf(self, filename, inf_config):
out = StringIO()
@@ -287,21 +633,22 @@
self.__write(filename, value)
def __write_xml(self, filename, xml_config):
- value = '<?xml version="1.0" encoding="utf-8"?>\r\n' + etree.tostring(xml_config, 'utf-8')
+ value = '<?xml version="1.0" encoding="utf-8"?>\r\n' + etree.tostring(xml_config, 'utf-8').decode('utf-8')
self.__write(filename, value)
def upload_file(self, local, remote_dir):
remote_path = '\\'.join([self.path, remote_dir])
self.__smb_mkdir_p(remote_path)
if os.path.exists(local):
- value = open(local).read()
+ value = open_bytes(local).read()
filename = '\\'.join([remote_path, os.path.basename(local)])
+ if PY3 and type(value) is str:
+ value = value.encode('utf-8')
try:
self.conn.savefile(filename, value)
except Exception as e:
- if e[0] == -1073741771: # 0xC0000035: STATUS_OBJECT_NAME_COLLISION
+ if e.args[0] == 0xC0000035: # STATUS_OBJECT_NAME_COLLISION
sys.stderr.write('The file \'%s\' already exists at \'%s\' and could not be saved.' % (os.path.basename(local), remote_path))
else:
- sys.stderr.write(e[1])
+ sys.stderr.write(e.args[1])
return filename
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/src/defaults.py new/yast-gpmc-1.3.1/src/defaults.py
--- old/yast-gpmc-1.0/src/defaults.py 2017-11-17 16:11:00.000000000 +0100
+++ new/yast-gpmc-1.3.1/src/defaults.py 2018-05-29 20:44:25.000000000 +0200
@@ -1,8 +1,11 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
import xml.etree.ElementTree as etree
import uuid
import os.path
from subprocess import Popen, PIPE
+from yast import import_module
+import_module('UI')
from yast import UI
def select_script(title, policy, conn):
full_path = UI.AskForExistingFile('/', '*.sh *.py *.pl', title)
@@ -15,12 +18,12 @@
def query_rpm(filename):
out,_ = Popen(['rpm', '-qip', filename], stdout=PIPE, stderr=PIPE).communicate()
- return {line.split(':')[0].strip() : ':'.join(line.split(':')[1:]).strip() for line in out.strip().split('\n')}
+ return {line.split(b':')[0].strip() : b':'.join(line.split(b':')[1:]).strip() for line in out.strip().split(b'\n')}
def select_exec(title, policy, conn):
full_path = UI.AskForExistingFile('/', '*.rpm', title)
rpm_data = query_rpm(full_path)
- others = {'Name' : rpm_data['Name'], 'Version': rpm_data['Release']}
+ others = {'Name' : rpm_data[b'Name'], 'Version': rpm_data[b'Release']}
path = '%s\\%s' % (conn.path_start, conn.upload_file(full_path, 'MACHINE\\Applications'))
return (others, path)
@@ -373,6 +376,17 @@
'options' : None,
},
},
+ 'partial' : {
+ 'order' : 4,
+ 'title' : 'Parital',
+ 'get' : a.find('Properties').attrib['partial'] if a is not None and a.find('Properties') is not None and 'partial' in a.find('Properties').attrib.keys() else '',
+ 'set' : (lambda v : a.find('Properties').set('partial', v)),
+ 'valstr' : (lambda v : 'False' if v and int(v) == 0 else 'True'),
+ 'input' : {
+ 'type' : 'ComboBox',
+ 'options' : {'True' : '1', 'False' : '0'},
+ },
+ },
'value' : {
'order' : 2,
'title' : 'Value',
@@ -880,5 +894,5 @@
}
if __name__ == "__main__":
- print Policies
+ print(Policies)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/src/dialogs.py new/yast-gpmc-1.3.1/src/dialogs.py
--- old/yast-gpmc-1.0/src/dialogs.py 2017-11-17 16:11:09.000000000 +0100
+++ new/yast-gpmc-1.3.1/src/dialogs.py 2018-05-29 20:44:25.000000000 +0200
@@ -1,7 +1,22 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
from defaults import Policies, fetch_inf_value
-from complex import GPConnection, GPOConnection
+from complex import GPConnection, GPOConnection, dn_to_path, parse_gplink
+from yast import import_module
+import_module('Wizard')
+import_module('UI')
from yast import *
import re
+from functools import cmp_to_key
+from samba.dcerpc import security
+from samba.ndr import ndr_unpack
+import samba.security
+from samba.ntacls import dsacl2fsacl
+
+def have_x():
+ from subprocess import Popen, PIPE
+ p = Popen(['xset', '-q'], stdout=PIPE, stderr=PIPE)
+ return p.wait() == 0
+have_advanced_gui = have_x()
selected_gpo = None
@@ -10,17 +25,28 @@
global selected_gpo
self.conn = GPOConnection(lp, creds, selected_gpo[1]['gPCFileSysPath'][-1])
+ def __reset(self):
+ global have_advanced_gui
+ if not have_advanced_gui:
+ Wizard.RestoreNextButton()
+ Wizard.SetContentsButtons('Group Policy Management Editor', self.__gpme_page(), 'Group Policy Management Editor', '', 'Close')
+ if have_advanced_gui:
+ Wizard.HideNextButton()
+ else:
+ Wizard.HideAbortButton()
+ Wizard.HideBackButton()
+
def Show(self):
if not self.conn:
return Symbol('back')
- Wizard.SetContentsButtons('Group Policy Management Editor', self.__gpme_page(), 'Group Policy Management Editor', 'Back', 'Close')
- Wizard.DisableAbortButton()
+ self.__reset()
UI.SetFocus('gpme_tree')
policy = None
while True:
ret = UI.UserInput()
- if str(ret) in ['back', 'abort', 'next']:
+ if str(ret) in ['back', 'abort', 'next', 'cancel']:
+ ret = 'back'
break
elif str(ret) == 'gpme_tree':
policy = UI.QueryWidget('gpme_tree', 'CurrentItem')
@@ -72,7 +98,8 @@
def __change_values_prompt(self, values):
items = []
- for value in sorted(values.iteritems(), cmp=(lambda a,b : a[-1]['order']-b[-1]['order'])):
+ ckey = cmp_to_key(lambda a,b : a[-1]['order']-b[-1]['order'])
+ for value in sorted(values.items(), key=ckey):
k = value[0]
if not value[-1]['input']:
continue
@@ -127,7 +154,7 @@
header = Header(*header)
for key in opts:
values = sorted(opts[key]['values'].values(), key=(lambda x : x['order']))
- vals = tuple([k['valstr'](k['get']) for k in values])
+ vals = tuple([k['valstr'](k['get'].decode('utf-8')) if type(k['get']) is bytes else k['valstr'](k['get']) for k in values])
items.append(Item(Id(key), *vals))
buttons = []
if terms['add']:
@@ -224,35 +251,57 @@
class GPMC:
def __init__(self, lp, creds):
- global selected_gpo
- self.__get_creds(creds)
+ global selected_gpo, have_advanced_gui
self.realm = lp.get('realm')
self.lp = lp
self.creds = creds
- try:
- self.q = GPConnection(lp, creds)
- self.gpos = self.q.gpo_list()
- except:
- self.gpos = []
+ self.gpos = []
selected_gpo = None
+ self.__setup_menus()
+ if have_advanced_gui:
+ Wizard.HideAbortButton()
+ Wizard.HideBackButton()
+ Wizard.HideNextButton()
+ self.got_creds = self.__get_creds(creds)
+ while self.got_creds:
+ try:
+ self.q = GPConnection(lp, creds)
+ self.gpos = self.q.gpo_list()
+ self.realm_dn = self.q.realm_to_dn(self.realm)
+ break
+ except Exception as e:
+ print(str(e))
+ creds.set_password('')
+ self.got_creds = self.__get_creds(creds)
+
+ def __setup_menus(self):
+ UI.WizardCommand(Term('DeleteMenus'))
+ UI.WizardCommand(Term('AddMenu', '&File', 'file-menu'))
+ UI.WizardCommand(Term('AddMenuEntry', 'file-menu', 'Close', 'abort'))
def __get_creds(self, creds):
- if not creds.get_username() or not creds.get_password():
- UI.OpenDialog(self.__password_prompt(creds.get_username(), creds.get_password()))
+ if not creds.get_password():
+ UI.OpenDialog(self.__password_prompt(creds.get_username()))
while True:
subret = UI.UserInput()
if str(subret) == 'creds_ok':
user = UI.QueryWidget('username_prompt', 'Value')
password = UI.QueryWidget('password_prompt', 'Value')
+ UI.CloseDialog()
+ if not password:
+ return False
creds.set_username(user)
creds.set_password(password)
- if str(subret) == 'creds_cancel' or str(subret) == 'creds_ok':
+ return True
+ if str(subret) == 'creds_cancel':
UI.CloseDialog()
- break
+ return False
+ return True
- def __password_prompt(self, user, password):
+ def __password_prompt(self, user):
return MinWidth(30, VBox(
- Left(TextEntry(Id('username_prompt'), Opt('hstretch'), 'Username')),
+ Left(Label('To continue, type an administrator password')),
+ Left(TextEntry(Id('username_prompt'), Opt('hstretch'), 'Username', user)),
Left(Password(Id('password_prompt'), Opt('hstretch'), 'Password')),
Right(HBox(
PushButton(Id('creds_ok'), 'OK'),
@@ -260,69 +309,168 @@
))
))
- def __select_gpo(self, gpo_guid):
- global selected_gpo
- selected_gpo = None
+ def __find_gpo(self, gpo_guid):
+ fgpo = None
for gpo in self.gpos:
if gpo[1]['name'][-1] == gpo_guid:
- selected_gpo = gpo
+ fgpo = gpo
break
- return selected_gpo
+ return fgpo
+
+ def add_gpo(self, container=None):
+ UI.OpenDialog(self.__name_gpo())
+ sret = UI.UserInput()
+ if str(sret) == 'ok_name_gpo':
+ gpo_name = UI.QueryWidget('gpo_name_entry', 'Value')
+ self.q.create_gpo(gpo_name, container)
+ UI.CloseDialog()
+ try:
+ self.gpos = self.q.gpo_list()
+ except:
+ self.gpos = []
+
+ def del_gpo(self, displayName):
+ UI.OpenDialog(self.__request_delete_gpo())
+ sret = UI.UserInput()
+ if str(sret) == 'delete_gpo':
+ self.q.delete_gpo(displayName)
+ UI.CloseDialog()
+ try:
+ self.gpos = self.q.gpo_list()
+ except:
+ self.gpos = []
+
+ def del_link(self, child, parent):
+ UI.OpenDialog(self.__request_delete_link())
+ sret = UI.UserInput()
+ if str(sret) == 'delete_link':
+ self.q.delete_link(child, parent)
+ UI.CloseDialog()
+ try:
+ self.gpos = self.q.gpo_list()
+ except:
+ self.gpos = []
+
+ def __reset(self):
+ global have_advanced_gui
+ if not have_advanced_gui:
+ Wizard.RestoreBackButton()
+ Wizard.RestoreNextButton()
+ Wizard.RestoreAbortButton()
+ Wizard.SetContentsButtons('Group Policy Management Console', self.__gpmc_page(), self.__help(), 'Back', 'Edit GPO')
+ if have_advanced_gui:
+ Wizard.HideAbortButton()
+ Wizard.HideBackButton()
+ Wizard.HideNextButton()
+ else:
+ Wizard.DisableBackButton()
+ Wizard.DisableNextButton()
def Show(self):
global selected_gpo
- Wizard.SetContentsButtons('Group Policy Management Console', self.__gpmc_page(), self.__help(), 'Back', 'Edit GPO')
- Wizard.DisableBackButton()
- Wizard.DisableNextButton()
+ if not self.got_creds:
+ return Symbol('abort')
+ self.__reset()
UI.SetFocus('gpmc_tree')
current_page = 'Domains'
old_gpo_guid = None
gpo_guid = None
while True:
- ret = UI.UserInput()
+ event = UI.WaitForEvent()
+ if 'WidgetID' in event:
+ ret = event['WidgetID']
+ elif 'ID' in event:
+ ret = event['ID']
+ else:
+ raise Exception('ID not found in response %s' % str(event))
old_gpo_guid = gpo_guid
gpo_guid = UI.QueryWidget('gpmc_tree', 'CurrentItem')
- if str(ret) in ['back', 'abort']:
+ if str(ret) in ['back', 'abort', 'cancel']:
break
elif str(ret) == 'next':
break
elif str(ret) == 'add_gpo':
- UI.OpenDialog(self.__name_gpo())
- while True:
- sret = UI.UserInput()
- if str(sret) == 'ok_name_gpo':
- gpo_name = UI.QueryWidget('gpo_name_entry', 'Value')
- self.q.create_gpo(gpo_name)
- UI.CloseDialog()
- try:
- self.gpos = self.q.gpo_list()
- except:
- self.gpos = []
- Wizard.SetContentsButtons('Group Policy Management Console', self.__gpmc_page(), self.__help(), 'Back', 'Edit GPO')
- break
+ self.add_gpo()
+ self.__reset()
+ UI.ReplaceWidget('rightPane', self.__container(gpo_guid))
+ current_page = 'Realm'
+ elif str(ret) == 'del_gpo':
+ self.del_gpo(UI.QueryWidget('link_order', 'CurrentItem'))
+ self.__reset()
+ UI.ReplaceWidget('rightPane', self.__container(gpo_guid))
+ current_page = 'Realm'
+ elif ret == 'gpmc_tree' and event['EventReason'] == 'ContextMenuActivated':
+ parent = UI.QueryWidget('gpmc_tree', 'CurrentBranch')[-2]
+ if gpo_guid == 'Group Policy Objects':
+ UI.OpenContextMenu(self.__objs_context_menu())
+ elif gpo_guid != 'Domains' and self.__find_gpo(gpo_guid):
+ if parent != 'Group Policy Objects' and parent != self.realm_dn:
+ UI.OpenContextMenu(self.__gpo_context_menu(parent))
+ else:
+ UI.OpenContextMenu(self.__gpo_context_menu())
+ elif gpo_guid != 'Domains':
+ UI.OpenContextMenu(self.__objs_context_menu(gpo_guid))
+ elif ret == 'edit_gpo':
+ selected_gpo = self.__find_gpo(gpo_guid)
+ ret = 'next'
+ break
+ elif ret == 'context_del_gpo':
+ selected_gpo = self.__find_gpo(gpo_guid)
+ current_page = self.del_gpo(selected_gpo[1]['displayName'][-1])
+ self.__reset()
+ UI.ReplaceWidget('rightPane', Empty())
+ current_page = None
+ elif ret == 'context_del_link':
+ selected_gpo = self.__find_gpo(gpo_guid)
+ parent = UI.QueryWidget('gpmc_tree', 'CurrentBranch')[-2]
+ self.del_link(selected_gpo[1]['distinguishedName'][-1], parent)
+ self.__reset()
+ UI.ReplaceWidget('rightPane', Empty())
+ current_page = None
+ elif ret == 'context_add_gpo':
+ self.add_gpo()
+ self.__reset()
+ UI.ReplaceWidget('rightPane', Empty())
+ current_page = None
+ elif ret == 'context_add_gpo_and_link':
+ self.add_gpo(gpo_guid)
+ self.__reset()
+ UI.ReplaceWidget('rightPane', Empty())
+ current_page = None
elif UI.HasSpecialWidget('DumbTab'):
if gpo_guid == 'Domains':
if current_page != None:
Wizard.DisableNextButton()
UI.ReplaceWidget('rightPane', Empty())
current_page = None
- elif gpo_guid == self.realm:
+ elif gpo_guid == self.realm_dn:
if current_page != 'Realm':
Wizard.DisableNextButton()
- UI.ReplaceWidget('rightPane', self.__realm())
+ UI.ReplaceWidget('rightPane', self.__container(gpo_guid))
current_page = 'Realm'
+ if ret == 'Linked Group Policy Objects':
+ UI.ReplaceWidget(Id('realm_tabContainer'), self.__container_links(gpo_guid))
+ elif ret == 'Delegation':
+ UI.ReplaceWidget(Id('realm_tabContainer'), self.__realm_delegation())
+ elif ret == 'Group Policy Inheritance':
+ UI.ReplaceWidget(Id('realm_tabContainer'), self.__realm_inheritance())
+ elif gpo_guid == 'Group Policy Objects':
+ if current_page != 'Group Policy Objects':
+ Wizard.DisableNextButton()
+ UI.ReplaceWidget('rightPane', Empty())
+ current_page = 'Group Policy Objects'
+ elif gpo_guid.lower().startswith('ou='):
+ UI.ReplaceWidget('rightPane', self.__container(gpo_guid))
+ current_page = None
else:
- if str(ret) == 'advanced':
- self.__gpo_tab_adv(gpo_guid)
- continue
if current_page != 'Dumbtab' or old_gpo_guid != gpo_guid:
Wizard.EnableNextButton()
- selected_gpo = self.__select_gpo(gpo_guid)
+ selected_gpo = self.__find_gpo(gpo_guid)
UI.ReplaceWidget('rightPane', self.__gpo_tab(gpo_guid))
current_page = 'Dumbtab'
if str(ret) == 'Scope':
- UI.ReplaceWidget('gpo_tabContents', self.__scope_page())
+ UI.ReplaceWidget('gpo_tabContents', self.__scope_page(gpo_guid))
elif str(ret) == 'Details':
UI.ReplaceWidget('gpo_tabContents', self.__details_page(gpo_guid))
elif str(ret) == 'Settings':
@@ -342,6 +490,26 @@
return Symbol(ret)
+ def __gpo_context_menu(self, parent=None):
+ if parent:
+ delete_id = 'context_del_link'
+ else:
+ delete_id = 'context_del_gpo'
+ return Term('menu', [
+ Item(Id('edit_gpo'), 'Edit...'),
+ Item(Id(delete_id), 'Delete')
+ ])
+
+ def __objs_context_menu(self, container=None):
+ if container:
+ return Term('menu', [
+ Item(Id('context_add_gpo_and_link'), 'Create a GPO in this domain, and Link it here...')
+ ])
+ else:
+ return Term('menu', [
+ Item(Id('context_add_gpo'), 'New')
+ ])
+
def __name_gpo(self):
return MinWidth(30, VBox(
TextEntry(Id('gpo_name_entry'), Opt('hstretch'), 'GPO Name'),
@@ -351,13 +519,47 @@
))
))
+ def __request_delete_gpo(self):
+ return MinWidth(30, VBox(
+ Label('Do you want to delete this GPO and all links to it in this\ndomain? This will not delete links in other domains.'),
+ Right(HBox(
+ PushButton(Id('delete_gpo'), 'Yes'),
+ PushButton(Id('cancel_delete_gpo'), 'No'),
+ ))
+ ))
+
+ def __request_delete_link(self):
+ return MinWidth(30, VBox(
+ Label('Do you want to delete this link?\nThis will not delete the GPO itself.'),
+ Right(HBox(
+ PushButton(Id('delete_link'), 'OK'),
+ PushButton(Id('cancel_delete_link'), 'Cancel'),
+ ))
+ ))
+
def __help(self):
return 'Group Policy Management Console'
- def __scope_page(self):
- return RichText('Contents of the scope page')
+ def __scope_page(self, gpo_guid):
+ header = Header('Location', 'Enforced', 'Link Enabled', 'Path')
+ contents = []
+ links = self.q.get_gpo_containers(gpo_guid)
+ for link in links:
+ if b'domain' in link['objectClass']:
+ name = self.realm.lower()
+ else:
+ name = link['name'][-1]
+ gplist = parse_gplink(link['gPLink'][-1])[gpo_guid]
+ vals = Item(name, str(gplist['enforced']), str(gplist['enabled']), dn_to_path(self.realm.lower(), link['distinguishedName'][-1]))
+ contents.append(vals)
+ return VBox(
+ Left(Label('Links')),
+ Table(Id('scope_links'), header, contents)
+ )
def __ms_time_to_readable(self, timestamp):
+ if type(timestamp) is bytes:
+ timestamp = timestamp.decode()
m = re.match('(?P<year>\d\d\d\d)(?P<month>\d\d)(?P<day>\d\d)(?P<hour>\d\d)(?P<minute>\d\d)(?P<second>\d\d)\..*', timestamp)
if m:
return '%s/%s/%s %s:%s:%s UTC' % (m.group('month'), m.group('day'), m.group('year'), m.group('hour'), m.group('minute'), m.group('second'))
@@ -375,6 +577,16 @@
status_selection[0] = True
combo_options = [Item('All settings disabled', status_selection[0]), Item('Computer configuration settings disabled', status_selection[1]), Item('Enabled', status_selection[2]), Item('User configuration settings disabled', status_selection[3])]
+
+ msg = self.q.gpo_list(selected_gpo[1]['displayName'][-1], attrs=['nTSecurityDescriptor'])
+ if msg:
+ ds_sd_ndr = msg[0][1]['nTSecurityDescriptor'][0]
+ ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr)
+ owner_obj = self.q.user_from_sid(ds_sd.owner_sid)
+ owner = owner_obj['sAMAccountName'][-1].decode('utf-8')
+ else:
+ owner = 'Unknown'
+
return Top(
HBox(
HWeight(1, VBox(
@@ -389,7 +601,7 @@
)),
HWeight(2, VBox(
Left(Label(self.realm)), VSpacing(),
- Left(Label('Unknown')), VSpacing(),
+ Left(Label(owner)), VSpacing(),
Left(Label(self.__ms_time_to_readable(selected_gpo[1]['whenCreated'][-1]))), VSpacing(),
Left(Label(self.__ms_time_to_readable(selected_gpo[1]['whenChanged'][-1]))), VSpacing(),
Left(Label('%d' % (int(selected_gpo[1]['versionNumber'][-1]) >> 16))), VSpacing(),
@@ -401,30 +613,74 @@
)
def __settings_page(self):
- return RichText('Contents of the settings page')
+ return Top(HBox(Empty()))
def __delegation_page(self):
- return RichText('Contents of the delegation page')
+ return Top(HBox(Empty()))
def __forest(self):
+ gp_containers = self.q.get_containers_with_gpos()
items = []
for gpo in self.gpos:
items.append(Item(Id(gpo[1]['name'][-1]), gpo[1]['displayName'][-1]))
- forest = [Item('Domains', True, [Item(self.realm, True, items)])]
- contents = Tree(Id('gpmc_tree'), Opt('notify'), 'Group Policy Management', forest)
+ folders = []
+ for container in gp_containers:
+ if b'domain' in container['objectClass']:
+ gplists = parse_gplink(container['gPLink'][-1])
+ for gpname in gplists:
+ gpo = self.__find_gpo(gpname)
+ displayName = gpo[1]['displayName'][-1] if gpo else gpname
+ folders.append(Item(Id(gpname), displayName))
+ else:
+ container_objs = []
+ if 'gPLink' in container:
+ gplists = parse_gplink(container['gPLink'][-1])
+ else:
+ gplists = []
+ for gpname in gplists:
+ gpo = self.__find_gpo(gpname)
+ displayName = gpo[1]['displayName'][-1] if gpo else gpname
+ container_objs.append(Item(Id(gpname), displayName))
+ folders.append(Item(Id(container['distinguishedName'][-1]), container['name'][-1], False, container_objs))
+ folders.append(Item('Group Policy Objects', False, items))
+ forest = [
+ Item('Domains', True,
+ [
+ Item(Id(self.realm_dn), self.realm, True, folders)
+ ])
+ ]
+ contents = Tree(Id('gpmc_tree'), Opt('notify', 'immediate', 'notifyContextMenu'), 'Group Policy Management', forest)
return contents
- def __realm(self):
+ def __container(self, dn):
+ global have_advanced_gui
+ if have_advanced_gui:
+ buttons = Empty()
+ else:
+ buttons = Right(HBox(
+ PushButton(Id('del_gpo'), 'Delete GPO'),
+ PushButton(Id('add_gpo'), 'Create a GPO')
+ ))
return VBox(
- Frame(self.realm, DumbTab(['Linked Group Policy Objects', 'Group Policy Inheritance', 'Delegation'], ReplacePoint(Id('realm_tabContainer'), self.__realm_links()))),
- Right(HBox(PushButton(Id('add_gpo'), 'Create a GPO'))),
+ Frame(self.realm, DumbTab([
+ 'Linked Group Policy Objects',
+ #'Group Policy Inheritance',
+ #'Delegation'
+ ], ReplacePoint(Id('realm_tabContainer'), self.__container_links(dn)))),
+ buttons,
)
- def __realm_links(self):
+ def __realm_delegation(self):
+ return Top(HBox(Empty()))
+
+ def __realm_inheritance(self):
+ return Top(HBox(Empty()))
+
+ def __container_links(self, dn):
header = Header('Link Order', 'GPO', 'Enforced', 'Link Enabled', 'GPO Status', 'WMI Filter', 'Modified', 'Domain')
contents = []
- for gpo in self.gpos:
+ for gpo in self.q.get_gpos_for_container(dn):
status = ''
if gpo[1]['flags'][-1] == '0':
status = 'Enabled'
@@ -441,11 +697,15 @@
def __gpo_tab(self, gpo_guid):
global selected_gpo
+ if not selected_gpo:
+ return Top(HBox(Empty()))
gpo_name = selected_gpo[1]['displayName'][-1]
- return Frame(gpo_name, ReplacePoint(Id('gpo_tabContainer'), VBox(self.__details_page(gpo_guid), Right(PushButton(Id('advanced'), 'Advanced')))))
-
- def __gpo_tab_adv(self, gpo_guid):
- UI.ReplaceWidget('gpo_tabContainer', DumbTab(Id('gpo_tab'), ['Scope', 'Details', 'Settings', 'Delegation'], ReplacePoint(Id('gpo_tabContents'), self.__scope_page())))
+ return Frame(gpo_name, DumbTab(Id('gpo_tab'), [
+ 'Scope',
+ Item('Details', True),
+ #'Settings',
+ #'Delegation'
+ ], ReplacePoint(Id('gpo_tabContents'), self.__details_page(gpo_guid))))
def __gpmc_page(self):
return HBox(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/src/gpmc.desktop.in new/yast-gpmc-1.3.1/src/gpmc.desktop.in
--- old/yast-gpmc-1.0/src/gpmc.desktop.in 2017-11-17 16:16:59.000000000 +0100
+++ new/yast-gpmc-1.3.1/src/gpmc.desktop.in 2018-05-29 20:44:25.000000000 +0200
@@ -16,7 +16,7 @@
X-SuSE-YaST-AutoInstResource=gpmc
Icon=yast-gpmc
-Exec="/usr/bin/xdg-su -c '@CLIENTDIR@/gpmc.py'"
+Exec=yast2 '@CLIENTDIR@/gpmc.py'
Name=Group Policy Management Console
GenericName=Manage Group Policy
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/src/gpmc.py new/yast-gpmc-1.3.1/src/gpmc.py
--- old/yast-gpmc-1.0/src/gpmc.py 2017-11-17 16:11:18.000000000 +0100
+++ new/yast-gpmc-1.3.1/src/gpmc.py 1970-01-01 01:00:00.000000000 +0100
@@ -1,56 +0,0 @@
-import sys, os, traceback
-import optparse
-
-from samba.param import LoadParm
-from samba.credentials import Credentials
-
-from subprocess import Popen, PIPE
-
-sys.path.append(sys.path[0]+"/../include/gpmc")
-
-from wizards import GPMCSequence
-
-if __name__ == "__main__":
- parser = optparse.OptionParser('gpmc [options]')
-
- # Yast command line args
- yast_opt = optparse.OptionGroup(parser, 'Command line options for the YaST2 Qt UI')
- yast_opt.add_option('--nothreads', help='run without additional UI threads', action='store_true')
- yast_opt.add_option('--fullscreen', help='use full screen for `opt(`defaultsize) dialogs', action='store_true')
- yast_opt.add_option('--noborder', help='no window manager border for `opt(`defaultsize) dialogs', action='store_true')
- yast_opt.add_option('--auto-fonts', help='automatically pick fonts, disregard Qt standard settings', action='store_true')
- yast_opt.add_option('--macro', help='play a macro right on startup')
- parser.add_option_group(yast_opt)
-
- # Get the command line options
- parser.add_option('--ncurses', dest='ncurses', help='Whether to run yast via ncurses interface', action='store_true')
- credopts = optparse.OptionGroup(parser, 'Credentials Options')
- credopts.add_option('--password', dest='password', help='Password')
- credopts.add_option('-U', '--username', dest='username', help='Username')
- credopts.add_option('--krb5-ccache', dest='krb5_ccache', help='Kerberos Credentials cache')
- parser.add_option_group(credopts)
-
- # Set the options and the arguments
- (opts, args) = parser.parse_args()
-
- # Set the loadparm context
- lp = LoadParm()
- if os.getenv("SMB_CONF_PATH") is not None:
- lp.load(os.getenv("SMB_CONF_PATH"))
- else:
- lp.load_default()
-
- # Initialize the session
- creds = Credentials()
- if opts.username and opts.password:
- creds.set_username(opts.username)
- creds.set_password(opts.password)
- elif opts.krb5_ccache:
- creds.set_named_ccache(opts.krb5_ccache)
- creds.guess(lp)
-
- try:
- GPMCSequence(lp, creds)
- except:
- traceback.print_exc(file=sys.stdout)
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/src/gpmc.py.in new/yast-gpmc-1.3.1/src/gpmc.py.in
--- old/yast-gpmc-1.0/src/gpmc.py.in 1970-01-01 01:00:00.000000000 +0100
+++ new/yast-gpmc-1.3.1/src/gpmc.py.in 2018-05-29 20:44:25.000000000 +0200
@@ -0,0 +1,60 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
+import sys, os, traceback
+import optparse
+
+from samba.param import LoadParm
+from samba.credentials import Credentials
+
+from subprocess import Popen, PIPE
+
+sys.path.append('@INCLUDEDIR@')
+
+from wizards import GPMCSequence
+
+if __name__ == "__main__":
+ parser = optparse.OptionParser('gpmc [options]')
+
+ # Yast command line args
+ yast_opt = optparse.OptionGroup(parser, 'Command line options for the YaST2 Qt UI')
+ yast_opt.add_option('--nothreads', help='run without additional UI threads', action='store_true')
+ yast_opt.add_option('--fullscreen', help='use full screen for `opt(`defaultsize) dialogs', action='store_true')
+ yast_opt.add_option('--noborder', help='no window manager border for `opt(`defaultsize) dialogs', action='store_true')
+ yast_opt.add_option('--auto-fonts', help='automatically pick fonts, disregard Qt standard settings', action='store_true')
+ yast_opt.add_option('--macro', help='play a macro right on startup')
+ parser.add_option_group(yast_opt)
+
+ # Get the command line options
+ parser.add_option('--ncurses', dest='ncurses', help='Whether to run yast via ncurses interface', action='store_true')
+ credopts = optparse.OptionGroup(parser, 'Credentials Options')
+ credopts.add_option('--password', dest='password', help='Password')
+ credopts.add_option('-U', '--username', dest='username', help='Username')
+ credopts.add_option('--krb5-ccache', dest='krb5_ccache', help='Kerberos Credentials cache')
+ parser.add_option_group(credopts)
+
+ # Set the options and the arguments
+ (opts, args) = parser.parse_args()
+
+ # Set the loadparm context
+ lp = LoadParm()
+ if os.getenv("SMB_CONF_PATH") is not None:
+ lp.load(os.getenv("SMB_CONF_PATH"))
+ else:
+ try:
+ lp.load_default()
+ except RuntimeError:
+ pass
+
+ # Initialize the session
+ creds = Credentials()
+ if opts.username and opts.password:
+ creds.set_username(opts.username)
+ creds.set_password(opts.password)
+ elif opts.krb5_ccache:
+ creds.set_named_ccache(opts.krb5_ccache)
+ creds.guess(lp)
+
+ try:
+ GPMCSequence(lp, creds)
+ except:
+ traceback.print_exc(file=sys.stdout)
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/src/wizards.py new/yast-gpmc-1.3.1/src/wizards.py
--- old/yast-gpmc-1.0/src/wizards.py 2017-11-17 16:11:27.000000000 +0100
+++ new/yast-gpmc-1.3.1/src/wizards.py 2018-05-29 20:44:25.000000000 +0200
@@ -1,4 +1,9 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
from dialogs import GPMC, GPME
+from yast import import_module
+import_module('Wizard')
+import_module('UI')
+import_module('Sequencer')
from yast import Wizard, UI, Sequencer, Code, Symbol
def GPMCSequence(lp, creds):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast-gpmc-1.0/yast2-gpmc.spec.in new/yast-gpmc-1.3.1/yast2-gpmc.spec.in
--- old/yast-gpmc-1.0/yast2-gpmc.spec.in 2017-11-08 19:20:04.000000000 +0100
+++ new/yast-gpmc-1.3.1/yast2-gpmc.spec.in 1970-01-01 01:00:00.000000000 +0100
@@ -1,31 +0,0 @@
-@HEADER-COMMENT@
-
-@HEADER@
-Requires: yast2
-BuildRequires: perl-XML-Writer update-desktop-files yast2 yast2-devtools yast2-testsuite
-
-BuildArchitectures: noarch
-
-Summary: Configuration of gpmc
-
-%description
--
-
-@PREP@
-
-@BUILD@
-
-@INSTALL@
-
-@CLEAN@
-
-%files
-%defattr(-,root,root)
-%dir @yncludedir@/gpmc
-@yncludedir@/gpmc/*
-@clientdir@/gpmc.ycp
-@clientdir@/gpmc_*.ycp
-@moduledir@/Gpmc.*
-@moduledir@/Gpmc2.*
-@desktopdir@/gpmc.desktop
-%doc @docdir@