commit yast-gpmc for openSUSE:Factory
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('<WKGUID=%s,%s>' % (wkguiduc, self.realm_to_dn(self.realm)), ldap.SCOPE_SUBTREE, '(objectClass=container)', ['distinguishedName']) + result = ldap_search(self.l, '<WKGUID=%s,%s>' % (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@
participants (1)
-
root