1
0
mirror of https://github.com/altlinux/gpupdate.git synced 2025-08-25 05:49:25 +03:00

Compare commits

..

2 Commits

78 changed files with 860 additions and 3998 deletions

1
.gitignore vendored
View File

@ -2,5 +2,4 @@ __pycache__
*~
_opam
_build
*.pyc

228
dist/gpupdate-setup vendored Executable file
View File

@ -0,0 +1,228 @@
#! /usr/bin/env python3
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import argparse
import subprocess
import re
from gpoa.util.samba import smbopts
def command(args):
try:
subprocess.check_call(args.split())
except:
print ('command: \'%s\' error' % args)
def from_command(args):
try:
with subprocess.Popen(args.split(), stdout=subprocess.PIPE) as proc:
value = proc.stdout.readline().decode('utf-8')
proc.wait()
except:
print ('from_command: \'%s\' error' % args)
return 'local'
return value.strip()
def get_default_policy_name():
localpolicy = 'workstation'
dcpolicy = 'ad-domain-controller'
try:
if smbopt.get_server_role() == 'active directory domain controller':
return dcpolicy
except:
pass
try:
release = '/etc/altlinux-release'
if os.path.isfile(release):
f = open(release)
s = f.readline()
if re.search('server', s, re.I):
localpolicy = 'server'
except:
pass
return localpolicy
def parse_arguments():
'''
Parse CLI arguments.
'''
parser = argparse.ArgumentParser(prog='gpupdate-setup')
subparsers = parser.add_subparsers(dest='action',
metavar='action',
help='Group Policy management actions (default action is status)')
parser_list = subparsers.add_parser('list',
help='List avalable types of local policy')
parser_status = subparsers.add_parser('status',
help='Show current Group Policy status')
parser_enable = subparsers.add_parser('enable',
help='Enable Group Policy subsystem')
parser_disable = subparsers.add_parser('disable',
help='Disable Group Policy subsystem')
parser_write = subparsers.add_parser('write',
help='Operate on Group Policies (enable or disable)')
parser_active = subparsers.add_parser('active-policy',
help='Show name of policy enabled')
parser_write.add_argument('status',
choices=['enable', 'disable'],
help='Enable or disable Group Policies')
parser_write.add_argument('localpolicy',
default=None,
nargs='?',
help='Name of local policy to enable')
parser_enable.add_argument('localpolicy',
default=None,
nargs='?',
help='Name of local policy to enable')
return parser.parse_args()
def get_policy_entries(directory):
filtered_entries = list()
if os.path.isdir(directory):
entries = [os.path.join(directory, entry) for entry in os.listdir(directory)]
for entry in entries:
if os.path.isdir(os.path.join(entry)):
if not os.path.islink(os.path.join(entry)):
if not entry.rpartition('/')[2] == 'default':
filtered_entries.append(entry)
return filtered_entries
def get_policy_variants():
'''
Get the list of local policy variants deployed on this system.
Please note that is case overlapping names the names in
/etc/local-policy must override names in /usr/share/local-policy
'''
policy_dir = '/usr/share/local-policy'
etc_policy_dir = '/etc/local-policy'
system_policies = get_policy_entries(policy_dir)
user_policies = get_policy_entries(etc_policy_dir)
general_listing = list()
general_listing.extend(system_policies)
general_listing.extend(user_policies)
return general_listing
def validate_policy_name(policy_name):
return policy_name in [os.path.basename(d) for d in get_policy_variants()]
def get_status():
systemd_unit_link = '/etc/systemd/system/multi-user.target.wants/gpupdate.service'
return os.path.islink(systemd_unit_link)
def get_active_policy():
policy_dir = '/usr/share/local-policy'
etc_policy_dir = '/etc/local-policy'
default_policy_name = os.path.join(policy_dir, get_default_policy_name())
active_policy_name = os.path.join(etc_policy_dir, 'active')
actual_policy_name = os.path.realpath(default_policy_name)
if os.path.isdir(active_policy_name):
actual_policy_name = os.path.realpath(active_policy_name)
return actual_policy_name
def disable_gp():
if from_command('/usr/sbin/control system-auth') != 'local':
command('/usr/sbin/control system-policy global')
else:
command('/usr/sbin/control system-policy local')
command('systemctl disable gpupdate.service')
command('systemctl --global disable gpupdate-user.service')
def enable_gp(policy_name):
policy_dir = '/usr/share/local-policy'
etc_policy_dir = '/etc/local-policy'
target_policy_name = get_default_policy_name()
if policy_name:
if validate_policy_name(policy_name):
target_policy_name = policy_name
print (target_policy_name)
default_policy_name = os.path.join(policy_dir, target_policy_name)
active_policy_name = os.path.join(etc_policy_dir, 'active')
if not os.path.isdir(etc_policy_dir):
os.makedirs(etc_policy_dir)
if not os.path.islink(active_policy_name):
os.symlink(default_policy_name, active_policy_name)
else:
os.unlink(active_policy_name)
os.symlink(default_policy_name, active_policy_name)
# Enable oddjobd_gpupdate in PAM config
command('/usr/sbin/control system-policy gpupdate')
# Bootstrap the Group Policy engine
command('/usr/sbin/gpoa --nodomain --loglevel 5')
# Enable gpupdate-setup.service for all users
command('systemctl --global enable gpupdate-user.service')
def main():
arguments = parse_arguments()
if arguments.action == 'list':
for entry in get_policy_variants():
print(entry.rpartition('/')[2])
if arguments.action == 'status' or arguments.action == None:
if get_status():
print('enabled')
else:
print('disabled')
if arguments.action == 'write':
if arguments.status == 'enable' or arguments.status == '#t':
enable_gp(arguments.localpolicy)
if arguments.status == 'disable' or arguments.status == '#f':
disable_gp()
if arguments.action == "enable":
enable_gp(arguments.localpolicy)
if arguments.action == "disable":
disable_gp()
if arguments.action == 'active-policy':
print(get_active_policy())
if __name__ == '__main__':
main()

4
dist/gpupdate.ini vendored
View File

@ -1,4 +0,0 @@
[gpoa]
backend = samba
local-policy = auto

View File

@ -1,4 +1,5 @@
#%PAM-1.0
session required pam_mktemp.so
session required pam_mkhomedir.so silent
session required pam_limits.so
-session required pam_oddjob_gpupdate.so
session optional pam_env.so user_readenv=1 conffile=/etc/gpupdate/environment user_envfile=.gpupdate_environment

View File

@ -16,12 +16,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from util.windows import smbcreds
from .samba_backend import samba_backend
from .nodomain_backend import nodomain_backend
from util.logging import log
from util.config import GPConfig
def backend_factory(dc, username, is_machine, no_domain = False):
'''
@ -31,31 +30,23 @@ def backend_factory(dc, username, is_machine, no_domain = False):
policies enforced by domain administrators.
'''
back = None
config = GPConfig()
if config.get_backend() == 'samba' and not no_domain:
if not dc:
dc = config.get_dc()
if dc:
ld = dict({'dc': dc})
log('D52', ld)
domain = None
if not no_domain:
sc = smbcreds(dc)
domain = sc.get_domain()
ldata = dict({'domain': domain})
log('D9', ldata)
if domain:
logging.debug('Initialize Samba backend for domain: {}'.format(domain))
try:
back = samba_backend(sc, username, domain, is_machine)
except Exception as exc:
logdata = dict({'error': str(exc)})
log('E7', logdata)
if config.get_backend() == 'local' or no_domain:
log('D8')
logging.error('Unable to initialize Samba backend: {}'.format(exc))
else:
logging.debug('Initialize local backend with no domain')
try:
back = nodomain_backend()
except Exception as exc:
logdata = dict({'error': str(exc)})
log('E8', logdata)
logging.error('Unable to initialize no-domain backend: {}'.format(exc))
return back

View File

@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
# Facility to determine GPTs for user
from samba.gpclass import check_safe_path, check_refresh_gpo_list
@ -27,19 +28,13 @@ from util.util import (
get_machine_name,
is_machine_name
)
from util.kerberos import (
machine_kinit
, machine_kdestroy
)
from util.windows import get_sid
import util.preg
from util.logging import log
from util.logging import slogm
class samba_backend(applier_backend):
def __init__(self, sambacreds, username, domain, is_machine):
self.cache_path = '/var/cache/gpupdate/creds/krb5cc_{}'.format(os.getpid())
self.__kinit_successful = machine_kinit(self.cache_path)
self.storage = registry_factory('registry')
self.storage.set_info('domain', domain)
machine_name = get_machine_name()
@ -62,51 +57,26 @@ class samba_backend(applier_backend):
self.sambacreds = sambacreds
self.cache_dir = self.sambacreds.get_cache_dir()
logdata = dict({'cachedir': self.cache_dir})
log('D7', logdata)
def __del__(self):
if self.__kinit_successful:
machine_kdestroy()
logging.debug(slogm('Cache directory is: {}'.format(self.cache_dir)))
def retrieve_and_store(self):
'''
Retrieve settings and strore it in a database
'''
# Get policies for machine at first.
machine_gpts = list()
try:
machine_gpts = self._get_gpts(get_machine_name(), self.storage.get_info('machine_sid'))
except Exception as exc:
log('F2')
raise exc
machine_gpts = self._get_gpts(get_machine_name(), self.storage.get_info('machine_sid'))
self.storage.wipe_hklm()
self.storage.wipe_user(self.storage.get_info('machine_sid'))
for gptobj in machine_gpts:
try:
gptobj.merge()
except Exception as exc:
logdata = dict()
logdata['msg'] = str(exc)
log('E26', logdata)
gptobj.merge()
# Load user GPT values in case user's name specified
# This is a buggy implementation and should be tested more
if not self._is_machine_username:
user_gpts = list()
try:
user_gpts = self._get_gpts(self.username, self.sid)
except Exception as exc:
log('F3')
raise exc
user_gpts = self._get_gpts(self.username, self.sid)
self.storage.wipe_user(self.sid)
for gptobj in user_gpts:
try:
gptobj.merge()
except Exception as exc:
logdata = dict()
logdata['msg'] = str(exc)
log('E27', logdata)
gptobj.merge()
def _check_sysvol_present(self, gpo):
'''
@ -116,23 +86,19 @@ class samba_backend(applier_backend):
# GPO named "Local Policy" has no entry by its nature so
# no reason to print warning.
if 'Local Policy' != gpo.name:
logdata = dict({'gponame': gpo.name})
log('W4', logdata)
logging.warning(slogm('No SYSVOL entry assigned to GPO {}'.format(gpo.name)))
return False
return True
def _get_gpts(self, username, sid):
gpts = list()
log('D45')
# util.windows.smbcreds
gpos = self.sambacreds.update_gpos(username)
log('D46')
for gpo in gpos:
if self._check_sysvol_present(gpo):
logging.debug(slogm('Found SYSVOL entry "{}" for GPO "{}"'.format(gpo.file_sys_path, gpo.display_name)))
path = check_safe_path(gpo.file_sys_path).upper()
slogdata = dict({'sysvol_path': gpo.file_sys_path, 'gpo_name': gpo.display_name, 'gpo_path': path})
log('D30', slogdata)
logging.debug(slogm('Path: {}'.format(path)))
gpt_abspath = os.path.join(self.cache_dir, 'gpo_cache', path)
obj = gpt(gpt_abspath, sid)
obj.set_name(gpo.display_name)

View File

@ -18,51 +18,6 @@
from abc import ABC
import logging
from util.logging import slogm
def check_experimental_enabled(storage):
experimental_enable_flag = 'Software\\BaseALT\\Policies\\GPUpdate\\GlobalExperimental'
flag = storage.get_hklm_entry(experimental_enable_flag)
result = False
if flag and '1' == flag.data:
result = True
return result
def check_module_enabled(storage, module_name):
gpupdate_module_enable_branch = 'Software\\BaseALT\\Policies\\GPUpdate'
gpupdate_module_flag = '{}\\{}'.format(gpupdate_module_enable_branch, module_name)
flag = storage.get_hklm_entry(gpupdate_module_flag)
result = None
if flag:
if '1' == flag.data:
result = True
if '0' == flag.data:
result = False
return result
def check_enabled(storage, module_name, is_experimental):
module_enabled = check_module_enabled(storage, module_name)
exp_enabled = check_experimental_enabled(storage)
result = False
if None == module_enabled:
if is_experimental and exp_enabled:
result = True
if not is_experimental:
result = True
else:
result = module_enabled
return result
class applier_frontend(ABC):
@classmethod
def __init__(self, regobj):

View File

@ -21,27 +21,11 @@ import threading
import logging
from util.logging import slogm
def control_subst(preg_name):
'''
This is a workaround for control names which can't be used in
PReg/ADMX files.
'''
control_triggers = dict()
control_triggers['dvd_rw-format'] = 'dvd+rw-format'
control_triggers['dvd_rw-mediainfo'] = 'dvd+rw-mediainfo'
control_triggers['dvd_rw-booktype'] = 'dvd+rw-booktype'
result = preg_name
if preg_name in control_triggers:
result = control_triggers[preg_name]
return result
class control:
def __init__(self, name, value):
if type(value) != int and type(value) != str:
raise Exception('Unknown type of value for control')
self.control_name = control_subst(name)
self.control_name = name
self.control_value = value
self.possible_values = self._query_control_values()
if self.possible_values == None:

View File

@ -1,97 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
import subprocess
def getprops(param_list):
props = dict()
for entry in param_list:
lentry = entry.lower()
if lentry.startswith('action'):
props['action'] = lentry.rpartition('=')[2]
if lentry.startswith('protocol'):
props['protocol'] = lentry.rpartition('=')[2]
if lentry.startswith('dir'):
props['dir'] = lentry.rpartition('=')[2]
return props
def get_ports(param_list):
portlist = list()
for entry in param_list:
lentry = entry.lower()
if lentry.startswith('lport'):
port = lentry.rpartition('=')[2]
portlist.append(port)
return portlist
class PortState(Enum):
OPEN = 'Allow'
CLOSE = 'Deny'
class Protocol(Enum):
TCP = 'tcp'
UDP = 'udp'
class FirewallMode(Enum):
ROUTER = 'router'
GATEWAY = 'gateway'
HOST = 'host'
# This shi^Wthing named alterator-net-iptables is unable to work in
# multi-threaded environment
class FirewallRule:
__alterator_command = '/usr/bin/alterator-net-iptables'
def __init__(self, data):
data_array = data.split('|')
self.version = data_array[0]
self.ports = get_ports(data_array[1:])
self.properties = getprops(data_array[1:])
def apply(self):
tcp_command = []
udp_command = []
for port in self.ports:
tcp_port = '{}'.format(port)
udp_port = '{}'.format(port)
if PortState.OPEN.value == self.properties['action']:
tcp_port = '+' + tcp_port
udp_port = '+' + udp_port
if PortState.CLOSE.value == self.properties['action']:
tcp_port = '-' + tcp_port
udp_port = '-' + udp_port
portcmd = [
self.__alterator_command
, 'write'
, '-m', FirewallMode.HOST.value
, '-t', tcp_port
, '-u', udp_port
]
proc = subprocess.Popen(portcmd)
proc.wait()

View File

@ -1,75 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from pathlib import Path
from gpt.folders import (
FileAction
, action_letter2enum
)
from util.windows import expand_windows_var
def remove_dir_tree(path, delete_files=False, delete_folder=False, delete_sub_folders=False):
for entry in path.iterdir():
if entry.is_file():
entry.unlink()
if entry.is_dir():
if delete_sub_folders:
remove_dir_tree(entry,
delete_files,
delete_folder,
delete_sub_folders)
if delete_folder:
path.rmdir()
def str2bool(boolstr):
if boolstr.lower in ['true', 'yes', '1']:
return True
return False
class Folder:
def __init__(self, folder_object, username):
self.folder_path = Path(expand_windows_var(folder_object.path, username).replace('\\', '/'))
self.action = action_letter2enum(folder_object.action)
self.delete_files = str2bool(folder_object.delete_files)
self.delete_folder = str2bool(folder_object.delete_folder)
self.delete_sub_folders = str2bool(folder_object.delete_sub_folders)
def _create_action(self):
self.folder_path.mkdir(parents=True, exist_ok=True)
def _delete_action(self):
remove_dir_tree(self.folder_path,
self.delete_files,
self.delete_folders,
self.delete_sub_folders)
def act(self):
if self.action == FileAction.CREATE:
self._create_action()
if self.action == FileAction.UPDATE:
self._create_action()
if self.action == FileAction.DELETE:
self._delete_action()
if self.action == FileAction.REPLACE:
self._delete_action()
self._create_action()

View File

@ -46,39 +46,19 @@ class system_gsetting:
with open(self.file_path, 'w') as f:
config.write(f)
def glib_map(value, glib_type):
result_value = value
if glib_type == 'i':
result_value = GLib.Variant(glib_type, int(value))
else:
result_value = GLib.Variant(glib_type, value)
return result_value
class user_gsetting:
def __init__(self, schema, path, value, helper_function=None):
logging.debug('Creating GSettings element {} (in {}) with value {}'.format(path, schema, value))
def __init__(self, schema, path, value):
self.schema = schema
self.path = path
self.value = value
self.helper_function = helper_function
def apply(self):
logging.debug('Setting GSettings key {} (in {}) to {}'.format(self.path, self.schema, self.value))
if self.helper_function:
self.helper_function(self.schema, self.path, self.value)
# Access the current schema
settings = Gio.Settings(self.schema)
# Get the key to modify
key = settings.get_value(self.path)
# Query the data type for the key
glib_value_type = key.get_type_string()
# Build the new value with the determined type
val = glib_map(self.value, glib_value_type)
# Set the value
settings.set_value(self.path, val)
source = Gio.SettingsSchemaSource.get_default()
schema = source.lookup(self.schema, True)
key = schema.get_key(self.path)
gvformat = key.get_value_type()
val = GLib.Variant(gvformat.dup_string(), self.value)
schema.set_value(self.path, val)
#gso = Gio.Settings.new(self.schema)
#variants = gso.get_property(self.path)
#if (variants.has_key(self.path)):

View File

@ -28,15 +28,11 @@ class polkit:
__template_loader = jinja2.FileSystemLoader(searchpath=__template_path)
__template_environment = jinja2.Environment(loader=__template_loader)
def __init__(self, template_name, arglist, username=None):
def __init__(self, template_name, arglist):
self.template_name = template_name
self.args = arglist
self.username = username
self.infilename = '{}.rules.j2'.format(self.template_name)
if self.username:
self.outfile = os.path.join(self.__policy_dir, '{}.{}.rules'.format(self.template_name, self.username))
else:
self.outfile = os.path.join(self.__policy_dir, '{}.rules'.format(self.template_name))
self.outfile = os.path.join(self.__policy_dir, '{}.rules'.format(self.template_name))
def generate(self):
try:

View File

@ -16,10 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from util.rpm import (
install_rpm,
remove_rpm
)
from .applier_backend import applier_backend
class rpm:
def __init__(self, name, action):
self.name = name
self.action = action
class freeipa_backend(applier_backend):
def __init__(self):
def apply(self):
pass

View File

@ -1,23 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
class WallpaperStretchMode(Enum):
STRETCH = 2

View File

@ -16,10 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .applier_frontend import applier_frontend
import logging
import json
@ -29,9 +26,6 @@ from util.logging import slogm
from util.util import is_machine_name
class chromium_applier(applier_frontend):
__module_name = 'ChromiumApplier'
__module_enabled = False
__module_experimental = True
__registry_branch = 'Software\\Policies\\Google\\Chrome'
__managed_policies_path = '/etc/chromium/policies/managed'
__recommended_policies_path = '/etc/chromium/policies/recommended'
@ -45,11 +39,6 @@ class chromium_applier(applier_frontend):
self.username = username
self._is_machine_name = is_machine_name(self.username)
self.policies = dict()
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def get_hklm_string_entry(self, hive_subkey):
query_str = '{}\\{}'.format(self.__registry_branch, hive_subkey)
@ -142,12 +131,7 @@ class chromium_applier(applier_frontend):
'''
All actual job done here.
'''
if self.__module_enabled:
logging.debug(slogm('Running Chromium applier for machine'))
self.machine_apply()
else:
logging.debug(slogm('Chromium applier for machine will not be started'))
self.machine_apply()
#if not self._is_machine_name:
# logging.debug('Running user applier for Chromium')
# self.user_apply()

View File

@ -1,164 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import fileinput
import jinja2
import os
import subprocess
import logging
from pathlib import Path
from .applier_frontend import (
applier_frontend
, check_enabled
)
from gpt.drives import json2drive
from util.util import get_homedir
from util.logging import slogm
def storage_get_drives(storage, sid):
drives = storage.get_drives(sid)
drive_list = list()
for drv_obj in drives:
drive_list.append(drv_obj)
return drive_list
def add_line_if_missing(filename, ins_line):
with open(filename, 'r+') as f:
for line in f:
if ins_line == line.strip():
break
else:
f.write(ins_line + '\n')
f.flush()
class cifs_applier(applier_frontend):
def __init__(self, storage):
pass
def apply(self):
pass
class cifs_applier_user(applier_frontend):
__module_name = 'CIFSApplierUser'
__module_enabled = False
__module_experimental = True
__auto_file = '/etc/auto.master'
__auto_dir = '/etc/auto.master.gpupdate.d'
__template_path = '/usr/share/gpupdate/templates'
__template_mountpoints = 'autofs_mountpoints.j2'
__template_identity = 'autofs_identity.j2'
__template_auto = 'autofs_auto.j2'
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
self.home = get_homedir(username)
conf_file = '{}.conf'.format(sid)
autofs_file = '{}.autofs'.format(sid)
cred_file = '{}.creds'.format(sid)
self.auto_master_d = Path(self.__auto_dir)
self.user_config = self.auto_master_d / conf_file
if os.path.exists(self.user_config.resolve()):
self.user_config.unlink()
self.user_autofs = self.auto_master_d / autofs_file
if os.path.exists(self.user_autofs.resolve()):
self.user_autofs.unlink()
self.user_creds = self.auto_master_d / cred_file
self.mount_dir = Path(os.path.join(self.home, 'net'))
self.drives = storage_get_drives(self.storage, self.sid)
self.template_loader = jinja2.FileSystemLoader(searchpath=self.__template_path)
self.template_env = jinja2.Environment(loader=self.template_loader)
self.template_mountpoints = self.template_env.get_template(self.__template_mountpoints)
self.template_indentity = self.template_env.get_template(self.__template_identity)
self.template_auto = self.template_env.get_template(self.__template_auto)
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def user_context_apply(self):
'''
Nothing to implement.
'''
pass
def __admin_context_apply(self):
# Create /etc/auto.master.gpupdate.d directory
self.auto_master_d.mkdir(parents=True, exist_ok=True)
# Create user's destination mount directory
self.mount_dir.mkdir(parents=True, exist_ok=True)
# Add pointer to /etc/auto.master.gpiupdate.d in /etc/auto.master
auto_destdir = '+dir:{}'.format(self.__auto_dir)
add_line_if_missing(self.__auto_file, auto_destdir)
# Collect data for drive settings
drive_list = list()
for drv in self.drives:
drive_settings = dict()
drive_settings['dir'] = drv.dir
drive_settings['login'] = drv.login
drive_settings['password'] = drv.password
drive_settings['path'] = drv.path.replace('\\', '/')
drive_list.append(drive_settings)
if len(drive_list) > 0:
mount_settings = dict()
mount_settings['drives'] = drive_list
mount_text = self.template_mountpoints.render(**mount_settings)
with open(self.user_config.resolve(), 'w') as f:
f.truncate()
f.write(mount_text)
f.flush()
autofs_settings = dict()
autofs_settings['home_dir'] = self.home
autofs_settings['mount_file'] = self.user_config.resolve()
autofs_text = self.template_auto.render(**autofs_settings)
with open(self.user_autofs.resolve(), 'w') as f:
f.truncate()
f.write(autofs_text)
f.flush()
subprocess.check_call(['/bin/systemctl', 'restart', 'autofs'])
def admin_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running CIFS applier for user in administrator context'))
self.__admin_context_apply()
else:
logging.debug(slogm('CIFS applier for user in administrator context will not be started'))

View File

@ -16,32 +16,24 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .applier_frontend import applier_frontend
from .appliers.control import control
from util.logging import slogm
import logging
class control_applier(applier_frontend):
__module_name = 'ControlApplier'
__module_experimental = False
__module_enabled = True
_registry_branch = 'Software\\BaseALT\\Policies\\Control'
def __init__(self, storage):
self.storage = storage
self.control_settings = self.storage.filter_hklm_entries('Software\\BaseALT\\Policies\\Control%')
self.controls = list()
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
def apply(self):
'''
Trigger control facility invocation.
'''
for setting in self.control_settings:
valuename = setting.hive_key.rpartition('\\')[2]
try:
@ -57,13 +49,3 @@ class control_applier(applier_frontend):
for cont in self.controls:
cont.set_control_status()
def apply(self):
'''
Trigger control facility invocation.
'''
if self.__module_enabled:
logging.debug(slogm('Running Control applier for machine'))
self.run()
else:
logging.debug(slogm('Control applier for machine will not be started'))

View File

@ -18,14 +18,8 @@
import logging
import os
import json
import cups
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .applier_frontend import applier_frontend
from gpt.printers import json2printer
from util.rpm import is_rpm_installed
from util.logging import slogm
@ -38,81 +32,42 @@ def storage_get_printers(storage, sid):
printers = list()
for prnj in printer_objs:
printers.append(prnj)
prn_obj = json2printer(prnj)
printers.append(prn_obj)
return printers
def connect_printer(connection, prn):
def write_printer(prn):
'''
Dump printer cinfiguration to disk as CUPS config
'''
# PPD file location
printer_driver = 'generic'
pjson = json.loads(prn.printer)
printer_parts = pjson['printer']['path'].partition(' ')
# Printer queue name in CUPS
printer_name = printer_parts[2].replace('(', '').replace(')', '')
# Printer description in CUPS
printer_info = printer_name
printer_uri = printer_parts[0].replace('\\', '/')
printer_uri = 'smb:' + printer_uri
connection.addPrinter(
name=printer_name
, info=printer_info
, device=printer_uri
#filename=printer_driver
)
printer_config_path = os.path.join('/etc/cups', prn.name)
with open(printer_config_path, 'r') as f:
print(prn.cups_config(), file=f)
class cups_applier(applier_frontend):
__module_name = 'CUPSApplier'
__module_experimental = True
__module_enabled = False
def __init__(self, storage):
self.storage = storage
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
if not is_rpm_installed('cups'):
logging.warning(slogm('CUPS is not installed: no printer settings will be deployed'))
return
self.cups_connection = cups.Connection()
self.printers = storage_get_printers(self.storage, self.storage.get_info('machine_sid'))
if self.printers:
for prn in self.printers:
connect_printer(self.cups_connection, prn)
def apply(self):
'''
Perform configuration of printer which is assigned to computer.
'''
if self.__module_enabled:
logging.debug(slogm('Running CUPS applier for machine'))
self.run()
else:
logging.debug(slogm('CUPS applier for machine will not be started'))
if not is_rpm_installed('cups'):
logging.warning(slogm('CUPS is not installed: no printer settings will be deployed'))
return
printers = storage_get_printers(self.storage, self.storage.get_info('machine_sid'))
if printers:
for prn in printers:
write_printer(prn)
class cups_applier_user(applier_frontend):
__module_name = 'CUPSApplierUser'
__module_experimental = True
__module_enabled = False
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_enabled
)
def user_context_apply(self):
'''
@ -121,25 +76,17 @@ class cups_applier_user(applier_frontend):
'''
pass
def run(self):
if not is_rpm_installed('cups'):
logging.warning(slogm('CUPS is not installed: no printer settings will be deployed'))
return
self.cups_connection = cups.Connection()
self.printers = storage_get_printers(self.storage, self.sid)
if self.printers:
for prn in self.printers:
connect_printer(self.cups_connection, prn)
def admin_context_apply(self):
'''
Perform printer configuration assigned for user.
'''
if self.__module_enabled:
logging.debug(slogm('Running CUPS applier for user in administrator context'))
self.run()
else:
logging.debug(slogm('CUPS applier for user in administrator context will not be started'))
if not is_rpm_installed('cups'):
logging.warning(slogm('CUPS is not installed: no printer settings will be deployed'))
return
printers = storage_get_printers(self.storage, self.sid)
if printers:
for prn in printers:
write_printer(prn)

View File

@ -30,17 +30,11 @@ import json
import os
import configparser
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .applier_frontend import applier_frontend
from util.logging import slogm
from util.util import is_machine_name
class firefox_applier(applier_frontend):
__module_name = 'FirefoxApplier'
__module_experimental = True
__module_enabled = False
__registry_branch = 'Software\\Policies\\Mozilla\\Firefox'
__firefox_installdir = '/usr/lib64/firefox/distribution'
__user_settings_dir = '.mozilla/firefox'
@ -52,11 +46,6 @@ class firefox_applier(applier_frontend):
self._is_machine_name = is_machine_name(self.username)
self.policies = dict()
self.policies_json = dict({ 'policies': self.policies })
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def get_profiles(self):
'''
@ -148,11 +137,7 @@ class firefox_applier(applier_frontend):
logging.debug(slogm('Found Firefox profile in {}/{}'.format(profiledir, profile)))
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Firefox applier for machine'))
self.machine_apply()
else:
logging.debug(slogm('Firefox applier for machine will not be started'))
self.machine_apply()
#if not self._is_machine_name:
# logging.debug('Running user applier for Firefox')
# self.user_apply()

View File

@ -1,65 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import subprocess
from util.logging import slogm
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .appliers.firewall_rule import FirewallRule
class firewall_applier(applier_frontend):
__module_name = 'FirewallApplier'
__module_experimental = True
__module_enabled = False
__firewall_branch = 'SOFTWARE\\Policies\\Microsoft\\WindowsFirewall\\FirewallRules'
__firewall_switch = 'SOFTWARE\\Policies\\Microsoft\\WindowsFirewall\\DomainProfile\\EnableFirewall'
__firewall_reset_cmd = ['/usr/bin/alterator-net-iptables', 'reset']
def __init__(self, storage):
self.storage = storage
self.firewall_settings = self.storage.filter_hklm_entries('{}%'.format(self.__firewall_branch))
self.firewall_enabled = self.storage.get_hklm_entry(self.__firewall_switch)
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
for setting in self.firewall_settings:
rule = FirewallRule(setting.data)
rule.apply()
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Firewall applier for machine'))
if '1' == self.firewall_enabled:
logging.debug(slogm('Firewall is enabled'))
self.run()
else:
logging.debug(slogm('Firewall is disabled, settings will be reset'))
proc = subprocess.Popen(self.__firewall_reset_cmd)
proc.wait()
else:
logging.debug(slogm('Firewall applier will not be started'))

View File

@ -1,84 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from pathlib import Path
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .appliers.folder import Folder
from util.logging import slogm
import logging
class folder_applier(applier_frontend):
__module_name = 'FoldersApplier'
__module_experimental = False
__module_enabled = True
def __init__(self, storage, sid):
self.storage = storage
self.sid = sid
self.folders = self.storage.get_folders(self.sid)
self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_enabled)
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Folder applier for machine'))
for directory_obj in self.folders:
fld = Folder(directory_obj)
fld.action()
else:
logging.debug(slogm('Folder applier for machine will not be started'))
class folder_applier_user(applier_frontend):
__module_name = 'FoldersApplierUser'
__module_experimental = False
__module_enabled = True
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
self.folders = self.storage.get_folders(self.sid)
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
for directory_obj in self.folders:
fld = Folder(directory_obj, self.username)
fld.act()
def admin_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Folder applier for user in administrator context'))
self.run()
else:
logging.debug(slogm('Folder applier for user in administrator context will not be started'))
def user_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Folder applier for user in user context'))
self.run()
else:
logging.debug(slogm('Folder applier for user administrator context will not be started'))

View File

@ -19,18 +19,12 @@
from storage import registry_factory
from .control_applier import control_applier
from .polkit_applier import (
polkit_applier
, polkit_applier_user
)
from .polkit_applier import polkit_applier
from .systemd_applier import systemd_applier
from .firefox_applier import firefox_applier
from .chromium_applier import chromium_applier
from .cups_applier import cups_applier
from .package_applier import (
package_applier
, package_applier_user
)
from .package_applier import package_applier
from .shortcut_applier import (
shortcut_applier,
shortcut_applier_user
@ -39,13 +33,6 @@ from .gsettings_applier import (
gsettings_applier,
gsettings_applier_user
)
from .firewall_applier import firewall_applier
from .folder_applier import (
folder_applier
, folder_applier_user
)
from .cifs_applier import cifs_applier_user
from .ntp_applier import ntp_applier
from util.windows import get_sid
from util.users import (
is_root,
@ -53,8 +40,14 @@ from util.users import (
username_match_uid,
with_privileges
)
from util.logging import log
from util.logging import slogm
from util.paths import (
frontend_module_dir
)
import logging
import os
import subprocess
def determine_username(username=None):
'''
@ -67,15 +60,13 @@ def determine_username(username=None):
# of process owner.
if not username:
name = get_process_user()
logdata = dict({'username': name})
log('D2', logdata)
logging.debug(slogm('Username is not specified - will use username of current process'))
if not username_match_uid(name):
if not is_root():
raise Exception('Current process UID does not match specified username')
logdata = dict({'username': name})
log('D15', logdata)
logging.debug(slogm('Username for frontend is set to {}'.format(name)))
return name
@ -86,87 +77,75 @@ class frontend_manager:
'''
def __init__(self, username, is_machine):
frontend_module_files = frontend_module_dir().glob('**/*')
self.frontend_module_binaries = list()
for exe in frontend_module_files:
if (exe.is_file() and os.access(exe.resolve(), os.X_OK)):
self.frontend_module_binaries.append(exe)
self.storage = registry_factory('registry')
self.username = determine_username(username)
self.is_machine = is_machine
self.process_uname = get_process_user()
self.sid = get_sid(self.storage.get_info('domain'), self.username, is_machine)
self.machine_appliers = dict()
self.machine_appliers['control'] = control_applier(self.storage)
self.machine_appliers['polkit'] = polkit_applier(self.storage)
self.machine_appliers['systemd'] = systemd_applier(self.storage)
self.machine_appliers['firefox'] = firefox_applier(self.storage, self.sid, self.username)
self.machine_appliers['chromium'] = chromium_applier(self.storage, self.sid, self.username)
self.machine_appliers['shortcuts'] = shortcut_applier(self.storage)
self.machine_appliers['gsettings'] = gsettings_applier(self.storage)
self.machine_appliers['cups'] = cups_applier(self.storage)
self.machine_appliers['firewall'] = firewall_applier(self.storage)
self.machine_appliers['folders'] = folder_applier(self.storage, self.sid)
self.machine_appliers['package'] = package_applier(self.storage)
self.machine_appliers['ntp'] = ntp_applier(self.storage)
self.machine_appliers = dict({
'control': control_applier(self.storage),
'polkit': polkit_applier(self.storage),
'systemd': systemd_applier(self.storage),
'firefox': firefox_applier(self.storage, self.sid, self.username),
'chromium': chromium_applier(self.storage, self.sid, self.username),
'shortcuts': shortcut_applier(self.storage),
'gsettings': gsettings_applier(self.storage),
'cups': cups_applier(self.storage),
'package': package_applier(self.storage)
})
# User appliers are expected to work with user-writable
# files and settings, mostly in $HOME.
self.user_appliers = dict()
self.user_appliers['shortcuts'] = shortcut_applier_user(self.storage, self.sid, self.username)
self.user_appliers['folders'] = folder_applier_user(self.storage, self.sid, self.username)
self.user_appliers['gsettings'] = gsettings_applier_user(self.storage, self.sid, self.username)
try:
self.user_appliers['cifs'] = cifs_applier_user(self.storage, self.sid, self.username)
except Exception as exc:
logdata = dict()
logdata['applier_name'] = 'cifs'
logdata['msg'] = str(exc)
log('E25', logdata)
self.user_appliers['package'] = package_applier_user(self.storage, self.sid, self.username)
self.user_appliers['polkit'] = polkit_applier_user(self.storage, self.sid, self.username)
self.user_appliers = dict({
'shortcuts': shortcut_applier_user(self.storage, self.sid, self.username),
'gsettings': gsettings_applier_user(self.storage, self.sid, self.username)
})
def machine_apply(self):
'''
Run global appliers with administrator privileges.
'''
if not is_root():
log('E13')
logging.error('Not sufficient privileges to run machine appliers')
return
log('D16')
for applier_name, applier_object in self.machine_appliers.items():
try:
applier_object.apply()
except Exception as exc:
logdata = dict()
logdata['applier_name'] = applier_name
logdata['msg'] = str(exc)
log('E24', logdata)
logging.debug(slogm('Applying computer part of settings'))
for exe in self.frontend_module_binaries:
subprocess.check_call([exe.resolve()])
self.machine_appliers['systemd'].apply()
self.machine_appliers['control'].apply()
self.machine_appliers['polkit'].apply()
self.machine_appliers['firefox'].apply()
self.machine_appliers['chromium'].apply()
self.machine_appliers['shortcuts'].apply()
self.machine_appliers['gsettings'].apply()
self.machine_appliers['cups'].apply()
#self.machine_appliers['package'].apply()
def user_apply(self):
'''
Run appliers for users.
'''
if is_root():
for applier_name, applier_object in self.user_appliers.items():
try:
applier_object.admin_context_apply()
except Exception as exc:
logdata = dict()
logdata['applier'] = applier_name
logdata['exception'] = str(exc)
log('E19', logdata)
logging.debug(slogm('Running user appliers from administrator context'))
self.user_appliers['shortcuts'].admin_context_apply()
self.user_appliers['gsettings'].admin_context_apply()
try:
with_privileges(self.username, applier_object.user_context_apply)
except Exception as exc:
logdata = dict()
logdata['applier'] = applier_name
logdata['exception'] = str(exc)
log('E20', logdata)
logging.debug(slogm('Running user appliers for user context'))
with_privileges(self.username, self.user_appliers['shortcuts'].user_context_apply)
with_privileges(self.username, self.user_appliers['gsettings'].user_context_apply)
else:
for applier_name, applier_object in self.user_appliers.items():
try:
applier_object.user_context_apply()
except Exception as exc:
logdata = dict({'applier_name': applier_name, 'message': str(exc)})
log('E11', logdata)
logging.debug(slogm('Running user appliers from user context'))
self.user_appliers['shortcuts'].user_context_apply()
self.user_appliers['gsettings'].user_context_apply()
def apply_parameters(self):
'''

View File

@ -18,18 +18,9 @@
import logging
import os
import pwd
import subprocess
from gi.repository import (
Gio
, GLib
)
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .applier_frontend import applier_frontend
from .appliers.gsettings import (
system_gsetting,
user_gsetting
@ -37,12 +28,8 @@ from .appliers.gsettings import (
from util.logging import slogm
class gsettings_applier(applier_frontend):
__module_name = 'GSettingsApplier'
__module_experimental = True
__module_enabled = False
__registry_branch = 'Software\\BaseALT\\Policies\\gsettings'
__global_schema = '/usr/share/glib-2.0/schemas'
__windows_settings = dict()
def __init__(self, storage):
self.storage = storage
@ -50,13 +37,8 @@ class gsettings_applier(applier_frontend):
self.gsettings_keys = self.storage.filter_hklm_entries(gsettings_filter)
self.gsettings = list()
self.override_file = os.path.join(self.__global_schema, '0_policy.gschema.override')
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
def apply(self):
# Cleanup settings from previous run
if os.path.exists(self.override_file):
logging.debug(slogm('Removing GSettings policy file from previous run'))
@ -80,47 +62,7 @@ class gsettings_applier(applier_frontend):
except Exception as exc:
logging.debug(slogm('Error recompiling global GSettings schemas'))
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running GSettings applier for machine'))
else:
logging.debug(slogm('GSettings applier for machine will not be started'))
class GSettingsMapping:
def __init__(self, hive_key, gsettings_schema, gsettings_key):
self.hive_key = hive_key
self.gsettings_schema = gsettings_schema
self.gsettings_key = gsettings_key
try:
self.schema_source = Gio.SettingsSchemaSource.get_default()
self.schema = self.schema_source.lookup(self.gsettings_schema, True)
self.gsettings_schema_key = self.schema.get_key(self.gsettings_key)
self.gsettings_type = self.gsettings_schema_key.get_value_type()
except Exception as exc:
logdata = dict()
logdata['hive_key'] = self.hive_key
logdata['gsettings_schema'] = self.gsettings_schema
logdata['gsettings_key'] = self.gsettings_key
logging.warning(slogm('Unable to resolve GSettings parameter {}.{}'.format(self.gsettings_schema, self.gsettings_key)))
def preg2gsettings(self):
'''
Transform PReg key variant into GLib.Variant. This function
performs mapping of PReg type system into GLib type system.
'''
pass
def gsettings2preg(self):
'''
Transform GLib.Variant key type into PReg key type.
'''
pass
class gsettings_applier_user(applier_frontend):
__module_name = 'GSettingsApplierUser'
__module_experimental = True
__module_enabled = False
__registry_branch = 'Software\\BaseALT\\Policies\\gsettings'
def __init__(self, storage, sid, username):
@ -130,74 +72,17 @@ class gsettings_applier_user(applier_frontend):
gsettings_filter = '{}%'.format(self.__registry_branch)
self.gsettings_keys = self.storage.filter_hkcu_entries(self.sid, gsettings_filter)
self.gsettings = list()
self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_enabled)
self.__windows_settings = dict()
self.windows_settings = list()
mapping = [
# Disable or enable screen saver
GSettingsMapping(
'Software\\Policies\\Microsoft\\Windows\\Control Panel\\Desktop\\ScreenSaveActive'
, 'org.mate.screensaver'
, 'idle-activation-enabled'
)
# Timeout in seconds for screen saver activation. The value of zero effectively disables screensaver start
, GSettingsMapping(
'Software\\Policies\\Microsoft\\Windows\\Control Panel\\Desktop\\ScreenSaveTimeOut'
, 'org.mate.session'
, 'idle-delay'
)
# Enable or disable password protection for screen saver
, GSettingsMapping(
'Software\\Policies\\Microsoft\\Windows\\Control Panel\\Desktop\\ScreenSaverIsSecure'
, 'org.mate.screensaver'
, 'lock-enabled'
)
# Specify image which will be used as a wallpaper
, GSettingsMapping(
'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\Wallpaper'
, 'org.mate.background'
, 'picture-filename'
)
]
self.windows_settings.extend(mapping)
for element in self.windows_settings:
self.__windows_settings[element.hive_key] = element
def run(self):
#for setting in self.gsettings_keys:
# valuename = setting.hive_key.rpartition('\\')[2]
# rp = valuename.rpartition('.')
# schema = rp[0]
# path = rp[2]
# self.gsettings.append(user_gsetting(schema, path, setting.data))
os.environ['DBUS_SESSION_BUS_ADDRESS'] = 'unix:path=/run/user/{}/bus'.format(pwd.getpwnam(self.username).pw_uid)
for setting_key in self.__windows_settings.keys():
logging.debug('Checking for GSettings mapping {}'.format(setting_key))
value = self.storage.get_hkcu_entry(self.sid, setting_key)
if value:
logging.debug('Found GSettings mapping {} to {}'.format(setting_key, value.data))
mapping = self.__windows_settings[setting_key]
self.gsettings.append(user_gsetting(mapping.gsettings_schema, mapping.gsettings_key, value.data))
else:
logging.debug('GSettings mapping for {} not found'.format(setting_key))
for gsetting in self.gsettings:
logging.debug('Applying setting {}/{}'.format(gsetting.schema, gsetting.path))
gsetting.apply()
del os.environ['DBUS_SESSION_BUS_ADDRESS']
def user_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running GSettings applier for user in user context'))
self.run()
else:
logging.debug(slogm('GSettings applier for user in user context will not be started'))
for setting in self.gsettings_keys:
valuename = setting.hive_key.rpartition('\\')[2]
rp = valuename.rpartition('.')
schema = rp[0]
path = rp[2]
self.gsettings.append(user_gsetting(schema, path, setting.data))
for gsetting in self.gsettings:
gsetting.apply()
def admin_context_apply(self):
'''

View File

@ -1,147 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import subprocess
from enum import Enum
from .applier_frontend import (
applier_frontend
, check_enabled
)
from util.logging import slogm
class NTPServerType(Enum):
NTP = 'NTP'
class ntp_applier(applier_frontend):
__module_name = 'NTPApplier'
__module_experimental = True
__module_enabled = False
__ntp_branch = 'Software\\Policies\\Microsoft\\W32time\\Parameters'
__ntp_client_branch = 'Software\\Policies\\Microsoft\\W32time\\TimeProviders\\NtpClient'
__ntp_server_branch = 'Software\\Policies\\Microsoft\\W32time\\TimeProviders\\NtpServer'
__ntp_key_address = 'NtpServer'
__ntp_key_type = 'Type'
__ntp_key_client_enabled = 'Enabled'
__ntp_key_server_enabled = 'Enabled'
__chrony_config = '/etc/chrony.conf'
def __init__(self, storage):
self.storage = storage
self.ntp_server_address_key = '{}\\{}'.format(self.__ntp_branch, self.__ntp_key_address)
self.ntp_server_type = '{}\\{}'.format(self.__ntp_branch, self.__ntp_key_type)
self.ntp_client_enabled = '{}\\{}'.format(self.__ntp_client_branch, self.__ntp_key_client_enabled)
self.ntp_server_enabled = '{}\\{}'.format(self.__ntp_server_branch, self.__ntp_key_server_enabled)
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def _chrony_as_client(self):
command = ['/usr/sbin/control', 'chrony', 'client']
proc = subprocess.Popen(command)
proc.wait()
def _chrony_as_server(self):
command = ['/usr/sbin/control', 'chrony', 'server']
proc = subprocess.Popen(command)
proc.wait()
def _start_chrony_client(self, server=None):
srv = None
if server:
srv = server.data.rpartition(',')[0]
logging.debug(slogm('NTP server is configured to {}'.format(srv)))
start_command = ['/usr/bin/systemctl', 'start', 'chronyd']
chrony_set_server = ['/usr/bin/chronyc', 'add', 'server', srv]
chrony_disconnect_all = ['/usr/bin/chronyc', 'offline']
chrony_connect = ['/usr/bin/chronyc', 'online', srv]
logging.debug(slogm('Starting Chrony daemon'))
proc = subprocess.Popen(start_command)
proc.wait()
if srv:
logging.debug(slogm('Setting reference NTP server to {}'.format(srv)))
proc = subprocess.Popen(chrony_disconnect_all)
proc.wait()
proc = subprocess.Popen(chrony_set_server)
proc.wait()
proc = subprocess.Popen(chrony_connect)
proc.wait()
def _stop_chrony_client(self):
stop_command = ['/usr/bin/systemctl', 'stop', 'chronyd']
logging.debug(slogm('Stopping Chrony daemon'))
proc = subprocess.Popen(stop_command)
proc.wait()
def run(self):
server_type = self.storage.get_hklm_entry(self.ntp_server_type)
server_address = self.storage.get_hklm_entry(self.ntp_server_address_key)
ntp_server_enabled = self.storage.get_hklm_entry(self.ntp_server_enabled)
ntp_client_enabled = self.storage.get_hklm_entry(self.ntp_client_enabled)
if NTPServerType.NTP.value != server_type.data:
logging.warning(slogm('Unsupported NTP server type: {}'.format(server_type)))
else:
logging.debug(slogm('Configuring NTP server...'))
if '1' == ntp_server_enabled.data:
logging.debug(slogm('NTP server is enabled'))
self._start_chrony_client(server_address)
self._chrony_as_server()
elif '0' == ntp_server_enabled.data:
logging.debug(slogm('NTP server is disabled'))
self._chrony_as_client()
else:
logging.debug(slogm('NTP server is not configured'))
if '1' == ntp_client_enabled.data:
logging.debug(slogm('NTP client is enabled'))
self._start_chrony_client()
elif '0' == ntp_client_enabled.data:
logging.debug(slogm('NTP client is disabled'))
self._stop_chrony_client()
else:
logging.debug(slogm('NTP client is not configured'))
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running NTP applier for machine'))
self.run()
else:
logging.debug(slogm('NTP applier for machine will not be started'))

View File

@ -17,84 +17,20 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from util.logging import slogm
from util.rpm import (
update
, install_rpm
, remove_rpm
)
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .applier_frontend import applier_frontend
from .appliers.rpm import rpm
class package_applier(applier_frontend):
__module_name = 'PackagesApplier'
__module_experimental = True
__module_enabled = False
__install_key_name = 'Install'
__remove_key_name = 'Remove'
__hklm_branch = 'Software\\BaseALT\\Policies\\Packages'
def __init__(self, storage):
self.storage = storage
install_branch = '{}\\{}%'.format(self.__hklm_branch, self.__install_key_name)
remove_branch = '{}\\{}%'.format(self.__hklm_branch, self.__remove_key_name)
self.install_packages_setting = self.storage.filter_hklm_entries(install_branch)
self.remove_packages_setting = self.storage.filter_hklm_entries(remove_branch)
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
if 0 < self.install_packages_setting.count() or 0 < self.remove_packages_setting.count():
update()
for package in self.install_packages_setting:
try:
install_rpm(package.data)
except Exception as exc:
logging.error(exc)
for package in self.remove_packages_setting:
try:
remove_rpm(package.data)
except Exception as exc:
logging.error(exc)
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Package applier for machine'))
self.run()
else:
logging.debug(slogm('Package applier for machine will not be started'))
pass
class package_applier_user(applier_frontend):
__module_name = 'PackagesApplierUser'
__module_experimental = True
__module_enabled = False
__install_key_name = 'Install'
__remove_key_name = 'Remove'
__hkcu_branch = 'Software\\BaseALT\\Policies\\Packages'
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
install_branch = '{}\\{}%'.format(self.__hkcu_branch, self.__install_key_name)
remove_branch = '{}\\{}%'.format(self.__hkcu_branch, self.__remove_key_name)
self.install_packages_setting = self.storage.filter_hkcu_entries(self.sid, install_branch)
self.remove_packages_setting = self.storage.filter_hkcu_entries(self.sid, remove_branch)
self.__module_enabled = check_enabled(self.storage, self.__module_name, self.__module_enabled)
def __init__(self):
pass
def user_context_apply(self):
'''
@ -102,29 +38,10 @@ class package_applier_user(applier_frontend):
'''
pass
def run(self):
if 0 < self.install_packages_setting.count() or 0 < self.remove_packages_setting.count():
update()
for package in self.install_packages_setting:
try:
install_rpm(package.data)
except Exception as exc:
logging.debug(exc)
for package in self.remove_packages_setting:
try:
remove_rpm(package.data)
except Exception as exc:
logging.debug(exc)
def admin_context_apply(self):
'''
Install software assigned to specified username regardless
which computer he uses to log into system.
'''
if self.__module_enabled:
logging.debug(slogm('Running Package applier for user in administrator context'))
self.run()
else:
logging.debug(slogm('Package applier for user in administrator context will not be started'))
pass

View File

@ -16,22 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .applier_frontend import applier_frontend
from .appliers.polkit import polkit
from util.logging import slogm
import logging
class polkit_applier(applier_frontend):
__module_name = 'PolkitApplier'
__module_experimental = False
__module_enabled = True
__deny_all = 'Software\\Policies\\Microsoft\\Windows\\RemovableStorageDevices\\Deny_All'
__polkit_map = {
__deny_all: ['49-gpoa_disk_permissions', { 'Deny_All': 0 }]
__deny_all: ['99-gpoa_disk_permissions', { 'Deny_All': 0 }]
}
def __init__(self, storage):
@ -47,66 +41,11 @@ class polkit_applier(applier_frontend):
logging.debug(slogm('Deny_All setting not found'))
self.policies = []
self.policies.append(polkit(template_file, template_vars))
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def apply(self):
'''
Trigger control facility invocation.
'''
if self.__module_enabled:
logging.debug(slogm('Running Polkit applier for machine'))
for policy in self.policies:
policy.generate()
else:
logging.debug(slogm('Polkit applier for machine will not be started'))
class polkit_applier_user(applier_frontend):
__module_name = 'PolkitApplierUser'
__module_experimental = False
__module_enabled = True
__deny_all = 'Software\\Policies\\Microsoft\\Windows\\RemovableStorageDevices\\Deny_All'
__polkit_map = {
__deny_all: ['48-gpoa_disk_permissions_user', { 'Deny_All': 0, 'User': '' }]
}
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
deny_all = storage.filter_hkcu_entries(self.sid, self.__deny_all).first()
# Deny_All hook: initialize defaults
template_file = self.__polkit_map[self.__deny_all][0]
template_vars = self.__polkit_map[self.__deny_all][1]
if deny_all:
logging.debug(slogm('Deny_All setting for user {} found: {}'.format(self.username, deny_all.data)))
self.__polkit_map[self.__deny_all][1]['Deny_All'] = deny_all.data
self.__polkit_map[self.__deny_all][1]['User'] = self.username
else:
logging.debug(slogm('Deny_All setting not found'))
self.policies = []
self.policies.append(polkit(template_file, template_vars, self.username))
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def user_context_apply(self):
pass
def admin_context_apply(self):
'''
Trigger control facility invocation.
'''
if self.__module_enabled:
logging.debug(slogm('Running Polkit applier for user in administrator context'))
for policy in self.policies:
policy.generate()
else:
logging.debug(slogm('Polkit applier for user in administrator context will not be started'))
for policy in self.policies:
policy.generate()

View File

@ -19,10 +19,7 @@
import logging
import subprocess
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .applier_frontend import applier_frontend
from gpt.shortcuts import json2sc
from util.windows import expand_windows_var
from util.logging import slogm
@ -50,12 +47,7 @@ def write_shortcut(shortcut, username=None):
:username: None means working with machine variables and paths
'''
dest_abspath = shortcut.dest
if not dest_abspath.startswith('/') and not dest_abspath.startswith('%'):
dest_abspath = '%HOME%/' + dest_abspath
logging.debug(slogm('Try to expand path for shortcut: {} for {}'.format(dest_abspath, username)))
dest_abspath = expand_windows_var(dest_abspath, username).replace('\\', '/') + '.desktop'
dest_abspath = expand_windows_var(shortcut.dest, username).replace('\\', '/') + '.desktop'
# Check that we're working for user, not on global system level
if username:
@ -66,66 +58,34 @@ def write_shortcut(shortcut, username=None):
if not homedir_exists(username):
logging.warning(slogm('No home directory exists for user {}: will not create link {}'.format(username, dest_abspath)))
return None
else:
logging.warning(slogm('User\'s shortcut not placed to home directory for {}: bad path {}'.format(username, dest_abspath)))
return None
if '%' in dest_abspath:
logging.debug(slogm('Fail for writing shortcut to file with \'%\': {}'.format(dest_abspath)))
return None
if not dest_abspath.startswith('/'):
logging.debug(slogm('Fail for writing shortcut to not absolute path \'%\': {}'.format(dest_abspath)))
return None
logging.debug(slogm('Writing shortcut file to {}'.format(dest_abspath)))
shortcut.write_desktop(dest_abspath)
class shortcut_applier(applier_frontend):
__module_name = 'ShortcutsApplier'
__module_experimental = False
__module_enabled = True
def __init__(self, storage):
self.storage = storage
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
def apply(self):
shortcuts = storage_get_shortcuts(self.storage, self.storage.get_info('machine_sid'))
if shortcuts:
for sc in shortcuts:
write_shortcut(sc)
if len(shortcuts) > 0:
# According to ArchWiki - this thing is needed to rebuild MIME
# type cache in order file bindings to work. This rebuilds
# databases located in /usr/share/applications and
# /usr/local/share/applications
subprocess.check_call(['/usr/bin/update-desktop-database'])
else:
logging.debug(slogm('No shortcuts to process for {}'.format(self.storage.get_info('machine_sid'))))
def apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Shortcut applier for machine'))
self.run()
else:
logging.debug(slogm('Shortcut applier for machine will not be started'))
# According to ArchWiki - this thing is needed to rebuild MIME
# type cache in order file bindings to work. This rebuilds
# databases located in /usr/share/applications and
# /usr/local/share/applications
subprocess.check_call(['/usr/bin/update-desktop-database'])
class shortcut_applier_user(applier_frontend):
__module_name = 'ShortcutsApplierUser'
__module_experimental = False
__module_enabled = True
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.username = username
def run(self):
def user_context_apply(self):
shortcuts = storage_get_shortcuts(self.storage, self.sid)
if shortcuts:
@ -135,17 +95,13 @@ class shortcut_applier_user(applier_frontend):
else:
logging.debug(slogm('No shortcuts to process for {}'.format(self.sid)))
def user_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Shortcut applier for user in user context'))
self.run()
else:
logging.debug(slogm('Shortcut applier for user in user context will not be started'))
def admin_context_apply(self):
if self.__module_enabled:
logging.debug(slogm('Running Shortcut applier for user in administrator context'))
self.run()
else:
logging.debug(slogm('Shortcut applier for user in administrator context will not be started'))
shortcuts = storage_get_shortcuts(self.storage, self.sid)
if shortcuts:
for sc in shortcuts:
if not sc.is_usercontext():
write_shortcut(sc, self.username)
else:
logging.debug(slogm('No shortcuts to process for {}'.format(self.sid)))

View File

@ -16,32 +16,24 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .applier_frontend import (
applier_frontend
, check_enabled
)
from .applier_frontend import applier_frontend
from .appliers.systemd import systemd_unit
from util.logging import slogm
import logging
class systemd_applier(applier_frontend):
__module_name = 'SystemdApplier'
__module_experimental = False
__module_enabled = True
__registry_branch = 'Software\\BaseALT\\Policies\\SystemdUnits'
def __init__(self, storage):
self.storage = storage
self.systemd_unit_settings = self.storage.filter_hklm_entries('Software\\BaseALT\\Policies\\SystemdUnits%')
self.units = []
self.__module_enabled = check_enabled(
self.storage
, self.__module_name
, self.__module_experimental
)
def run(self):
def apply(self):
'''
Trigger control facility invocation.
'''
for setting in self.systemd_unit_settings:
valuename = setting.hive_key.rpartition('\\')[2]
try:
@ -55,20 +47,7 @@ class systemd_applier(applier_frontend):
except:
logging.error(slogm('Failed applying unit {}'.format(unit.unit_name)))
def apply(self):
'''
Trigger control facility invocation.
'''
if self.__module_enabled:
logging.debug(slogm('Running systemd applier for machine'))
self.run()
else:
logging.debug(slogm('systemd applier for machine will not be started'))
class systemd_applier_user(applier_frontend):
__module_name = 'SystemdApplierUser'
__module_experimental = False
__module_enabled = True
__registry_branch = 'Software\\BaseALT\\Policies\\SystemdUnits'
def __init__(self, storage, sid, username):

View File

@ -18,17 +18,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
import logging
import os
import signal
import gettext
import locale
from backend import backend_factory
from frontend.frontend_manager import frontend_manager, determine_username
from plugin import plugin_manager
from messages import message_with_code
from util.util import get_machine_name
from util.kerberos import machine_kinit
from util.users import (
is_root,
get_process_user
@ -36,8 +35,7 @@ from util.users import (
from util.arguments import (
set_loglevel
)
from util.logging import log
from util.exceptions import geterr
from util.logging import slogm
from util.signals import signal_handler
def parse_arguments():
@ -58,9 +56,6 @@ def parse_arguments():
arguments.add_argument('--noplugins',
action='store_true',
help='Don\'t start plugins')
arguments.add_argument('--list-backends',
action='store_true',
help='Show list of available backends')
arguments.add_argument('--loglevel',
type=int,
default=4,
@ -68,6 +63,7 @@ def parse_arguments():
return arguments.parse_args()
class gpoa_controller:
__kinit_successful = False
__args = None
def __init__(self):
@ -77,16 +73,11 @@ class gpoa_controller:
user = get_machine_name()
self.is_machine = True
set_loglevel(self.__args.loglevel)
locale.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale')
gettext.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale')
gettext.textdomain('gpoa')
self.__kinit_successful = machine_kinit()
uname = get_process_user()
uid = os.getuid()
logdata = dict()
logdata['username'] = uname
logdata['uid'] = uid
log('D1', logdata)
logging.debug(slogm('The process was started for user {} with UID {}'.format(uname, uid), uid=uid))
if not is_root():
self.username = uname
@ -97,12 +88,9 @@ class gpoa_controller:
'''
GPOA controller entry point
'''
if self.__args.list_backends:
print('local')
print('samba')
return
self.start_plugins()
self.start_backend()
self.start_frontend()
def start_backend(self):
'''
@ -115,31 +103,9 @@ class gpoa_controller:
if not self.__args.noupdate:
if is_root():
back = None
try:
back = backend_factory(dc, self.username, self.is_machine, nodomain)
except Exception as exc:
logdata = dict({'msg': str(exc)})
einfo = geterr()
print(einfo)
print(type(einfo))
#logdata.update(einfo)
log('E12', logdata)
back = backend_factory(dc, self.username, self.is_machine, nodomain)
if back:
try:
back.retrieve_and_store()
# Start frontend only on successful backend finish
self.start_frontend()
except Exception as exc:
logdata = dict({'message': str(exc)})
# In case we're handling "E3" - it means that
# this is a very specific exception that was
# not handled properly on lower levels of
# code so we're also printing file name and
# other information.
einfo = geterr()
logdata.update(einfo)
log('E3', logdata)
back.retrieve_and_store()
def start_frontend(self):
'''
@ -149,11 +115,7 @@ class gpoa_controller:
appl = frontend_manager(self.username, self.is_machine)
appl.apply_parameters()
except Exception as exc:
logdata = dict({'message': str(exc)})
einfo = geterr()
#print(einfo)
logdata.update(einfo)
log('E4', logdata)
logging.error(slogm('Error occured while running applier: {}'.format(exc)))
def start_plugins(self):
'''
@ -168,7 +130,6 @@ def main():
controller.run()
if __name__ == "__main__":
default_handler = signal.getsignal(signal.SIGINT)
signal.signal(signal.SIGINT, signal_handler)
main()

View File

@ -22,13 +22,24 @@ from Crypto.Cipher import AES
from util.xml import get_xml_root
def read_drives(drives_file):
drives = list()
for drive in get_xml_root(drives_file):
drive_obj = drivemap()
props = drive.find('Properties')
drive_obj.set_login(props.get('username'))
drive_obj.set_pass(props.get('cpassword'))
drives.append(drive_obj)
return drives
def decrypt_pass(cpassword):
'''
AES key for cpassword decryption: http://msdn.microsoft.com/en-us/library/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be%28v=PROT.13%29#endNote2
'''
if not cpassword:
return cpassword
key = (
b'\x4e\x99\x06\xe8'
b'\xfc\xb6\x6c\xc9'
@ -42,80 +53,23 @@ def decrypt_pass(cpassword):
cpass_len = len(cpassword)
padded_pass = (cpassword + "=" * ((4 - cpass_len % 4) % 4))
password = b64decode(padded_pass)
decrypter = AES.new(key, AES.MODE_CBC, '\x00' * 16)
decrypter = AES(key, AES.MODE_CBC, '\x00' * 16)
# decrypt() returns byte array which is immutable and we need to
# strip padding, then convert UTF-16LE to UTF-8
binstr = decrypter.decrypt(password)
by = list()
for item in binstr:
if item != 16:
by.append(item)
utf16str = bytes(by).decode('utf-16', 'ignore')
utf8str = utf16str.encode('utf8')
return utf8str.decode()
def read_drives(drives_file):
drives = list()
for drive in get_xml_root(drives_file):
drive_obj = drivemap()
props = drive.find('Properties')
drive_obj.set_login(props.get('username'))
drive_obj.set_pass(decrypt_pass(props.get('cpassword')))
drive_obj.set_dir(props.get('letter'))
drive_obj.set_path(props.get('path'))
drives.append(drive_obj)
return drives
def merge_drives(storage, sid, drive_objects, policy_name):
for drive in drive_objects:
storage.add_drive(sid, drive, policy_name)
def json2drive(json_str):
json_obj = json.loads(json_str)
drive_obj = drivemap()
drive_obj.set_login(json_obj['login'])
drive_obj.set_pass(json_obj['password'])
drive_obj.set_dir(json_obj['dir'])
drive_obj.set_path(json_obj['path'])
return drive_obj
return decrypter.decrypt(password)
class drivemap:
def __init__(self):
self.login = None
self.password = None
self.dir = None
self.path = None
def set_login(self, username):
self.login = username
if not username:
self.login = ''
def set_pass(self, password):
self.password = password
if not password:
self.password = ''
def set_dir(self, path):
self.dir = path
def set_path(self, path):
self.path = path
def to_json(self):
drive = dict()
drive['login'] = self.login
drive['password'] = self.password
drive['dir'] = self.dir
drive['path'] = self.path
contents = dict()
contents['drive'] = drive

View File

@ -28,10 +28,6 @@ def read_envvars(envvars_file):
return variables
def merge_envvars(storage, sid, envvars_objects, policy_name):
for envvar in envvars_objects:
pass
class envvar:
def __init__(self):
pass

View File

@ -28,10 +28,6 @@ def read_files(filesxml):
return files
def merge_files(storage, sid, file_objects, policy_name):
for fileobj in file_objects:
pass
class fileentry:
def __init__(self):
pass

View File

@ -16,83 +16,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from util.xml import get_xml_root
class FileAction(Enum):
CREATE = 'C'
REPLACE = 'R'
UPDATE = 'U'
DELETE = 'D'
def action_letter2enum(letter):
if letter in ['C', 'R', 'U', 'D']:
if letter == 'C': return FileAction.CREATE
if letter == 'R': return FileAction.REPLACE
if letter == 'U': return FileAction.UPDATE
if letter == 'D': return FileAction.DELETE
return FileAction.CREATE
def action_enum2letter(enumitem):
return enumitem.value
def folder_int2bool(val):
value = val
if type(value) == str:
value = int(value)
if value == 0:
return True
return False
def read_folders(folders_file):
folders = list()
for fld in get_xml_root(folders_file):
props = fld.find('Properties')
fld_obj = folderentry(props.get('path'))
fld_obj.set_action(action_letter2enum(props.get('action', default='C')))
fld_obj.set_delete_folder(folder_int2bool(props.get('deleteFolder', default=1)))
fld_obj.set_delete_sub_folders(folder_int2bool(props.get('deleteSubFolders', default=1)))
fld_obj.set_delete_files(folder_int2bool(props.get('deleteFiles', default=1)))
fld_obj = folderentry()
folders.append(fld_obj)
return folders
def merge_folders(storage, sid, folder_objects, policy_name):
for folder in folder_objects:
storage.add_folder(sid, folder, policy_name)
class folderentry:
def __init__(self, path):
self.path = path
self.action = FileAction.CREATE
self.delete_folder = False
self.delete_sub_folders = False
self.delete_files = False
def set_action(self, action):
self.action = action
def set_delete_folder(self, del_bool):
self.delete_folder = del_bool
def set_delete_sub_folders(self, del_bool):
self.delete_sub_folders = del_bool
def set_delete_files(self, del_bool):
self.delete_files = del_bool
def __init__(self):
pass

View File

@ -16,55 +16,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
from pathlib import Path
from enum import Enum, unique
from samba.gp_parse.gp_pol import GPPolParser
from storage import registry_factory
from .polfile import (
read_polfile
, merge_polfile
)
from .shortcuts import (
read_shortcuts
, merge_shortcuts
)
from .services import (
read_services
, merge_services
)
from .printers import (
read_printers
, merge_printers
)
from .inifiles import (
read_inifiles
, merge_inifiles
)
from .folders import (
read_folders
, merge_folders
)
from .files import (
read_files
, merge_files
)
from .envvars import (
read_envvars
, merge_envvars
)
from .drives import (
read_drives
, merge_drives
)
from .tasks import (
read_tasks
, merge_tasks
)
from .shortcuts import read_shortcuts
from .services import read_services
from .printers import read_printers
from .inifiles import read_inifiles
from .folders import read_folders
from .files import read_files
from .envvars import read_envvars
from .drives import read_drives
import util
import util.preg
from util.paths import (
@ -72,72 +37,7 @@ from util.paths import (
cache_dir,
local_policy_cache
)
from util.logging import log
@unique
class FileType(Enum):
PREG = 'registry.pol'
SHORTCUTS = 'shortcuts.xml'
FOLDERS = 'folders.xml'
FILES = 'files.xml'
DRIVES = 'drives.xml'
SCHEDULEDTASKS = 'scheduledtasks.xml'
ENVIRONMENTVARIABLES = 'environmentvariables.xml'
INIFILES = 'inifiles.xml'
SERVICES = 'services.xml'
PRINTERS = 'printers.xml'
def get_preftype(path_to_file):
fpath = Path(path_to_file)
if fpath.exists():
file_name = fpath.name.lower()
for item in FileType:
if file_name == item.value:
return item
return None
def pref_parsers():
parsers = dict()
parsers[FileType.PREG] = read_polfile
parsers[FileType.SHORTCUTS] = read_shortcuts
parsers[FileType.FOLDERS] = read_folders
parsers[FileType.FILES] = read_files
parsers[FileType.DRIVES] = read_drives
parsers[FileType.SCHEDULEDTASKS] = read_tasks
parsers[FileType.ENVIRONMENTVARIABLES] = read_envvars
parsers[FileType.INIFILES] = read_inifiles
parsers[FileType.SERVICES] = read_services
parsers[FileType.PRINTERS] = read_printers
return parsers
def get_parser(preference_type):
parsers = pref_parsers()
return parsers[preference_type]
def pref_mergers():
mergers = dict()
mergers[FileType.PREG] = merge_polfile
mergers[FileType.SHORTCUTS] = merge_shortcuts
mergers[FileType.FOLDERS] = merge_folders
mergers[FileType.FILES] = merge_files
mergers[FileType.DRIVES] = merge_drives
mergers[FileType.SCHEDULEDTASKS] = merge_tasks
mergers[FileType.ENVIRONMENTVARIABLES] = merge_envvars
mergers[FileType.INIFILES] = merge_inifiles
mergers[FileType.SERVICES] = merge_services
mergers[FileType.PRINTERS] = merge_printers
return mergers
def get_merger(preference_type):
mergers = pref_mergers()
return mergers[preference_type]
from util.logging import slogm
class gpt:
__user_policy_mode_key = 'Software\\Policies\\Microsoft\\Windows\\System\\UserPolicyMode'
@ -146,40 +46,27 @@ class gpt:
self.path = gpt_path
self.sid = sid
self.storage = registry_factory('registry')
self.name = ''
self._scan_gpt()
def _scan_gpt(self):
'''
Collect the data from the specified GPT on file system (cached
by Samba).
'''
self.guid = self.path.rpartition('/')[2]
self.name = ''
if 'default' == self.guid:
self.guid = 'Local Policy'
self._machine_path = find_dir(self.path, 'Machine')
self._user_path = find_dir(self.path, 'User')
self._machine_prefs = find_dir(self._machine_path, 'Preferences')
self._user_prefs = find_dir(self._user_path, 'Preferences')
self.settings_list = [
'shortcuts'
, 'drives'
, 'environmentvariables'
, 'printers'
, 'folders'
, 'files'
, 'inifiles'
, 'services'
, 'scheduledtasks'
]
self.settings = dict()
self.settings['machine'] = dict()
self.settings['user'] = dict()
self.settings['machine']['regpol'] = find_file(self._machine_path, 'registry.pol')
self.settings['user']['regpol'] = find_file(self._user_path, 'registry.pol')
for setting in self.settings_list:
machine_preffile = find_preffile(self._machine_path, setting)
user_preffile = find_preffile(self._user_path, setting)
mlogdata = dict({'setting': setting, 'prefpath': machine_preffile})
log('D24', mlogdata)
self.settings['machine'][setting] = machine_preffile
ulogdata = dict({'setting': setting, 'prefpath': user_preffile})
log('D23', ulogdata)
self.settings['user'][setting] = user_preffile
logging.debug(slogm('Looking for machine part of GPT {}'.format(self.guid)))
self._find_machine()
logging.debug(slogm('Looking for user part of GPT {}'.format(self.guid)))
self._find_user()
def set_name(self, name):
'''
@ -202,55 +89,142 @@ class gpt:
return upm
def _find_user(self):
self._user_regpol = self._find_regpol('user')
self._user_shortcuts = self._find_shortcuts('user')
def _find_machine(self):
self._machine_regpol = self._find_regpol('machine')
self._machine_shortcuts = self._find_shortcuts('machine')
def _find_regpol(self, part):
'''
Find Registry.pol files.
'''
search_path = self._machine_path
if 'user' == part:
search_path = self._user_path
if not search_path:
return None
return find_file(search_path, 'registry.pol')
def _find_shortcuts(self, part):
'''
Find Shortcuts.xml files.
'''
shortcuts_dir = find_dir(self._machine_prefs, 'Shortcuts')
shortcuts_file = find_file(shortcuts_dir, 'shortcuts.xml')
if 'user' == part:
shortcuts_dir = find_dir(self._user_prefs, 'Shortcuts')
shortcuts_file = find_file(shortcuts_dir, 'shortcuts.xml')
return shortcuts_file
def _find_envvars(self, part):
'''
Find EnvironmentVariables.xml files.
'''
search_path = os.path.join(self._machine_path, 'Preferences', 'EnvironmentVariables')
if 'user' == part:
search_path = os.path.join(self._user_path, 'Preferences', 'EnvironmentVariables')
if not search_path:
return None
return find_file(search_path, 'environmentvariables.xml')
def _find_drives(self, part):
'''
Find Drives.xml files.
'''
search_path = os.path.join(self._machine_path, 'Preferences', 'Drives')
if 'user' == part:
search_path = os.path.join(self._user_path, 'Preferences', 'Drives')
if not search_path:
return None
return find_file(search_path, 'drives.xml')
def _find_printers(self, part):
'''
Find Printers.xml files.
'''
search_path = os.path.join(self._machine_path, 'Preferences', 'Printers')
if 'user' == part:
search_path = os.path.join(self._user_path, 'Preferences', 'Printers')
if not search_path:
return None
return find_file(search_path, 'printers.xml')
def _merge_shortcuts(self):
shortcuts = list()
if self.sid == self.storage.get_info('machine_sid'):
shortcuts = read_shortcuts(self._machine_shortcuts)
else:
shortcuts = read_shortcuts(self._user_shortcuts)
for sc in shortcuts:
self.storage.add_shortcut(self.sid, sc)
def merge(self):
'''
Merge machine and user (if sid provided) settings to storage.
'''
if self.sid == self.storage.get_info('machine_sid'):
try:
# Merge machine settings to registry if possible
for preference_name, preference_path in self.settings['machine'].items():
if preference_path:
preference_type = get_preftype(preference_path)
logdata = dict({'pref': preference_type.value, 'sid': self.sid})
log('D28', logdata)
preference_parser = get_parser(preference_type)
preference_merger = get_merger(preference_type)
preference_objects = preference_parser(preference_path)
preference_merger(self.storage, self.sid, preference_objects, self.name)
if self.settings['user']['regpol']:
mulogdata = dict({'polfile': self.settings['machine']['regpol']})
log('D35', mulogdata)
util.preg.merge_polfile(self.settings['user']['regpol'], sid=self.sid, policy_name=self.name)
if self.settings['machine']['regpol']:
mlogdata = dict({'polfile': self.settings['machine']['regpol']})
log('D34', mlogdata)
util.preg.merge_polfile(self.settings['machine']['regpol'], policy_name=self.name)
except Exception as exc:
logdata = dict()
logdata['gpt'] = self.name
logdata['msg'] = str(exc)
log('E28', logdata)
# Merge machine settings to registry if possible
if self._machine_regpol:
logging.debug(slogm('Merging machine settings from {}'.format(self._machine_regpol)))
util.preg.merge_polfile(self._machine_regpol)
if self._user_regpol:
logging.debug(slogm('Merging machine(user) settings from {}'.format(self._machine_regpol)))
util.preg.merge_polfile(self._user_regpol, self.sid)
if self._machine_shortcuts:
logging.debug(slogm('Merging machine shortcuts from {}'.format(self._machine_shortcuts)))
self._merge_shortcuts()
else:
# Merge user settings if UserPolicyMode set accordingly
# and user settings (for HKCU) are exist.
policy_mode = upm2str(self.get_policy_mode())
if 'Merge' == policy_mode or 'Not configured' == policy_mode:
try:
for preference_name, preference_path in self.settings['user'].items():
if preference_path:
preference_type = get_preftype(preference_path)
logdata = dict({'pref': preference_type.value, 'sid': self.sid})
log('D29', logdata)
preference_parser = get_parser(preference_type)
preference_merger = get_merger(preference_type)
preference_objects = preference_parser(preference_path)
preference_merger(self.storage, self.sid, preference_objects, self.name)
except Exception as exc:
logdata = dict()
logdata['gpt'] = self.name
logdata['msg'] = str(exc)
log('E29', logdata)
if self._user_regpol:
logging.debug(slogm('Merging user settings from {} for {}'.format(self._user_regpol, self.sid)))
util.preg.merge_polfile(self._user_regpol, self.sid)
if self._user_shortcuts:
logging.debug(slogm('Merging user shortcuts from {} for {}'.format(self._user_shortcuts, self.sid)))
self._merge_shortcuts()
def __str__(self):
template = '''
GUID: {}
Name: {}
For SID: {}
Machine part: {}
Machine Registry.pol: {}
Machine Shortcuts.xml: {}
User part: {}
User Registry.pol: {}
User Shortcuts.xml: {}
'''
result = template.format(
self.guid,
self.name,
self.sid,
self._machine_path,
self._machine_regpol,
self._machine_shortcuts,
self._user_path,
self._user_regpol,
self._user_shortcuts,
)
return result
def find_dir(search_path, name):
'''
@ -295,33 +269,6 @@ def find_file(search_path, name):
return None
def find_preferences(search_path):
'''
Find 'Preferences' directory
'''
if not search_path:
return None
return find_dir(search_path, 'Preferences')
def find_preffile(search_path, prefname):
'''
Find file with path like Preferences/prefname/prefname.xml
'''
# Look for 'Preferences' directory
prefdir = find_preferences(search_path)
if not prefdir:
return None
# Then search for preference directory
pref_dir = find_dir(prefdir, prefname)
file_name = '{}.xml'.format(prefname)
# And then try to find the corresponding file.
pref_file = find_file(pref_dir, file_name)
return pref_file
def lp2gpt():
'''
Convert local-policy to full-featured GPT.
@ -344,7 +291,7 @@ def get_local_gpt(sid):
'''
Convert default policy to GPT and create object out of it.
'''
log('D25')
logging.debug(slogm('Re-caching Local Policy'))
lp2gpt()
local_policy = gpt(str(local_policy_cache()), sid)
local_policy.set_name('Local Policy')

View File

@ -28,10 +28,6 @@ def read_inifiles(inifiles_file):
return inifiles
def merge_inifiles(storage, sid, inifile_objects, policy_name):
for inifile in inifile_objects:
pass
def inifile():
def __init__(self):
pass

View File

@ -1,32 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from util.preg import (
load_preg
)
def read_polfile(filename):
return load_preg(filename).entries
def merge_polfile(storage, sid, policy_objects, policy_name):
for entry in policy_objects:
if not sid:
storage.add_hklm_entry(entry, policy_name)
else:
storage.add_hkcu_entry(entry, sid, policy_name)

View File

@ -41,10 +41,6 @@ def read_printers(printers_file):
return printers
def merge_printers(storage, sid, printer_objects, policy_name):
for device in printer_objects:
storage.add_printer(sid, device, policy_name)
def json2printer(json_str):
'''
Build printer object out of string-serialized JSON.

View File

@ -39,10 +39,6 @@ def read_services(service_file):
return services
def merge_services(storage, sid, service_objects, policy_name):
for srv in service_objects:
pass
class service:
def __init__(self, name):
self.unit = name

View File

@ -84,15 +84,10 @@ def read_shortcuts(shortcuts_file):
sc.set_clsid(link.get('clsid'))
sc.set_guid(link.get('uid'))
sc.set_usercontext(link.get('userContext', False))
sc.set_icon(props.get('iconPath'))
shortcuts.append(sc)
return shortcuts
def merge_shortcuts(storage, sid, shortcut_objects, policy_name):
for shortcut in shortcut_objects:
storage.add_shortcut(sid, shortcut, policy_name)
def json2sc(json_str):
'''
Build shortcut out of string-serialized JSON
@ -105,8 +100,6 @@ def json2sc(json_str):
sc.set_clsid(json_obj['clsid'])
sc.set_guid(json_obj['guid'])
sc.set_usercontext(json_obj['is_in_user_context'])
if 'icon' in json_obj:
sc.set_icon(json_obj['icon'])
return sc
@ -124,7 +117,6 @@ class shortcut:
self.arguments = arguments
self.name = name
self.changed = ''
self.icon = None
self.is_in_user_context = self.set_usercontext()
self.type = ttype
@ -144,9 +136,6 @@ class shortcut:
def set_guid(self, uid):
self.guid = uid
def set_icon(self, icon_name):
self.icon = icon_name
def set_type(self, ttype):
'''
Set type of the hyperlink - FILESYSTEM or URL
@ -183,8 +172,7 @@ class shortcut:
content['changed'] = self.changed
content['is_in_user_context'] = self.is_in_user_context
content['type'] = ttype2str(self.type)
if self.icon:
content['icon'] = self.icon
result = self.desktop()
result.content.update(content)
@ -211,9 +199,6 @@ class shortcut:
self.desktop_file.set('Terminal', 'false')
self.desktop_file.set('Exec', '{} {}'.format(self.path, self.arguments))
if self.icon:
self.desktop_file.set('Icon', self.icon)
return self.desktop_file
def write_desktop(self, dest):

View File

@ -1,25 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
def read_tasks(filename):
pass
def merge_tasks(storage, sid, task_objects, policy_name):
for task in task_objects:
pass

View File

@ -18,11 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
import locale
import gettext
import subprocess
import os
import sys
import logging
import pwd
import signal
@ -31,7 +31,6 @@ from util.users import (
)
from util.arguments import (
process_target,
set_loglevel,
ExitCodeUpdater
)
from util.dbus import (
@ -40,9 +39,7 @@ from util.dbus import (
)
from util.signals import signal_handler
from util.logging import log
#logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG)
class file_runner:
_gpoa_exe = '/usr/sbin/gpoa'
@ -92,18 +89,24 @@ def runner_factory(args, target):
target = 'Computer'
except:
username = None
logdata = dict({'username': args.user})
log('W1', logdata)
logstring = (
'Unable to perform gpupdate for non-existent user {},'
' will update machine settings'
)
logging.error(logstring.format(args.user))
else:
# User may only perform gpupdate for machine (None) or
# itself (os.getusername()).
username = pwd.getpwuid(os.getuid()).pw_name
if args.user != username:
logdata = dict({'username': args.user})
log('W2', logdata)
logstring = (
'Unable to perform gpupdate for {} with current'
' permissions, will update current user settings'
)
logging.error(logstring.format(args.user))
if is_oddjobd_gpupdate_accessible():
log('D13')
logging.debug('Starting gpupdate via D-Bus')
computer_runner = None
user_runner = None
if target == 'All' or target == 'Computer':
@ -113,10 +116,10 @@ def runner_factory(args, target):
user_runner = dbus_runner(username)
return (computer_runner, user_runner)
else:
log('W3')
logging.warning('oddjobd is inaccessible')
if is_root():
log('D14')
logging.debug('Starting gpupdate by command invocation')
computer_runner = None
user_runner = None
if target == 'All' or target == 'Computer':
@ -125,16 +128,12 @@ def runner_factory(args, target):
user_runner = file_runner(username)
return (computer_runner, user_runner)
else:
log('E1')
logging.error('Insufficient permissions to run gpupdate')
return None
def main():
args = parse_cli_arguments()
locale.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale')
gettext.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale')
gettext.textdomain('gpoa')
set_loglevel(0)
gpo_appliers = runner_factory(args, process_target(args.target))
if gpo_appliers:
@ -142,19 +141,17 @@ def main():
try:
gpo_appliers[0].run()
except Exception as exc:
logdata = dict({'error': str(exc)})
log('E5')
logging.error('Error running GPOA for computer: {}'.format(exc))
return int(ExitCodeUpdater.FAIL_GPUPDATE_COMPUTER_NOREPLY)
if gpo_appliers[1]:
try:
gpo_appliers[1].run()
except Exception as exc:
logdata = dict({'error': str(exc)})
log('E6', logdata)
logging.error('Error running GPOA for user: {}'.format(exc))
return int(ExitCodeUpdater.FAIL_GPUPDATE_USER_NOREPLY)
else:
log('E2')
logging.error('gpupdate will not be started')
return int(ExitCodeUpdater.FAIL_NO_RUNNER)
return int(ExitCodeUpdater.EXIT_SUCCESS)

View File

@ -1,333 +0,0 @@
#! /usr/bin/env python3
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import argparse
import subprocess
import re
from util.util import (
runcmd
, get_backends
, get_default_policy_name
, get_policy_entries
, get_policy_variants
)
from util.config import GPConfig
class Runner:
__control_path = '/usr/sbin/control'
__systemctl_path = '/bin/systemctl'
__etc_policy_dir = '/etc/local-policy'
__usr_policy_dir = '/usr/share/local-policy'
def __init__(self):
self.etc_policies = get_policy_entries(self.__etc_policy_dir)
self.usr_policies = get_policy_entries(self.__usr_policy_dir)
self.arguments = parse_arguments()
def parse_arguments():
'''
Parse CLI arguments.
'''
parser = argparse.ArgumentParser(prog='gpupdate-setup')
subparsers = parser.add_subparsers(dest='action',
metavar='action',
help='Group Policy management actions (default action is status)')
parser_list = subparsers.add_parser('list',
help='List avalable types of local policy')
parser_list = subparsers.add_parser('list-backends',
help='Show list of available backends')
parser_status = subparsers.add_parser('status',
help='Show current Group Policy status')
parser_enable = subparsers.add_parser('enable',
help='Enable Group Policy subsystem')
parser_disable = subparsers.add_parser('disable',
help='Disable Group Policy subsystem')
parser_write = subparsers.add_parser('write',
help='Operate on Group Policies (enable or disable)')
parser_set_backend = subparsers.add_parser('set-backend',
help='Set or change currently active backend')
parser_default = subparsers.add_parser('default-policy',
help='Show name of default policy')
parser_active = subparsers.add_parser('active-policy',
help='Show name of policy enabled')
parser_active_backend = subparsers.add_parser('active-backend',
help='Show currently configured backend')
parser_set_backend.add_argument('backend',
default='samba',
type=str,
nargs='?',
const='backend',
choices=['local', 'samba'],
help='Backend (source of settings) name')
parser_write.add_argument('status',
choices=['enable', 'disable'],
help='Enable or disable Group Policies')
parser_write.add_argument('localpolicy',
default=None,
nargs='?',
help='Name of local policy to enable')
parser_write.add_argument('backend',
default='samba',
type=str,
nargs='?',
const='backend',
choices=['local', 'samba'],
help='Backend (source of settings) name')
parser_enable.add_argument('--local-policy',
default=None,
help='Name of local policy to enable')
parser_enable.add_argument('--backend',
default='samba',
type=str,
choices=['local', 'samba'],
help='Backend (source of settings) name')
return parser.parse_args()
def validate_policy_name(policy_name):
return policy_name in [os.path.basename(d) for d in get_policy_variants()]
def is_unit_enabled(unit_name, unit_global=False):
'''
Check that designated systemd unit is enabled
'''
command = ['/bin/systemctl', 'is-enabled', unit_name]
if unit_global:
command = ['/bin/systemctl', '--global', 'is-enabled', unit_name]
value = runcmd(command)
# If first line of stdout is equal to "enabled" and return code
# is zero then unit is considered enabled.
rc = value[0]
result = []
try:
result = value[1].replace('\n', '')
except IndexError as exc:
return False
if result == 'enabled' and rc == 0:
return True
return False
def get_status():
'''
Check that gpupdate.service and gpupdate-user.service are enabled.
'''
is_gpupdate = is_unit_enabled('gpupdate.service')
is_gpupdate_user = is_unit_enabled('gpupdate-user.service', unit_global=True)
if is_gpupdate and is_gpupdate_user:
return True
return False
def get_active_policy_name():
'''
Show the name of an active Local Policy template
'''
config = GPConfig()
return os.path.basename(config.get_local_policy_template())
def get_active_backend():
config = GPConfig()
return config.get_backend()
def rollback_on_error(command_name):
'''
Disable group policy services in case command returns error code
'''
if 0 != runcmd(command_name)[0]:
disable_gp()
return False
return True
def disable_gp():
'''
Consistently disable group policy services
'''
cmd_set_global_policy = ['/usr/sbin/control', 'system-policy', 'remote']
cmd_set_local_policy = ['/usr/sbin/control', 'system-policy', 'local']
cmd_disable_gpupdate_service = ['/bin/systemctl', 'disable', 'gpupdate.service']
cmd_disable_gpupdate_user_service = ['/bin/systemctl', '--global', 'disable', 'gpupdate-user.service']
cmd_control_system_auth = ['/usr/sbin/control', 'system-auth']
config = GPConfig()
auth_result = 'local'
try:
auth_result = runcmd(cmd_control_system_auth)[1][0]
except Exception as exc:
print(str(exc))
if auth_result != 'local':
runcmd(cmd_set_global_policy)
else:
runcmd(cmd_set_local_policy)
runcmd(cmd_disable_gpupdate_service)
runcmd(cmd_disable_gpupdate_user_service)
def enable_gp(policy_name, backend_type):
'''
Consistently enable group policy services
'''
policy_dir = '/usr/share/local-policy'
etc_policy_dir = '/etc/local-policy'
cmd_set_gpupdate_policy = ['/usr/sbin/control', 'system-policy', 'gpupdate']
cmd_gpoa_nodomain = ['/usr/sbin/gpoa', '--nodomain', '--loglevel', '5']
cmd_enable_gpupdate_service = ['/bin/systemctl', 'enable', 'gpupdate.service']
cmd_enable_gpupdate_user_service = ['/bin/systemctl', '--global', 'enable', 'gpupdate-user.service']
config = GPConfig()
target_policy_name = get_default_policy_name()
if policy_name:
if validate_policy_name(policy_name):
target_policy_name = policy_name
print (target_policy_name)
default_policy_name = os.path.join(policy_dir, target_policy_name)
if not os.path.isdir(etc_policy_dir):
os.makedirs(etc_policy_dir)
config.set_local_policy_template(default_policy_name)
config.set_backend(backend_type)
# Enable oddjobd_gpupdate in PAM config
if not rollback_on_error(cmd_set_gpupdate_policy):
return
# Bootstrap the Group Policy engine
if not rollback_on_error(cmd_gpoa_nodomain):
return
# Enable gpupdate.service
if not rollback_on_error(cmd_enable_gpupdate_service):
return
if not is_unit_enabled('gpupdate.service'):
disable_gp()
return
# Enable gpupdate-setup.service for all users
if not rollback_on_error(cmd_enable_gpupdate_user_service):
return
if not is_unit_enabled('gpupdate-user.service', unit_global=True):
disable_gp()
return
def act_list():
'''
Show list of available templates of Local Policy
'''
for entry in get_policy_variants():
print(entry.rpartition('/')[2])
def act_list_backends():
'''
List backends supported by GPOA
'''
backends = get_backends()
for backend in backends:
print(backend)
def act_status():
'''
Check that group policy services are enabled
'''
if get_status():
print('enabled')
else:
print('disabled')
def act_set_backend(backend_name):
config = GPConfig()
config.set_backend(backend_name)
def act_write(status, localpolicy, backend):
'''
Enable or disable group policy services
'''
if status == 'enable' or status == '#t':
enable_gp(localpolicy, backend)
if status == 'disable' or status == '#f':
disable_gp()
def act_enable(localpolicy, backend):
'''
Enable group policy services
'''
enable_gp(localpolicy, backend)
def act_active_policy():
'''
Print active Local Policy template name to stdout
'''
print(get_active_policy_name())
def act_active_backend():
'''
Print currently configured backend.
'''
print(get_active_backend())
def act_default_policy():
'''
Print default Local Policy template name to stdout
'''
print(get_default_policy_name())
def main():
arguments = parse_arguments()
action = dict()
action['list'] = act_list
action['list-backends'] = act_list_backends
action['status'] = act_status
action['set-backend'] = act_set_backend
action['write'] = act_write
action['enable'] = act_enable
action['disable'] = disable_gp
action['active-policy'] = act_active_policy
action['active-backend'] = act_active_backend
action['default-policy'] = act_default_policy
if arguments.action == None:
action['status']()
elif arguments.action == 'enable':
action[arguments.action](arguments.local_policy, arguments.backend)
elif arguments.action == 'write':
action[arguments.action](arguments.status, arguments.localpolicy, arguments.backend)
elif arguments.action == 'set-backend':
action[arguments.action](arguments.backend)
else:
action[arguments.action]()
if __name__ == '__main__':
main()

View File

@ -1,331 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#domain "gpoa"
msgid ""
msgstr ""
"Project-Id-Version: 0.8.0\n"
"Report-Msgid-Bugs-To: samba@lists.altlinux.org\n"
"PO-Revision-Date: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain;charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
msgid "Don't start plugins"
msgstr "Не запускать модули"
# Info
msgid "Got GPO list for username"
msgstr "Получен список GPO для пользователя"
msgid "Got GPO"
msgstr "Получен объект групповой политики"
msgid "Unknown info code"
msgstr "Неизвестный код информационного сообщения"
# Error
msgid "Insufficient permissions to run gpupdate"
msgstr "Недостаточно прав для запуска gpupdate"
msgid "gpupdate will not be started"
msgstr "gpupdate не будет запущен"
msgid "Backend execution error"
msgstr "Ошибка бэкэнда"
msgid "Error occurred while running frontend manager"
msgstr "Ошибка фронтенда"
msgid "Error running GPOA for computer"
msgstr "Ошибка запуска GPOA для машины"
msgid "Error running GPOA for user"
msgstr "Ошибка запуска GPOA для пользователя"
msgid "Unable to initialize Samba backend"
msgstr "Невозможно инициализировать бэкэнд Samba"
msgid "Unable to initialize no-domain backend"
msgstr "Невозможно инициализировать бэкэнд-заглушку"
msgid "Error running ADP"
msgstr "Ошибка во время работы ADP"
msgid "Unable to determine DC hostname"
msgstr "Невозможно определить имя контроллера домена"
msgid "Error occured while running applier with user privileges"
msgstr "Ошибка во время работы applier в контексте пользователя"
msgid "Unable to initialize backend"
msgstr "Невозможно инициализировать бэкэнд"
msgid "Not sufficient privileges to run machine appliers"
msgstr "Недостаточно прав для запуска appliers для машины"
msgid "Kerberos ticket check failed"
msgstr "Проверка билета Kerberos закончилась неудачно"
msgid "Unable to retrieve domain name via CLDAP query"
msgstr "Не удалось определить имя домена AD через запрос к LDAP"
msgid "Error getting SID using wbinfo, will use SID from cache"
msgstr "Не удалось определить SID с использованием утилиты wbinfo, будет использоваться фиктивный/кэшированный SID"
msgid "Unable to get GPO list for user from AD DC"
msgstr "Не удалось получить список групповых политик для пользователя от контроллера домена AD"
msgid "Error getting XDG_DESKTOP_DIR"
msgstr "Не удалось получить значение XDG_DESKTOP_DIR"
msgid "Error occured while running user applier in administrator context"
msgstr "Ошибка выполнения applier в контексте администратора"
msgid "Error occured while running user applier in user context (with dropped privileges)"
msgstr "Ошибка работы пользовательского applier в пользовательском контексте (со сбросом привилегий процесса)"
msgid "No reply from oddjobd GPOA runner via D-Bus for current user"
msgstr "Не получен ответ от oddjobd для текущего пользователя"
msgid "No reply from oddjobd GPOA runner via D-Bus for computer"
msgstr "Не получен ответ от oddjobd для компьютера"
msgid "No reply from oddjobd GPOA runner via D-Bus for user"
msgstr "Не получен ответ от oddjobd для пользователя"
msgid "Error occured while running machine applier"
msgstr "Ошибка во время работы applier для машины"
msgid "Error occured while initializing user applier"
msgstr "Ошибка инициализации пользовательского applier"
msgid "Error merging machine GPT"
msgstr "Ошибка слияния машинной групповой политики"
msgid "Error merging user GPT"
msgstr "Ошибка слияния пользовательской групповой политики"
msgid "Error merging machine part of GPT"
msgstr "Ошибка слияния машинной части групповой политики"
msgid "Error merging user part of GPT"
msgstr "Ошибка слияния пользовательской части групповой политики"
msgid "Unknown error code"
msgstr "Неизвестный код ошибки"
# Debug
msgid "The GPOA process was started for user"
msgstr "Произведён запуск GPOA для обновления политик пользователя"
msgid "Username is not specified - will use username of the current process"
msgstr "Имя пользователя не указано - будет использовано имя владельца процесса"
msgid "Initializing plugin manager"
msgstr "Инициализация плагинов"
msgid "ADP plugin initialized"
msgstr "Инициализирован плагин ADP"
msgid "Running ADP plugin"
msgstr "Запущен плагин ADP"
msgid "Starting GPOA for user via D-Bus"
msgstr "Запускается GPOA для пользователя обращением к oddjobd через D-Bus"
msgid "Cache directory determined"
msgstr "Определена директория кэша Samba"
msgid "Initializing local backend without domain"
msgstr "Инициализация бэкэнда-заглушки"
msgid "Initializing Samba backend for domain"
msgstr "Инициализация бэкэнда Samba"
msgid "Group Policy target set for update"
msgstr "Групповые политики будут обновлены для указанной цели"
msgid "Starting GPOA for computer via D-Bus"
msgstr "Запускается GPOA для компьютера обращением к oddjobd через D-Bus"
msgid "Got exit code"
msgstr "Получен код возврата из утилиты"
msgid "Starting GPOA via D-Bus"
msgstr "Запускается GPOA обращением к oddjobd через D-Bus"
msgid "Starting GPOA via command invocation"
msgstr "GPOA запускается с помощью прямого вызова приложения"
msgid "Username for frontend is determined"
msgstr "Определено имя пользователя для фронтенда"
msgid "Applying computer part of settings"
msgstr "Применение настроек для машины"
msgid "Kerberos ticket check succeed"
msgstr "Проверка билета Kerberos прошла успешно"
msgid "Found AD domain via CLDAP query"
msgstr "Имя домена Active Directory успешно определено при запросе к LDAP"
msgid "Setting info"
msgstr "Установка вспомогательной переменной"
msgid "Initializing cache"
msgstr "Инициализация кэша"
msgid "Set operational SID"
msgstr "Установка рабочего SID"
msgid "Got PReg entry"
msgstr "Получен ключ реестра"
msgid "Looking for preference in user part of GPT"
msgstr "Поиск настроек в пользовательской части GPT"
msgid "Looking for preference in machine part of GPT"
msgstr "Поиск настроек в машинной части GPT"
msgid "Re-caching Local Policy"
msgstr "Обновление кэша локальной политики"
msgid "Adding HKCU entry"
msgstr "Слияние ключа в пользовательскую (HKCU) часть реестра"
msgid "Skipping HKLM branch deletion key"
msgstr "Пропускаем специальный ключ удаления ветви реестра HKLM"
msgid "Reading and merging machine preference"
msgstr "Вычитывание и слияние машинных настроек"
msgid "Reading and merging user preference"
msgstr "Вычитывание и слияние пользовательских настроек"
msgid "Found SYSVOL entry"
msgstr "Найден путь SYSVOL"
msgid "Trying to load PReg from .pol file"
msgstr "Пробуем загрузить ключи реестра из .pol файла"
msgid "Finished reading PReg from .pol file"
msgstr "Вычитаны ключи реестра из .pol файла"
msgid "Determined length of PReg file"
msgstr "Определена длина .pol файла"
msgid "Merging machine settings from PReg file"
msgstr "Слияние машинных настроек из .pol файла"
msgid "Merging machine (user part) settings from PReg file"
msgstr "Слияние пользовательской части машинных настроек из .pol файла"
msgid "Loading PReg from XML"
msgstr "Загружаем ключи реестра из XML"
msgid "Setting process permissions"
msgstr "Установка прав процесса"
msgid "Samba DC setting is overriden by user setting"
msgstr "Используется указанный пользователем контроллер домена AD"
msgid "Saving information about drive mapping"
msgstr "Сохранение информации о привязках дисков"
msgid "Saving information about printer"
msgstr "Сохранение информации о принтерах"
msgid "Saving information about link"
msgstr "Сохранение информации о ярлычках"
msgid "Saving information about folder"
msgstr "Сохранение информации о папках"
msgid "No value cached for object"
msgstr "Отсутствует кэшированное значение для объекта"
msgid "Key is already present in cache, will update the value"
msgstr "Ключ уже существует, его значение будет обновлено"
msgid "GPO update started"
msgstr "Начато обновление GPO"
msgid "GPO update finished"
msgstr "Завершено обновление GPO"
msgid "Retrieving list of GPOs to replicate from AD DC"
msgstr "Получение списка GPO для репликации с контроллера домена AD"
msgid "Establishing connection with AD DC"
msgstr "Установка соединения с контроллером домена AD"
msgid "Started GPO replication from AD DC"
msgstr "Начата репликация GPO от контроллера домена AD"
msgid "Finished GPO replication from AD DC"
msgstr "Завершена репликация GPO от контроллера домена AD"
msgid "Skipping HKCU branch deletion key"
msgstr "Пропускаем специальный ключ удаления ветви реестра HKCU"
msgid "Read domain name from configuration file"
msgstr "Имя контроллера домена для репликации прочитано из файла конфигурации"
msgid "Unknown debug code"
msgstr "Неизвестный отладочный код"
# Warning
msgid "Unable to perform gpupdate for non-existent user, will update machine settings"
msgstr "Невозможно запустить gpupdate для несуществующего пользователя, будут обновлены настройки машины"
msgid "Current permissions does not allow to perform gpupdate for designated user. Will update current user settings"
msgstr "Текущий уровень привилегий не позволяет выполнить gpupdate для указанного пользователя. Будут обновлены настройки текущего пользователя."
msgid "oddjobd is inaccessible"
msgstr "oddjobd недоступен"
msgid "No SYSVOL entry assigned to GPO"
msgstr "Объект групповой политики не имеет привязанного пути на SYSVOL"
msgid "ADP package is not installed - plugin will not be initialized"
msgstr "Пакет ADP не установлен, плагин не будет инициализирован"
msgid "Unknown warning code"
msgstr "Неизвестный код предупреждения"
# Fatal
msgid "Unable to refresh GPO list"
msgstr "Невозможно обновить список объектов групповых политик"
msgid "Error getting GPTs for machine"
msgstr "Не удалось получить GPT для машины"
msgid "Error getting GPTs for user"
msgstr "Не удалось получить GPT для пользователя"
msgid "Unknown fatal code"
msgstr "Неизвестный код фатальной ошибки"
# get_message
msgid "Unknown message type, no message assigned"
msgstr "Неизвестный тип сообщения"

View File

@ -1,164 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gettext
def info_code(code):
info_ids = dict()
info_ids[1] = 'Got GPO list for username'
info_ids[2] = 'Got GPO'
return info_ids.get(code, 'Unknown info code')
def error_code(code):
error_ids = dict()
error_ids[1] = 'Insufficient permissions to run gpupdate'
error_ids[2] = 'gpupdate will not be started'
error_ids[3] = 'Backend execution error'
error_ids[4] = 'Error occurred while running frontend manager'
error_ids[5] = 'Error running GPOA for computer'
error_ids[6] = 'Error running GPOA for user'
error_ids[7] = 'Unable to initialize Samba backend'
error_ids[8] = 'Unable to initialize no-domain backend'
error_ids[9] = 'Error running ADP'
error_ids[10] = 'Unable to determine DC hostname'
error_ids[11] = 'Error occured while running applier with user privileges'
error_ids[12] = 'Unable to initialize backend'
error_ids[13] = 'Not sufficient privileges to run machine appliers'
error_ids[14] = 'Kerberos ticket check failed'
error_ids[15] = 'Unable to retrieve domain name via CLDAP query'
error_ids[16] = 'Error getting SID using wbinfo, will use SID from cache'
error_ids[17] = 'Unable to get GPO list for user from AD DC'
error_ids[18] = 'Error getting XDG_DESKTOP_DIR'
error_ids[19] = 'Error occured while running user applier in administrator context'
error_ids[20] = 'Error occured while running user applier in user context (with dropped privileges)'
error_ids[21] = 'No reply from oddjobd GPOA runner via D-Bus for current user'
error_ids[22] = 'No reply from oddjobd GPOA runner via D-Bus for computer'
error_ids[23] = 'No reply from oddjobd GPOA runner via D-Bus for user'
error_ids[24] = 'Error occured while running machine applier'
error_ids[25] = 'Error occured while initializing user applier'
error_ids[26] = 'Error merging machine GPT'
error_ids[27] = 'Error merging user GPT'
error_ids[28] = 'Error merging machine part of GPT'
error_ids[29] = 'Error merging user part of GPT'
return error_ids.get(code, 'Unknown error code')
def debug_code(code):
debug_ids = dict()
debug_ids[1] = 'The GPOA process was started for user'
debug_ids[2] = 'Username is not specified - will use username of the current process'
debug_ids[3] = 'Initializing plugin manager'
debug_ids[4] = 'ADP plugin initialized'
debug_ids[5] = 'Running ADP plugin'
debug_ids[6] = 'Starting GPOA for user via D-Bus'
debug_ids[7] = 'Cache directory determined'
debug_ids[8] = 'Initializing local backend without domain'
debug_ids[9] = 'Initializing Samba backend for domain'
debug_ids[10] = 'Group Policy target set for update'
debug_ids[11] = 'Starting GPOA for computer via D-Bus'
debug_ids[12] = 'Got exit code'
debug_ids[13] = 'Starting GPOA via D-Bus'
debug_ids[14] = 'Starting GPOA via command invocation'
debug_ids[15] = 'Username for frontend is determined'
debug_ids[16] = 'Applying computer part of settings'
debug_ids[17] = 'Kerberos ticket check succeed'
debug_ids[18] = 'Found AD domain via CLDAP query'
debug_ids[19] = 'Setting info'
debug_ids[20] = 'Initializing cache'
debug_ids[21] = 'Set operational SID'
debug_ids[22] = 'Got PReg entry'
debug_ids[23] = 'Looking for preference in user part of GPT'
debug_ids[24] = 'Looking for preference in machine part of GPT'
debug_ids[25] = 'Re-caching Local Policy'
debug_ids[26] = 'Adding HKCU entry'
debug_ids[27] = 'Skipping HKLM branch deletion key'
debug_ids[28] = 'Reading and merging machine preference'
debug_ids[29] = 'Reading and merging user preference'
debug_ids[30] = 'Found SYSVOL entry'
debug_ids[31] = 'Trying to load PReg from .pol file'
debug_ids[32] = 'Finished reading PReg from .pol file'
debug_ids[33] = 'Determined length of PReg file'
debug_ids[34] = 'Merging machine settings from PReg file'
debug_ids[35] = 'Merging machine (user part) settings from PReg file'
debug_ids[36] = 'Loading PReg from XML'
debug_ids[37] = 'Setting process permissions'
debug_ids[38] = 'Samba DC setting is overriden by user setting'
debug_ids[39] = 'Saving information about drive mapping'
debug_ids[40] = 'Saving information about printer'
debug_ids[41] = 'Saving information about link'
debug_ids[42] = 'Saving information about folder'
debug_ids[43] = 'No value cached for object'
debug_ids[44] = 'Key is already present in cache, will update the value'
debug_ids[45] = 'GPO update started'
debug_ids[46] = 'GPO update finished'
debug_ids[47] = 'Retrieving list of GPOs to replicate from AD DC'
debug_ids[48] = 'Establishing connection with AD DC'
debug_ids[49] = 'Started GPO replication from AD DC'
debug_ids[50] = 'Finished GPO replication from AD DC'
debug_ids[51] = 'Skipping HKCU branch deletion key'
debug_ids[52] = 'Read domain name from configuration file'
return debug_ids.get(code, 'Unknown debug code')
def warning_code(code):
warning_ids = dict()
warning_ids[1] = (
'Unable to perform gpupdate for non-existent user, '
'will update machine settings'
)
warning_ids[2] = (
'Current permissions does not allow to perform gpupdate for '
'designated user. Will update current user settings'
)
warning_ids[3] = 'oddjobd is inaccessible'
warning_ids[4] = 'No SYSVOL entry assigned to GPO'
warning_ids[5] = 'ADP package is not installed - plugin will not be initialized'
return warning_ids.get(code, 'Unknown warning code')
def fatal_code(code):
fatal_ids = dict()
fatal_ids[1] = 'Unable to refresh GPO list'
fatal_ids[2] = 'Error getting GPTs for machine'
fatal_ids[3] = 'Error getting GPTs for user'
return fatal_ids.get(code, 'Unknown fatal code')
def get_message(code):
retstr = 'Unknown message type, no message assigned'
if code.startswith('E'):
retstr = error_code(int(code[1:]))
if code.startswith('I'):
retstr = info_code(int(code[1:]))
if code.startswith('D'):
retstr = debug_code(int(code[1:]))
if code.startswith('W'):
retstr = warning_code(int(code[1:]))
if code.startswith('F'):
retstr = fatal_code(int(code[1:]))
return retstr
def message_with_code(code):
retstr = '[' + code[0:1] + code[1:].rjust(5, '0') + ']| ' + gettext.gettext(get_message(code))
return retstr

View File

@ -22,20 +22,19 @@ import subprocess
from util.rpm import is_rpm_installed
from .exceptions import PluginInitError
from util.logging import slogm
from messages import message_with_code
class adp:
def __init__(self):
if not is_rpm_installed('adp'):
raise PluginInitError(message_with_code('W5'))
logging.info(slogm(message_with_code('D4')))
raise PluginInitError('adp is not installed - plugin cannot be initialized')
logging.info(slogm('ADP plugin initialized'))
def run(self):
try:
logging.info(slogm(message_with_code('D5')))
logging.info('Running ADP plugin')
subprocess.call(['/usr/bin/adp', 'fetch'])
subprocess.call(['/usr/bin/adp', 'apply'])
except Exception as exc:
logging.error(slogm(message_with_code('E9')))
logging.error(slogm('Error running ADP'))
raise exc

View File

@ -23,16 +23,15 @@ from .roles import roles
from .exceptions import PluginInitError
from .plugin import plugin
from util.logging import slogm
from messages import message_with_code
class plugin_manager:
def __init__(self):
self.plugins = dict()
logging.debug(slogm(message_with_code('D3')))
logging.info(slogm('Starting plugin manager'))
try:
self.plugins['adp'] = adp()
except PluginInitError as exc:
logging.warning(slogm(str(exc)))
logging.error(slogm(exc))
def run(self):
self.plugins.get('adp', plugin('adp')).run()

View File

@ -20,131 +20,40 @@ class samba_preg(object):
'''
Object mapping representing HKLM entry (registry key without SID)
'''
def __init__(self, preg_obj, policy_name):
self.policy_name = policy_name
def __init__(self, preg_obj):
self.hive_key = '{}\\{}'.format(preg_obj.keyname, preg_obj.valuename)
self.type = preg_obj.type
self.data = preg_obj.data
def update_fields(self):
fields = dict()
fields['policy_name'] = self.policy_name
fields['type'] = self.type
fields['data'] = self.data
return fields
class samba_hkcu_preg(object):
'''
Object mapping representing HKCU entry (registry key with SID)
'''
def __init__(self, sid, preg_obj, policy_name):
def __init__(self, sid, preg_obj):
self.sid = sid
self.policy_name = policy_name
self.hive_key = '{}\\{}'.format(preg_obj.keyname, preg_obj.valuename)
self.type = preg_obj.type
self.data = preg_obj.data
def update_fields(self):
fields = dict()
fields['policy_name'] = self.policy_name
fields['type'] = self.type
fields['data'] = self.data
return fields
class ad_shortcut(object):
'''
Object mapping representing Windows shortcut.
'''
def __init__(self, sid, sc, policy_name):
def __init__(self, sid, sc):
self.sid = sid
self.policy_name = policy_name
self.path = sc.dest
self.shortcut = sc.to_json()
def update_fields(self):
fields = dict()
fields['policy_name'] = self.policy_name
fields['path'] = self.path
fields['shortcut'] = self.shortcut
return fields
class info_entry(object):
def __init__(self, name, value):
self.name = name
self.value = value
def update_fields(self):
fields = dict()
fields['value'] = self.value
return fields
class printer_entry(object):
'''
Object mapping representing Windows printer of some type.
'''
def __init__(self, sid, pobj, policy_name):
def __init__(self, sid, pobj):
self.sid = sid
self.policy_name = policy_name
self.name = pobj.name
self.printer = pobj.to_json()
def update_fields(self):
fields = dict()
fields['policy_name'] = self.policy_name
fields['name'] = self.name
fields['printer'] = self.printer.to_json()
return fields
class drive_entry(object):
'''
Object mapping representing Samba share bound to drive letter
'''
def __init__(self, sid, dobj, policy_name):
self.sid = sid
self.policy_name = policy_name
self.login = dobj.login
self.password = dobj.password
self.dir = dobj.dir
self.path = dobj.path
def update_fields(self):
fields = dict()
fields['policy_name'] = self.policy_name
fields['login'] = self.login
fields['password'] = self.password
fields['dir'] = self.dir
fields['path'] = self.path
return fields
class folder_entry(object):
'''
Object mapping representing file system directory
'''
def __init__(self, sid, fobj, policy_name):
self.sid = sid
self.policy_name = policy_name
self.path = fobj.path
self.action = fobj.action.value
self.delete_folder = str(fobj.delete_folder)
self.delete_sub_folders = str(fobj.delete_sub_folders)
self.delete_files = str(fobj.delete_files)
def update_fields(self):
'''
Return list of fields to update
'''
fields = dict()
fields['policy_name'] = self.policy_name
fields['action'] = self.action
fields['delete_folder'] = self.delete_folder
fields['delete_sub_folders'] = self.delete_sub_folders
fields['delete_files'] = self.delete_files
return fields

View File

@ -18,6 +18,7 @@
from .cache import cache
import logging
import os
from sqlalchemy import (
@ -33,7 +34,7 @@ from sqlalchemy.orm import (
sessionmaker
)
from util.logging import log
from util.logging import slogm
from util.paths import cache_dir
def mapping_factory(mapper_suffix):
@ -52,8 +53,7 @@ class sqlite_cache(cache):
self.cache_name = cache_name
self.mapper_obj = mapping_factory(self.cache_name)
self.storage_uri = os.path.join('sqlite:///{}/{}.sqlite'.format(cache_dir(), self.cache_name))
logdata = dict({'cache_file': self.storage_uri})
log('D20', logdata)
logging.debug(slogm('Initializing cache {}'.format(self.storage_uri)))
self.db_cnt = create_engine(self.storage_uri, echo=False)
self.__metadata = MetaData(self.db_cnt)
self.cache_table = Table(
@ -80,9 +80,7 @@ class sqlite_cache(cache):
def get_default(self, obj_id, default_value):
result = self.get(obj_id)
if result == None:
logdata = dict()
logdata['object'] = obj_id
log('D43', logdata)
logging.debug(slogm('No value cached for {}'.format(obj_id)))
self.store(obj_id, default_value)
return str(default_value)
return result.value
@ -91,11 +89,9 @@ class sqlite_cache(cache):
try:
self.db_session.add(obj)
self.db_session.commit()
except Exception as exc:
except:
self.db_session.rollback()
logdata = dict()
logdata['msg'] = str(exc)
log('D44', logdata)
logging.error(slogm('Error inserting value into cache, will update the value'))
self.db_session.query(self.mapper_obj).filter(self.mapper_obj.str_id == obj.str_id).update({ 'value': obj.value })
self.db_session.commit()

View File

@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
from sqlalchemy import (
@ -32,7 +33,7 @@ from sqlalchemy.orm import (
sessionmaker
)
from util.logging import log
from util.logging import slogm
from util.paths import cache_dir
from .registry import registry
from .record_types import (
@ -41,8 +42,6 @@ from .record_types import (
, ad_shortcut
, info_entry
, printer_entry
, drive_entry
, folder_entry
)
class sqlite_registry(registry):
@ -62,69 +61,40 @@ class sqlite_registry(registry):
Column('value', String(65536))
)
self.__hklm = Table(
'HKLM'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('hive_key', String(65536), unique=True)
, Column('policy_name', String)
, Column('type', Integer)
, Column('data', String)
'HKLM',
self.__metadata,
Column('id', Integer, primary_key=True),
Column('hive_key', String(65536), unique=True),
Column('type', Integer),
Column('data', String)
)
self.__hkcu = Table(
'HKCU'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('hive_key', String(65536))
, Column('policy_name', String)
, Column('type', Integer)
, Column('data', String)
, UniqueConstraint('sid', 'hive_key')
'HKCU',
self.__metadata,
Column('id', Integer, primary_key=True),
Column('sid', String),
Column('hive_key', String(65536)),
Column('type', Integer),
Column('data', String),
UniqueConstraint('sid', 'hive_key')
)
self.__shortcuts = Table(
'Shortcuts'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('path', String)
, Column('policy_name', String)
, Column('shortcut', String)
, UniqueConstraint('sid', 'path')
'Shortcuts',
self.__metadata,
Column('id', Integer, primary_key=True),
Column('sid', String),
Column('path', String),
Column('shortcut', String),
UniqueConstraint('sid', 'path')
)
self.__printers = Table(
'Printers'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('name', String)
, Column('policy_name', String)
, Column('printer', String)
, UniqueConstraint('sid', 'name')
)
self.__drives = Table(
'Drives'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('login', String)
, Column('password', String)
, Column('dir', String)
, Column('policy_name', String)
, Column('path', String)
, UniqueConstraint('sid', 'dir')
)
self.__folders = Table(
'Folders'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('path', String)
, Column('policy_name', String)
, Column('action', String)
, Column('delete_folder', String)
, Column('delete_sub_folders', String)
, Column('delete_files', String)
, UniqueConstraint('sid', 'path')
'Printers',
self.__metadata,
Column('id', Integer, primary_key=True),
Column('sid', String),
Column('name', String),
Column('printer', String),
UniqueConstraint('sid', 'name')
)
self.__metadata.create_all(self.db_cnt)
Session = sessionmaker(bind=self.db_cnt)
@ -135,8 +105,6 @@ class sqlite_registry(registry):
mapper(samba_hkcu_preg, self.__hkcu)
mapper(ad_shortcut, self.__shortcuts)
mapper(printer_entry, self.__printers)
mapper(drive_entry, self.__drives)
mapper(folder_entry, self.__folders)
except:
pass
#logging.error('Error creating mapper')
@ -153,185 +121,133 @@ class sqlite_registry(registry):
try:
self._add(row)
except:
update_obj = dict({ 'value': row.value })
(self
.db_session.query(info_entry)
.filter(info_entry.name == row.name)
.update(row.update_fields()))
.update(update_obj))
self.db_session.commit()
def _hklm_upsert(self, row):
try:
self._add(row)
except:
update_obj = dict({'type': row.type, 'data': row.data })
(self
.db_session
.query(samba_preg)
.filter(samba_preg.hive_key == row.hive_key)
.update(row.update_fields()))
.update(update_obj))
self.db_session.commit()
def _hkcu_upsert(self, row):
try:
self._add(row)
except Exception as exc:
except:
update_obj = dict({'type': row.type, 'data': row.data })
(self
.db_session
.query(samba_hkcu_preg)
.query(samba_preg)
.filter(samba_hkcu_preg.sid == row.sid)
.filter(samba_hkcu_preg.hive_key == row.hive_key)
.update(row.update_fields()))
.update(update_obj))
self.db_session.commit()
def _shortcut_upsert(self, row):
try:
self._add(row)
except:
update_obj = dict({ 'shortcut': row.shortcut })
(self
.db_session
.query(ad_shortcut)
.filter(ad_shortcut.sid == row.sid)
.filter(ad_shortcut.path == row.path)
.update(row.update_fields()))
.update(update_obj))
self.db_session.commit()
def _printer_upsert(self, row):
try:
self._add(row)
except:
update_obj = dict({ 'printer': row.printer })
(self
.db_session
.query(printer_entry)
.filter(printer_entry.sid == row.sid)
.filter(printer_entry.name == row.name)
.update(row.update_fields()))
self.db_session.commit()
def _drive_upsert(self, row):
try:
self._add(row)
except:
(self
.db_session
.query(drive_entry)
.filter(drive_entry.sid == row.sid)
.filter(drive_entry.dir == row.dir)
.update(row.update_fields()))
.update(update_obj))
self.db_session.commit()
def set_info(self, name, value):
ientry = info_entry(name, value)
logdata = dict()
logdata['varname'] = name
logdata['value'] = value
log('D19', logdata)
logging.debug(slogm('Setting info {}:{}'.format(name, value)))
self._info_upsert(ientry)
def add_hklm_entry(self, preg_entry, policy_name):
def add_hklm_entry(self, preg_entry):
'''
Write PReg entry to HKEY_LOCAL_MACHINE
'''
pentry = samba_preg(preg_entry, policy_name)
pentry = samba_preg(preg_entry)
if not pentry.hive_key.rpartition('\\')[2].startswith('**'):
self._hklm_upsert(pentry)
else:
logdata = dict({'key': pentry.hive_key})
log('D27', logdata)
logging.warning(slogm('Skipping branch deletion key: {}'.format(pentry.hive_key)))
def add_hkcu_entry(self, preg_entry, sid, policy_name):
def add_hkcu_entry(self, preg_entry, sid):
'''
Write PReg entry to HKEY_CURRENT_USER
'''
hkcu_pentry = samba_hkcu_preg(sid, preg_entry, policy_name)
logdata = dict({'sid': sid, 'policy': policy_name, 'key': hkcu_pentry.hive_key})
hkcu_pentry = samba_hkcu_preg(sid, preg_entry)
if not hkcu_pentry.hive_key.rpartition('\\')[2].startswith('**'):
log('D26', logdata)
logging.debug(slogm('Adding HKCU entry for {}'.format(sid)))
self._hkcu_upsert(hkcu_pentry)
else:
log('D51', logdata)
logging.warning(slogm('Skipping branch deletion key: {}'.format(hkcu_pentry.hive_key)))
def add_shortcut(self, sid, sc_obj, policy_name):
def add_shortcut(self, sid, sc_obj):
'''
Store shortcut information in the database
'''
sc_entry = ad_shortcut(sid, sc_obj, policy_name)
logdata = dict()
logdata['link'] = sc_entry.path
logdata['sid'] = sid
log('D41', logdata)
sc_entry = ad_shortcut(sid, sc_obj)
logging.debug(slogm('Saving info about {} link for {}'.format(sc_entry.path, sid)))
self._shortcut_upsert(sc_entry)
def add_printer(self, sid, pobj, policy_name):
def add_printer(self, sid, pobj):
'''
Store printer configuration in the database
'''
prn_entry = printer_entry(sid, pobj, policy_name)
logdata = dict()
logdata['printer'] = prn_entry.name
logdata['sid'] = sid
log('D40', logdata)
prn_entry = printer_entry(sid, pobj)
logging.debug(slogm('Saving info about printer {} for {}'.format(prn_entry.name, sid)))
self._printer_upsert(prn_entry)
def add_drive(self, sid, dobj, policy_name):
drv_entry = drive_entry(sid, dobj, policy_name)
logdata = dict()
logdata['uri'] = drv_entry.path
logdata['sid'] = sid
log('D39', logdata)
self._drive_upsert(drv_entry)
def add_folder(self, sid, fobj, policy_name):
fld_entry = folder_entry(sid, fobj, policy_name)
logdata = dict()
logdata['folder'] = fld_entry.path
logdata['sid'] = sid
log('D42', logdata)
try:
self._add(fld_entry)
except Exception as exc:
(self
._filter_sid_obj(folder_entry, sid)
.filter(folder_entry.path == fld_entry.path)
.update(fld_entry.update_fields()))
self.db_session.commit()
def _filter_sid_obj(self, row_object, sid):
def get_shortcuts(self, sid):
res = (self
.db_session
.query(row_object)
.filter(row_object.sid == sid))
return res
def _filter_sid_list(self, row_object, sid):
res = (self
.db_session
.query(row_object)
.filter(row_object.sid == sid)
.query(ad_shortcut)
.filter(ad_shortcut.sid == sid)
.all())
return res
def get_shortcuts(self, sid):
return self._filter_sid_list(ad_shortcut, sid)
def get_printers(self, sid):
return self._filter_sid_list(printer_entry, sid)
def get_drives(self, sid):
return self._filter_sid_list(drive_entry, sid)
def get_folders(self, sid):
return self._filter_sid_list(folder_entry, sid)
res = (self
.db_session
.query(printer_entry)
.filter(printer_entry.sid == sid)
.all())
return res
def get_hkcu_entry(self, sid, hive_key):
res = (self
.db_session
.query(samba_hkcu_preg)
.query(samba_preg)
.filter(samba_hkcu_preg.sid == sid)
.filter(samba_hkcu_preg.hive_key == hive_key)
.first())
# Try to get the value from machine SID as a default if no option is set.
if not res:
machine_sid = self.get_info('machine_sid')
res = self.db_session.query(samba_hkcu_preg).filter(samba_hkcu_preg.sid == machine_sid).filter(samba_hkcu_preg.hive_key == hive_key).first()
res = self.db_session.query(samba_preg).filter(samba_hkcu_preg.sid == machine_sid).filter(samba_hkcu_preg.hive_key == hive_key).first()
return res
def filter_hkcu_entries(self, sid, startswith):
@ -366,16 +282,31 @@ class sqlite_registry(registry):
return res
def wipe_user(self, sid):
self._wipe_sid(samba_hkcu_preg, sid)
self._wipe_sid(ad_shortcut, sid)
self._wipe_sid(printer_entry, sid)
self._wipe_sid(drive_entry, sid)
self.wipe_hkcu(sid)
self.wipe_shortcuts(sid)
self.wipe_printers(sid)
def _wipe_sid(self, row_object, sid):
def wipe_shortcuts(self, sid):
(self
.db_session
.query(row_object)
.filter(row_object.sid == sid)
.query(ad_shortcut)
.filter(ad_shortcut.sid == sid)
.delete())
self.db_session.commit()
def wipe_printers(self, sid):
(self
.db_session
.query(printer_entry)
.filter(printer_entry.sid == sid)
.delete())
self.db_session.commit()
def wipe_hkcu(self, sid):
(self
.db_session
.query(samba_hkcu_preg)
.filter(samba_hkcu_preg.sid == sid)
.delete())
self.db_session.commit()

View File

@ -1,29 +0,0 @@
{#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% if Deny_All == '1' %}
polkit.addRule(function (action, subject) {
if ((action.id == "org.freedesktop.udisks2.filesystem-mount" ||
action.id == "org.freedesktop.udisks2.filesystem-mount-system" ||
action.id == "org.freedesktop.udisks2.filesystem-mount-other-seat") &&
subject.user == "{{User}}" ) {
return polkit.Result.NO;
}
});
{% endif %}

View File

@ -1,20 +0,0 @@
{#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{{ home_dir }}/net {{ mount_file }} -t 120

View File

@ -1,25 +0,0 @@
{#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% if login %}
username={{ login }}
{% endif %}
{% if password %}
password={{ password }}
{% endif %}

View File

@ -1,21 +0,0 @@
{#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{%- for drv in drives %}
{{ drv.dir }} -fstype=cifs,cruid=$USER,sec=krb5,noperm :{{ drv.path }}
{% endfor %}

View File

@ -1,39 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from frontend.appliers.rpm import rpm
class PackageTestCase(unittest.TestCase):
'''
Semi-integrational tests for packages installation/removing
'''
def test_package_not_exist(self):
packages_for_install = 'dummy1 dummy2'
packages_for_remove = 'dummy3'
test_rpm = rpm(packages_for_install, packages_for_remove)
test_rpm.apply()
def test_install_remove_same_package(self):
packages_for_install = 'gotop'
packages_for_remove = 'gotop'
test_rpm = rpm(packages_for_install, packages_for_remove)
test_rpm.apply()

View File

@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Printers clsid="{1F577D12-3D1B-471e-A1B7-060317597B9C}"><PortPrinter clsid="{C3A739D2-4A44-401e-9F9D-88E5E77DFB3E}" name="10.64.128.250" status="10.64.128.250" image="0" changed="2020-01-23 11:48:07" uid="{88D998C2-9875-4278-A607-EC828839EFCE}" userContext="1" bypassErrors="1"><Properties lprQueue="" snmpCommunity="public" protocol="PROTOCOL_RAWTCP_TYPE" portNumber="9100" doubleSpool="0" snmpEnabled="0" snmpDevIndex="1" ipAddress="10.64.128.250" action="C" location="" localName="printer" comment="" default="1" skipLocal="0" useDNS="0" path="\\prnt" deleteAll="0"/><Filters><FilterGroup bool="AND" not="0" name="DOMAIN\Domain Users" sid="S-1-5-21-3359553909-270469630-9462315-513" userContext="1" primaryGroup="0" localGroup="0"/></Filters></PortPrinter>
</Printers>

Binary file not shown.

View File

@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><TaskV2 clsid="{D8896631-B747-47a7-84A6-C155337F3BC8}" name="mytask" image="2" changed="2020-01-24 13:06:25" uid="{0DBF3CAA-3DCF-4AAA-A52F-82B010B35380}"><Properties action="U" name="mytask" runAs="%LogonDomain%\%LogonUser%" logonType="InteractiveToken"><Task version="1.3"><RegistrationInfo><Author>DOMAIN\samba</Author><Description></Description></RegistrationInfo><Principals><Principal id="Author"><UserId>%LogonDomain%\%LogonUser%</UserId><LogonType>InteractiveToken</LogonType><RunLevel>HighestAvailable</RunLevel></Principal></Principals><Settings><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>true</AllowHardTerminate><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><ExecutionTimeLimit>P3D</ExecutionTimeLimit><Priority>7</Priority></Settings><Triggers><CalendarTrigger><StartBoundary>2020-01-24T14:59:48</StartBoundary><Enabled>true</Enabled><ScheduleByDay><DaysInterval>1</DaysInterval></ScheduleByDay></CalendarTrigger></Triggers><Actions><Exec><Command>C:\Program Files (x86)\Google\Chrome\Application\chrome.exe</Command></Exec></Actions></Task></Properties></TaskV2>
</ScheduledTasks>

View File

@ -1,42 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
import unittest.mock
import os
import util.paths
import json
class GptDrivesTestCase(unittest.TestCase):
@unittest.mock.patch('util.paths.cache_dir')
def test_drive_reader(self, cdir_mock):
'''
Test functionality to read objects from Shortcuts.xml
'''
cdir_mock.return_value = '/var/cache/gpupdate'
import gpt.drives
testdata_path = '{}/test/gpt/data/Drives.xml'.format(os.getcwd())
drvs = gpt.drives.read_drives(testdata_path)
json_obj = json.loads(drvs[0].to_json())
self.assertIsNotNone(json_obj['drive'])

View File

@ -1,32 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from util.xdg import (
xdg_get_desktop_user
)
class XDGTestCase(unittest.TestCase):
def test_get_desktop_dir(self):
print('Machine desktop:')
print(xdg_get_desktop_user(None))
print('Users desktop:')
print(xdg_get_desktop_user('nir'))

View File

@ -20,7 +20,6 @@ import logging
import logging.handlers
from enum import IntEnum
from messages import message_with_code
from .logging import slogm
@ -44,6 +43,7 @@ def set_loglevel(loglevel_num=None):
log_level = 10 * log_num
print('Setting log level to {}'.format(loglevels[log_num]))
logging.basicConfig(format=format_message)
logger = logging.getLogger()
logger.setLevel(log_level)
@ -72,8 +72,7 @@ def process_target(target_name=None):
if target_name == 'User':
target = 'User'
logdata = dict({'target': target})
logging.debug(slogm(message_with_code('D10'), logdata))
logging.debug(slogm('Target is: {}'.format(target)))
return target

View File

@ -1,81 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from configparser import ConfigParser
from .util import (
get_backends
, get_default_policy_name
)
class GPConfig:
__config_path = '/etc/gpupdate/gpupdate.ini'
def __init__(self, config_path=None):
if config_path:
self.__config_path = config_path
self.full_config = ConfigParser()
self.full_config.read(self.__config_path)
def get_backend(self):
'''
Fetch the name of the backend from configuration file.
'''
if 'gpoa' in self.full_config:
if 'backend' in self.full_config['gpoa']:
if self.full_config['gpoa']['backend'] in get_backends():
return self.full_config['gpoa']['backend']
return 'samba'
def set_backend(self, backend_name):
self.full_config['gpoa']['backend'] = backend_name
self.write_config()
# This function is not expected corresponding "set_dc()" function
# because we have no way to automatically determine such kind
# of setting.
def get_dc(self):
'''
Fetch Domain Controller from configuration file.
'''
if 'samba' in self.full_config:
if 'dc' in self.full_config['samba']:
return self.full_config['samba']['dc']
def get_local_policy_template(self):
'''
Fetch the name of chosen Local Policy template from
configuration file.
'''
if 'gpoa' in self.full_config:
if 'local-policy' in self.full_config['gpoa']:
return self.full_config['gpoa']['local-policy']
return get_default_policy_name()
def set_local_policy_template(self, template_name):
self.full_config['gpoa']['local-policy'] = template_name
self.write_config()
def write_config(self):
with open(self.__config_path, 'w') as config_file:
self.full_config.write(config_file)

View File

@ -16,9 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import dbus
from .logging import log
from .logging import slogm
from .users import is_root
@ -29,76 +30,41 @@ class dbus_runner:
'''
_bus_name = 'com.redhat.oddjob_gpupdate'
# Interface name is equal to bus name.
_interface_name = 'com.redhat.oddjob_gpupdate'
_object_path = '/'
# The timeout is in milliseconds. The default is -1 which is
# DBUS_TIMEOUT_USE_DEFAULT which is 25 seconds. There is also
# DBUS_TIMEOUT_INFINITE constant which is equal to INT32_MAX or
# 0x7ffffff (largest 32-bit integer).
#
# It was decided to set the timeout to 10 minutes which must be
# sufficient to replicate and apply all recognizable GPOs.
_synchronous_timeout = 600000
def __init__(self, username=None):
self.username = username
self.system_bus = dbus.SystemBus()
system_bus = dbus.SystemBus()
obj = system_bus.get_object(self._bus_name, self._object_path)
self.interface = dbus.Interface(obj, self._bus_name)
def run(self):
#print(obj.Introspect()[0])
if self.username:
logdata = dict({'username': self.username})
log('D6', logdata)
logging.info(slogm('Starting GPO applier for user {} via D-Bus'.format(self.username)))
if is_root():
# oddjobd-gpupdate's ACL allows access to this method
# only for superuser. This method is called via PAM
# when user logs in.
try:
result = self.system_bus.call_blocking(self._bus_name,
self._object_path,
self._interface_name,
'gpupdatefor',
(username),
(dbus.String(self.username)),
timeout=self._synchronous_timeout)
result = self.interface.gpupdatefor(dbus.String(self.username))
print_dbus_result(result)
except dbus.exceptions.DBusException as exc:
logdata = dict()
logdata['username'] = self.username
log('E23', logdata)
logging.error(slogm('No reply from oddjobd gpoa runner for {}'.format(self.username)))
raise exc
else:
try:
result = self.system_bus.call_blocking(self._bus_name,
self._object_path,
self._interface_name,
'gpupdate',
None,
(),
timeout=self._synchronous_timeout)
result = self.interface.gpupdate()
print_dbus_result(result)
except dbus.exceptions.DBusException as exc:
logdata = dict({'error': str(exc)})
log('E21', logdata)
logging.error(slogm('No reply from oddjobd gpoa runner for current user'))
raise exc
else:
log('D11')
logging.info(slogm('Starting GPO applier for computer via D-Bus'))
try:
result = self.system_bus.call_blocking(self._bus_name,
self._object_path,
self._interface_name,
'gpupdate_computer',
None,
# The following positional parameter is called "args".
# There is no official documentation for it.
(),
timeout=self._synchronous_timeout)
result = self.interface.gpupdate_computer()
print_dbus_result(result)
except dbus.exceptions.DBusException as exc:
print(exc)
logdata = dict({'error': str(exc)})
log('E22', logdata)
logging.error(slogm('No reply from oddjobd gpoa runner for computer'))
raise exc
#self.interface.Quit()
def start_gpupdate_user():
@ -164,8 +130,7 @@ def print_dbus_result(result):
'''
exitcode = result[0]
message = result[1:]
logdata = dict({'retcode': exitcode})
log('D12', logdata)
logging.debug(slogm('Exit code is {}'.format(exitcode)))
for line in message:
print(str(line))

View File

@ -1,41 +0,0 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
def geterr():
'''
Fetches information about recent exception so we will be able
to print tracebacks and other information in a uniform way.
'''
etype, evalue, etrace = sys.exc_info()
traceinfo = dict({
'file': etrace.tb_frame.f_code.co_filename
, 'line': etrace.tb_lineno
, 'name': etrace.tb_frame.f_code.co_name
, 'type': etype.__name__
, 'message': evalue
})
del(etype, evalue, etrace)
return traceinfo

View File

@ -16,54 +16,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import logging
import subprocess
from .util import get_machine_name
from .logging import log
from .logging import slogm
def machine_kinit(cache_name=None):
def machine_kinit():
'''
Perform kinit with machine credentials
'''
host = get_machine_name()
os.environ['KRB5CCNAME'] = 'FILE:{}'.format(cache_name)
kinit_cmd = ['kinit', '-k', host]
if cache_name:
kinit_cmd.extend(['-c', cache_name])
proc = subprocess.Popen(kinit_cmd)
proc.wait()
result = False
if 0 == proc.returncode:
result = True
if result:
result = check_krb_ticket()
return result
def machine_kdestroy(cache_name=None):
'''
Perform kdestroy for machine credentials
'''
host = get_machine_name()
kdestroy_cmd = ['kdestroy']
if cache_name:
kdestroy_cmd.extend(['-c', cache_name])
proc = subprocess.Popen(kdestroy_cmd, stderr=subprocess.DEVNULL)
proc.wait()
if cache_name and os.path.exists(cache_name):
os.unlink(cache_name)
elif 'KRB5CCNAME' in os.environ:
path = os.environ['KRB5CCNAME'][5:]
if os.path.exists(path):
os.unlink(path)
subprocess.call(['kinit', '-k', host])
return check_krb_ticket()
def check_krb_ticket():
@ -74,13 +40,11 @@ def check_krb_ticket():
try:
subprocess.check_call(['klist', '-s'])
output = subprocess.check_output('klist', stderr=subprocess.STDOUT).decode()
logging.info(output)
result = True
logdata = dict()
logdata['output'] = output
log('D17', logdata)
except Exception as exc:
logdata = dict()
logdata['krb-exc'] = exc
log('E14', logdata)
except:
logging.error(slogm('Kerberos ticket check unsuccessful'))
logging.debug(slogm('Ticket check succeed'))
return result

View File

@ -18,15 +18,11 @@
import json
import datetime
import logging
from messages import message_with_code
class encoder(json.JSONEncoder):
def default(self, obj):
result = super(encoder, self)
result = result.default(obj)
result = super(encoder, self).default(obj)
if isinstance(obj, set):
result = tuple(obj)
@ -40,46 +36,19 @@ class slogm(object):
'''
Structured log message class
'''
def __init__(self, message, kwargs=dict()):
def __init__(self, message, **kwargs):
self.message = message
self.kwargs = kwargs
if not self.kwargs:
self.kwargs = dict()
def __str__(self):
now = str(datetime.datetime.now().isoformat(sep=' ', timespec='milliseconds'))
now = str(datetime.datetime.now())
args = dict()
#args.update(dict({'timestamp': now, 'message': str(self.message)}))
args.update(dict({'timestamp': now, 'message': str(self.message)}))
args.update(self.kwargs)
kwa = dict()
try:
kwa = encoder().encode(args)
except Exception as exc:
pass
kwa = encoder().encode(args)
result = '{}|{}|{}'.format(now, self.message, kwa)
result = '{}:{}'.format(now.rpartition('.')[0], self.message)
return result
def log(message_code, data=None):
mtype = message_code[0]
if 'I' == mtype:
logging.info(slogm(message_with_code(message_code), data))
return
if 'W' == mtype:
logging.warning(slogm(message_with_code(message_code), data))
return
if 'E' == mtype:
logging.error(slogm(message_with_code(message_code), data))
return
if 'F' == mtype:
logging.fatal(slogm(message_with_code(message_code), data))
return
if 'D' == mtype:
logging.debug(slogm(message_with_code(message_code), data))
return
logging.error(slogm(message_with_code(message_code), data))

View File

@ -19,20 +19,18 @@
import pathlib
import os
from .config import GPConfig
def default_policy_path():
'''
Returns path pointing to Default Policy directory.
'''
local_policy_default = '/usr/share/local-policy/default'
config = GPConfig()
etc_local_policy_default = '/etc/local-policy/active'
result_path = pathlib.Path(local_policy_default)
if os.path.exists(config.get_local_policy_template()):
result_path = pathlib.Path(config.get_local_policy_template())
if os.path.exists(etc_local_policy_default):
result_path = pathlib.Path(etc_local_policy_default)
return pathlib.Path(result_path)
@ -61,3 +59,31 @@ def local_policy_cache():
return lpcache
def backend_module_dir():
backend_dir = '/usr/lib/gpoa/backend'
return pathlib.Path(backend_dir)
def frontend_module_dir():
frontend_dir = '/usr/lib/gpoa/frontend'
return pathlib.Path(frontend_dir)
def storage_module_dir():
storage_dir = '/usr/lib/gpoa/storage'
return pathlib.Path(storage_dir)
def pre_backend_plugin_dir():
pre_backend_dir = '/usr/lib/gpoa/backend_pre'
return pathlib.Path(pre_backend_dir)
def post_backend_plugin_dir():
post_backend_dir = '/usr/lib/gpoa/backend_post'
return pathlib.Path(post_backend_dir)
def pre_frontend_plugin_dir():
pre_forntend_dir = '/usr/lib/gpoa/frontend_pre'
return pathlib.Path(pre_frontend_dir)
def post_frontend_plugin_dir():
post_frontend_dir = '/usr/lib/gpoa/frontend_post'
return pathlib.Path(post_frontend_dir)

View File

@ -16,13 +16,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from xml.etree import ElementTree
from storage import registry_factory
from samba.gp_parse.gp_pol import GPPolParser
from .logging import log
from .logging import slogm
def load_preg(file_path):
@ -39,8 +40,7 @@ def load_xml_preg(xml_path):
'''
Parse XML/PReg file and return its preg object
'''
logdata = dict({'polfile': xml_path})
log('D36', logdata)
logging.debug('Loading PReg from XML: {}'.format(xml_path))
gpparser = GPPolParser()
xml_root = ElementTree.parse(xml_path).getroot()
gpparser.load_xml(xml_root)
@ -53,20 +53,16 @@ def load_pol_preg(polfile):
'''
Parse PReg file and return its preg object
'''
logdata = dict({'polfile': polfile})
log('D31', logdata)
logging.debug(slogm('Loading PReg from .pol file: {}'.format(polfile)))
gpparser = GPPolParser()
data = None
with open(polfile, 'rb') as f:
data = f.read()
logdata = dict({'polfile': polfile, 'length': len(data)})
log('D33', logdata)
gpparser.parse(data)
#print(gpparser.pol_file.__ndr_print__())
pentries = preg2entries(gpparser.pol_file)
return pentries
return gpparser.pol_file
def preg_keymap(preg):
@ -80,16 +76,15 @@ def preg_keymap(preg):
return keymap
def merge_polfile(preg, sid=None, reg_name='registry', reg_path=None, policy_name='Unknown'):
def merge_polfile(preg, sid=None, reg_name='registry', reg_path=None):
pregfile = load_preg(preg)
logdata = dict({'pregfile': preg})
log('D32', logdata)
logging.info(slogm('Loaded PReg {}'.format(preg)))
storage = registry_factory(reg_name, reg_path)
for entry in pregfile.entries:
if not sid:
storage.add_hklm_entry(entry, policy_name)
storage.add_hklm_entry(entry)
else:
storage.add_hkcu_entry(entry, sid, policy_name)
storage.add_hkcu_entry(entry, sid)
class entry:
@ -98,22 +93,12 @@ class entry:
self.valuename = e_valuename
self.type = e_type
self.data = e_data
logdata = dict()
logdata['keyname'] = self.keyname
logdata['valuename'] = self.valuename
logdata['type'] = self.type
logdata['data'] = self.data
log('D22', logdata)
class pentries:
def __init__(self):
self.entries = list()
def preg2entries(preg_obj):
entries = pentries()
for elem in preg_obj.entries:
entries = []
for elem in prej_obj.entries:
entry_obj = entry(elem.keyname, elem.valuename, elem.type, elem.data)
entries.entries.append(entry_obj)
entries.append(entry_obj)
return entries

View File

@ -34,11 +34,11 @@ def is_rpm_installed(rpm_name):
return False
class Package:
__install_command = ['/usr/bin/apt-get', '-y', 'install']
__remove_command = ['/usr/bin/apt-get', '-y', 'remove']
__reinstall_command = ['/usr/bin/apt-get', '-y', 'reinstall']
def __init__(self, package_name):
self.__install_command = ['/usr/bin/apt-get', '-y', 'install']
self.__remove_command = ['/usr/bin/apt-get', '-y', 'remove']
self.__reinstall_command = ['/usr/bin/apt-get', '-y', 'reinstall']
self.package_name = package_name
self.for_install = True
@ -102,6 +102,7 @@ def install_rpm(rpm_name):
'''
Install single RPM
'''
update()
rpm = Package(rpm_name)
return rpm.install()

View File

@ -1,245 +0,0 @@
#! /usr/bin/env python3
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2020 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
def wbinfo_getsid(domain, user):
'''
Get SID using wbinfo
'''
# This part works only on client
username = '{}\\{}'.format(domain.upper(), user)
sid = pysss_nss_idmap.getsidbyname(username)
if username in sid:
return sid[username]['sid']
# This part works only on DC
wbinfo_cmd = ['wbinfo', '-n', username]
output = subprocess.check_output(wbinfo_cmd)
sid = output.split()[0].decode('utf-8')
return sid
def get_sid(domain, username):
'''
Lookup SID not only using wbinfo or sssd but also using own cache
'''
domain_username = '{}\\{}'.format(domain, username)
sid = 'local-{}'.format(username)
try:
sid = wbinfo_getsid(domain, username)
except:
sid = 'local-{}'.format(username)
logging.warning(
slogm('Error getting SID using wbinfo, will use cached SID: {}'.format(sid)))
logging.debug(slogm('Working with SID: {}'.format(sid)))
return sid
class IssuingAuthority(Enum):
SECURITY_NULL_SID_AUTHORITY = 0
SECURITY_WORLD_SID_AUTHORITY = 1
SECURITY_LOCAL_SID_AUTHORITY = 2
SECURITY_CREATOR_SID_AUTHORITY = 3
SECURITY_NON_UNIQUE_AUTHORITY = 4
SECURITY_NT_AUTHORITY = 5
SECURITY_RESOURCE_MANAGER_AUTHORITY = 9
class SidRevision(Enum):
FIRST = 1
# This thing exists only after "S-1-5-21-"
# Last part of full SID
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab
class WellKnown21RID(Enum):
ENTERPRISE_READONLY_DOMAIN_CONTROLLERS = 498
ADMINISTRATOR = 500 # For machine
GUEST = 501 # For machine
KRBTGT = 502
DOMAIN_ADMINS = 512
DOMAIN_USERS = 513
DOMAIN_GUESTS = 514
DOMAIN_COMPUTERS = 515
DOMAIN_CONTROLLERS = 516
CERT_PUBLISHERS = 517
SCHEMA_ADMINISTRATORS = 518 # For root domain
ENTERPRISE_ADMINS = 519 # For root domain
GROUP_POLICY_CREATOR_OWNERS = 520
READONLY_DOMAIN_CONTROLLERS = 521
CLONEABLE_CONTROLLERS = 522
PROTECTED_USERS = 525
KEY_ADMINS = 526
ENTERPRISE_KEY_ADMINS = 527
RAS_SERVERS = 553
ALLOWED_RODC_PASSWORD_REPLICATION_GROUP = 571
DENIED_RODC_PASSWORD_REPLICATION_GROUP = 572
# This thing exists only after "S-1-5-32-"
# Last part of full SID
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab
class WellKnown32RID(Enum):
BUILTIN_ADMINISTRATORS = 544
BUILTIN_USERS = 545
BUILTIN_GUESTS = 546
POWER_USERS = 547
ACCOUNT_OPERATORS = 548
SERVER_OPERATORS = 549
PRINTER_OPERATORS = 550
BACKUP_OPERATORS = 551
REPLICATOR = 552
ALIAS_PREW2KCOMPACC = 554
REMOTE_DESKTOP = 555
NETWORK_CONFIGURATION_OPS = 556
INCOMING_FOREST_TRUST_BUILDERS = 557
PERFMON_USERS = 558
PERFLOG_USERS = 559
WINDOWS_AUTHORIZATION_ACCESS_GROUP = 560
TERMINAL_SERVER_LICENSE_SERVERS = 561
DISTRIBUTED_COM_USERS = 562
IIS_IUSRS = 568
CRYPTOGRAPHIC_OPERATORS = 569
EVENT_LOG_READERS = 573
CERTIFICATE_SERVICE_DCOM_ACCESS = 574
RDS_REMOTE_ACCESS_SERVERS = 575
RDS_ENDPOINT_SERVERS = 576
RDS_MANAGEMENT_SERVERS = 577
HYPER_V_ADMINS = 578
ACCESS_CONTROL_ASSISTANCE_OPS = 579
REMOTE_MANAGEMENT_USERS = 580
# This thing exists only after "S-1-5-"
class FirstSubAuthority(Enum):
SECURITY_DIALUP_RID = 1
SECURITY_NETWORK_RID = 2
SECURITY_BATCH_RID = 3
SECURITY_INTERACTIVE_RID = 4
SECURITY_LOGON_IDS_RID = 5
SECURITY_SERVICE_RID = 6
SECURITY_ANONYMOUS_LOGON_RID = 7
SECURITY_PROXY_RID = 8
SECURITY_ENTERPRISE_CONTROLLERS_RID = 9
SECURITY_PRINCIPAL_SELF_RID = 10
SECURITY_AUTHENTICATED_USER_RID = 11
SECURITY_RESTRICTED_CODE_RID = 12
SECURITY_TERMINAL_SERVER_RID = 13
SECURITY_LOCAL_SYSTEM_RID = 18
SECURITY_NT_NON_UNIQUE = 21
SECURITY_BUILTIN_DOMAIN_RID = 32
SECURITY_WRITE_RESTRICTED_CODE_RID = 33
class SecondSubAuthority(Enum):
DOMAIN_ALIAS_RID_ADMINS = 544
def validate_issuing_authority(ia_num):
ia_value = None
ia_value = int(IssuingAuthority(ia_num))
return ia_value
def validate_sid_revision(revnum):
rev_value = None
rev_value = int(SidRevision(revnum))
return rev_value
def is_sid(sid):
# Check that SID is SID (S)
if not sid[0] == 'S':
return False
# Check revision version (1 for Windows-generated SID) (R)
if not validate_sid_revision(int(sid[2])):
return False
# Check issuing authority (IA)
issuing_authority = validate_issuing_authority(int(sid[4]))
if not issuing_authority:
return False
if issuing_authority == 21:
pass
elif issuing_authority == 32:
pass
else:
pass
def sid2descr(sid):
sids = dict()
sids['S-1-0'] = 'Null Authority'
sids['S-1-0-0'] = 'Nobody'
sids['S-1-1'] = 'World Authority'
sids['S-1-1-0'] = 'Everyone'
sids['S-1-2'] = 'Local Authority'
sids['S-1-2-0'] = 'Local'
sids['S-1-3'] = 'Creator Authority'
sids['S-1-3-0'] = 'Creator Owner'
sids['S-1-3-1'] = 'Creator Group'
sids['S-1-3-2'] = 'Creator Owner Server' # Since Windows 2003
sids['S-1-3-3'] = 'Creator Group Server' # Since Windows 2003
sids['S-1-3-4'] = 'Owner Rights'
sids['S-1-4'] = 'Non-unique Authority'
sids['S-1-5'] = 'NT Authority'
sids['S-1-5-1'] = 'Dialup'
sids['S-1-5-2'] = 'Network'
sids['S-1-5-3'] = 'Batch'
sids['S-1-5-4'] = 'Interactive'
sids['S-1-5-6'] = 'Service'
sids['S-1-5-7'] = 'Anonymous'
sids['S-1-5-8'] = 'Proxy' # Since Windows 2003
sids['S-1-5-9'] = 'Enterprise Domain Controllers'
sids['S-1-5-10'] = 'Principal Self'
sids['S-1-5-11'] = 'Authenticated Users'
sids['S-1-5-12'] = 'Restricted Code'
sids['S-1-5-13'] = 'Terminal Server Users'
sids['S-1-5-14'] = 'Remote Interactive Logon'
sids['S-1-5-15'] = 'This Organization' # Since Windows 2003
sids['S-1-5-17'] = 'This Organization'
sids['S-1-5-18'] = 'Local System'
sids['S-1-5-19'] = 'NT Authority' # Local Service
sids['S-1-5-20'] = 'NT Authority' # Network Service
sids['S-1-5-32-544'] = 'Administrators'
sids['S-1-5-32-545'] = 'Users'
sids['S-1-5-32-546'] = 'Guests'
sids['S-1-5-32-547'] = 'Power Users'
sids['S-1-5-32-548'] = 'Account Operators'
sids['S-1-5-32-549'] = 'Server Operators'
sids['S-1-5-32-550'] = 'Print Operators'
sids['S-1-5-32-551'] = 'Backup Operators'
sids['S-1-5-32-552'] = 'Replicators'
sids['S-1-5-32-554'] = 'Builtin\\Pre-Windows 2000 Compatible Access' # Since Windows 2003
sids['S-1-5-32-555'] = 'Builtin\\Remote Desktop Users' # Since Windows 2003
sids['S-1-5-32-556'] = 'Builtin\\Network Configuration Operators' # Since Windows 2003
sids['S-1-5-32-557'] = 'Builtin\\Incoming Forest Trust Builders' # Since Windows 2003
sids['S-1-5-32-558'] = 'Builtin\\Performance Monitor Users' # Since Windows 2003
sids['S-1-5-32-582'] = 'Storage Replica Administrators'
sids['S-1-5-64-10'] = 'NTLM Authentication'
sids['S-1-5-64-14'] = 'SChannel Authentication'
sids['S-1-5-64-21'] = 'Digest Authentication'
sids['S-1-5-80'] = 'NT Service'
return sids.get(sid, None)

View File

@ -16,20 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import signal
from sys import exit
from .arguments import ExitCodeUpdater
from .kerberos import machine_kdestroy
default_handler = signal.getsignal(signal.SIGINT)
def signal_handler(sig_number, frame):
print('Received signal, exiting gracefully')
# Ignore extra signals
signal.signal(sig_number, signal.SIG_IGN)
# Kerberos cache cleanup on interrupt
machine_kdestroy()
os._exit(ExitCodeUpdater.EXIT_SIGINT)
print('Received signal, exiting gracefully')
exit(ExitCodeUpdater.EXIT_SIGINT)

View File

@ -18,8 +18,9 @@
import os
import pwd
import logging
from .logging import log
from .logging import slogm
def is_root():
@ -74,11 +75,9 @@ def set_privileges(username, uid, gid, groups=list()):
#except Exception as exc:
# print('setgroups')
logdata = dict()
logdata['uid'] = uid
logdata['gid'] = gid
logdata['username'] = username
log('D37', logdata)
logging.debug(
slogm('Set process permissions to UID {} and GID {} for user {}'.format(
uid, gid, username)))
def with_privileges(username, func):
@ -100,14 +99,11 @@ def with_privileges(username, func):
# We need to catch exception in order to be able to restore
# privileges later in this function
out = None
try:
out = func()
func()
except Exception as exc:
raise exc
logging.debug(slogm(exc))
# Restore privileges
set_privileges('root', current_uid, 0, current_groups)
return out

View File

@ -20,9 +20,7 @@
import socket
import os
import pwd
import subprocess
from pathlib import Path
from .samba import smbopts
def get_machine_name():
@ -85,95 +83,3 @@ def mk_homedir_path(username, homedir_path):
os.chown(homedir, uid=uid, gid=gid)
longer_path = os.path.join(longer_path, elem)
def runcmd(command_name):
'''
Run application.
'''
try:
with subprocess.Popen(command_name, stdout=subprocess.PIPE) as proc:
value = proc.stdout.read().decode('utf-8')
proc.wait()
rc = proc.returncode
return (rc, value)
except Exception as exc:
print(str(exc))
def get_backends():
'''
Get the list of backends supported by GPOA
'''
command = ['/usr/sbin/gpoa', '--list-backends']
backends = list()
out = list()
with subprocess.Popen(command, stdout=subprocess.PIPE) as proc:
out = proc.stdout.read().decode('utf-8')
proc.wait()
out = out.split('\n')
for line in out:
tmpline = line.replace('\n', '')
if tmpline != '':
backends.append(tmpline)
return backends
def get_default_policy_name():
'''
Determine the preferred Local Policy template name according to
ALT distribution type
'''
localpolicy = 'workstation'
dcpolicy = 'ad-domain-controller'
try:
if smbopts().get_server_role() == 'active directory domain controller':
return dcpolicy
except:
pass
try:
release = '/etc/altlinux-release'
if os.path.isfile(release):
f = open(release)
s = f.readline()
if re.search('server', s, re.I):
localpolicy = 'server'
except:
pass
return localpolicy
def get_policy_entries(directory):
'''
Get list of directories representing "Local Policy" templates.
'''
filtered_entries = list()
if os.path.isdir(directory):
entries = [os.path.join(directory, entry) for entry in os.listdir(directory)]
for entry in entries:
if os.path.isdir(os.path.join(entry)):
if not os.path.islink(os.path.join(entry)):
if not entry.rpartition('/')[2] == 'default':
filtered_entries.append(entry)
return filtered_entries
def get_policy_variants():
'''
Get the list of local policy variants deployed on this system.
Please note that is case overlapping names the names in
/etc/local-policy must override names in /usr/share/local-policy
'''
policy_dir = '/usr/share/local-policy'
etc_policy_dir = '/etc/local-policy'
system_policies = get_policy_entries(policy_dir)
user_policies = get_policy_entries(etc_policy_dir)
general_listing = list()
general_listing.extend(system_policies)
general_listing.extend(user_policies)
return general_listing

View File

@ -17,6 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
import pwd
@ -28,12 +29,9 @@ import samba.gpo
import pysss_nss_idmap
from storage import cache_factory
from messages import message_with_code
from .xdg import (
xdg_get_desktop
)
from .xdg import get_user_dir
from .util import get_homedir
from .logging import log
from .logging import slogm
from .samba import smbopts
@ -43,7 +41,7 @@ class smbcreds (smbopts):
smbopts.__init__(self, 'GPO Applier')
self.credopts = options.CredentialsOptions(self.parser)
self.creds = self.credopts.get_credentials(self.lp, fallback_machine=True)
self.set_dc(dc_fqdn)
self.selected_dc = self.set_dc(dc_fqdn)
def get_dc(self):
return self.selected_dc
@ -55,19 +53,21 @@ class smbcreds (smbopts):
self.selected_dc = None
try:
if dc_fqdn is not None:
logdata = dict()
logdata['user_dc'] = dc_fqdn
log('D38', logdata)
samba_dc = get_dc_hostname(self.creds, self.lp)
if samba_dc != dc_fqdn and dc_fqdn is not None:
logging.debug(
slogm('Samba DC setting is {} and is overwritten by user setting {}'.format(
samba_dc, dc)))
self.selected_dc = dc_fqdn
else:
self.selected_dc = get_dc_hostname(self.creds, self.lp)
except Exception as exc:
logdata = dict()
logdata['msg'] = str(exc)
log('E10', logdata)
raise exc
self.selected_dc = samba_dc
except:
logging.error(slogm('Unable to determine DC hostname'))
return self.selected_dc
def get_domain(self):
'''
@ -79,11 +79,9 @@ class smbcreds (smbopts):
# Look and python/samba/netcmd/domain.py for more examples
res = netcmd_get_domain_infos_via_cldap(self.lp, None, self.selected_dc)
dns_domainname = res.dns_domain
logdata = dict({'domain': dns_domainname})
log('D18', logdata)
except Exception as exc:
log('E15')
raise exc
logging.info(slogm('Found domain via CLDAP: {}'.format(dns_domainname)))
except:
logging.error(slogm('Unable to retrieve domain name via CLDAP query'))
return dns_domainname
@ -95,22 +93,19 @@ class smbcreds (smbopts):
gpos = list()
try:
log('D48')
ads = samba.gpo.ADS_STRUCT(self.selected_dc, self.lp, self.creds)
if ads.connect():
log('D47')
gpos = ads.get_gpo_list(username)
logdata = dict({'username': username})
log('I1', logdata)
logging.info(slogm('Got GPO list for {}:'.format(username)))
for gpo in gpos:
# These setters are taken from libgpo/pygpo.c
# print(gpo.ds_path) # LDAP entry
ldata = dict({'gpo_name': gpo.display_name, 'gpo_uuid': gpo.name})
log('I2', ldata)
logging.info(slogm('GPO: {} ({})'.format(gpo.display_name, gpo.name)))
except Exception as exc:
logdata = dict({'username': username, 'dc': self.selected_dc})
log('E17', logdata)
logging.error(
slogm('Unable to get GPO list for {} from {}'.format(
username, self.selected_dc)))
return gpos
@ -118,16 +113,11 @@ class smbcreds (smbopts):
gpos = self.get_gpos(username)
try:
log('D49')
check_refresh_gpo_list(self.selected_dc, self.lp, self.creds, gpos)
log('D50')
except Exception as exc:
logdata = dict()
logdata['username'] = username
logdata['dc'] = self.selected_dc
logdata['err'] = str(exc)
log('F1')
raise exc
logging.error(
slogm('Unable to refresh GPO list for {} from {}'.format(
username, self.selected_dc)))
return gpos
@ -171,11 +161,10 @@ def get_sid(domain, username, is_machine = False):
try:
sid = wbinfo_getsid(domain, username)
except:
logdata = dict({'sid': sid})
log('E16', logdata)
logging.warning(
slogm('Error getting SID using wbinfo, will use cached SID: {}'.format(sid)))
logdata = dict({'sid': sid})
log('D21', logdata)
logging.debug(slogm('Working with SID: {}'.format(sid)))
return sid
@ -185,15 +174,17 @@ def expand_windows_var(text, username=None):
Scan the line for percent-encoded variables and expand them.
'''
variables = dict()
variables['HOME'] = '/etc/skel'
variables['HOME'] = '/'
variables['SystemRoot'] = '/'
variables['StartMenuDir'] = '/usr/share/applications'
variables['SystemDrive'] = '/'
variables['DesktopDir'] = xdg_get_desktop(username, variables['HOME'])
if username:
variables['HOME'] = get_homedir(username)
variables['DesktopDir'] = get_user_dir(
'DESKTOP', os.path.join(variables['HOME'], 'Desktop'))
variables['StartMenuDir'] = os.path.join(
variables['HOME'], '.local', 'share', 'applications')

View File

@ -17,22 +17,21 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from configparser import RawConfigParser, DEFAULTSECT
import os
from xdg.BaseDirectory import xdg_config_home
from .util import get_homedir
from .logging import log
def xdg_get_desktop(username, homedir = None):
if username:
homedir = get_homedir(username)
if not homedir:
msgtext = message_with_code('E18')
logdata = dict()
logdata['username'] = username
log('E18', logdata)
raise Exception(msgtext)
stream = os.popen('export HOME={}; xdg-user-dir DESKTOP'.format(homedir))
output = stream.read()[:-1]
return output
def get_user_dir(dir_name, default=None):
'''
Get path to XDG's user directory
'''
config = RawConfigParser(allow_no_value=True)
userdirs_path = os.path.join(xdg_config_home, 'user-dirs.dirs')
try:
with open(userdirs_path, 'r') as f:
config.read_string('[DEFAULT]\n' + f.read())
return config.get(DEFAULTSECT, 'XDG_DESKTOP_DIR')
except Exception as exc:
return default

View File

@ -1,7 +1,7 @@
%define _unpackaged_files_terminate_build 1
Name: gpupdate
Version: 0.8.2
Version: 0.5.0
Release: alt1
Summary: GPT applier
@ -11,16 +11,15 @@ Url: https://github.com/altlinux/gpupdate
BuildArch: noarch
Requires: control
Requires: local-policy >= 0.1.0
BuildRequires: rpm-build-python3
BuildRequires: python-tools-i18n
Requires: python3-module-rpm
Requires: python3-module-dbus
Requires: oddjob-%name >= 0.2.0
Requires: libnss-role >= 0.5.0
Requires: local-policy >= 0.4.0
Requires: pam-config >= 1.9.0
Requires: autofs
Requires: local-policy >= 0.3.0
Requires: pam-config >= 1.8
# This is needed by shortcuts_applier
Requires: desktop-file-utils
@ -39,37 +38,27 @@ mkdir -p \
cp -r gpoa \
%buildroot%python3_sitelibdir/
# Generate translations
pymsgfmt \
-o %buildroot%python3_sitelibdir/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.mo \
%buildroot%python3_sitelibdir/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po
mkdir -p \
%buildroot%_bindir/ \
%buildroot%_sbindir/ \
%buildroot%_cachedir/%name/ \
%buildroot%_cachedir/%name/creds
%buildroot%_cachedir/%name/
ln -s %python3_sitelibdir/gpoa/gpoa \
%buildroot%_sbindir/gpoa
ln -s %python3_sitelibdir/gpoa/gpupdate \
%buildroot%_bindir/gpupdate
ln -s %python3_sitelibdir/gpoa/gpupdate-setup \
cp dist/gpupdate-setup \
%buildroot%_sbindir/gpupdate-setup
mkdir -p %buildroot%_datadir/%name
mv %buildroot%python3_sitelibdir/gpoa/templates \
%buildroot%_datadir/%name/
mkdir -p %buildroot%_sysconfdir/%name
touch %buildroot%_sysconfdir/%name/environment
install -Dm0644 dist/%name.service %buildroot%_unitdir/%name.service
install -Dm0644 dist/%name.service %buildroot/usr/lib/systemd/user/%name-user.service
install -Dm0644 dist/%name.service %{buildroot}/usr/lib/systemd/user/%{name}-user.service
install -Dm0644 dist/system-policy-%name %buildroot%_sysconfdir/pam.d/system-policy-%name
install -Dm0644 dist/%name.ini %buildroot%_sysconfdir/%name/%name.ini
install -Dm0644 doc/gpoa.1 %buildroot/%_man1dir/gpoa.1
install -Dm0644 doc/gpupdate.1 %buildroot/%_man1dir/gpupdate.1
install -Dm0644 doc/gpoa.1 %buildroot/%{_man1dir}/gpoa.1
install -Dm0644 doc/gpupdate.1 %buildroot/%{_man1dir}/gpupdate.1
%preun
%preun_service gpupdate
@ -77,74 +66,22 @@ install -Dm0644 doc/gpupdate.1 %buildroot/%_man1dir/gpupdate.1
%post
%post_service gpupdate
# Remove storage in case we've lost compatibility between versions.
# The storage will be regenerated on GPOA start.
%define active_policy %_sysconfdir/local-policy/active
%triggerpostun -- %name < 0.8.0
rm -f %_cachedir/%name/registry.sqlite
if test -L %active_policy; then
sed -i "s|^\s*local-policy\s*=.*|local-policy = $(readlink -f %active_policy)|" \
%_sysconfdir/%name/%name.ini
fi
%files
%_sbindir/gpoa
%_sbindir/gpupdate-setup
%_bindir/gpupdate
%attr(755,root,root) %python3_sitelibdir/gpoa/gpoa
%attr(755,root,root) %python3_sitelibdir/gpoa/gpupdate
%attr(755,root,root) %python3_sitelibdir/gpoa/gpupdate-setup
%python3_sitelibdir/gpoa
%_datadir/%name
%_unitdir/%name.service
%_man1dir/gpoa.1.*
%_man1dir/gpupdate.1.*
/usr/lib/systemd/user/%name-user.service
%dir %_sysconfdir/%name
%config(noreplace) %_sysconfdir/%name/environment
%config(noreplace) %_sysconfdir/%name/%name.ini
%config(noreplace) %_sysconfdir/pam.d/system-policy-%name
%dir %attr(0700, root, root) %_cachedir/%name
%dir %attr(0700, root, root) %_cachedir/%name/creds
%exclude %python3_sitelibdir/gpoa/.pylintrc
%exclude %python3_sitelibdir/gpoa/.prospector.yaml
%exclude %python3_sitelibdir/gpoa/Makefile
%exclude %python3_sitelibdir/gpoa/test
%_sysconfdir/pam.d/system-policy-%name
%dir %_cachedir/%name
%changelog
* Tue Dec 22 2020 Igor Chudov <nir@altlinux.org> 0.8.2-alt1
- Increased D-Bus timeouts on calls
- Minor logging fixes
* Wed Oct 07 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.8.1-alt3
- Fixed compatibility upgrade trigger condition
* Wed Oct 07 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.8.1-alt2
- Fixed compatibility upgrade trigger from 0.7 releases for update
active local-policy in new gpupdate.ini configuartion file
* Fri Sep 11 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.8.1-alt1
- Workaround for control names with special symbols
- Improved logging on Kerberos errors
* Fri Sep 04 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.8.0-alt1
- Improve gpo applier logging
- Add new configuration file /etc/gpupdate/gpupdate.ini
- Fix folders applier regression
- kinit move to samba backend
- Replace gpupdate-setup utility with new implementation
* Wed Jul 01 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.7.0-alt1
- Add multiple appliers, part of which marks as experimental yet
* Wed May 20 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.6.0-alt2
- Update system-policy PAM-rules (clean system-auth-common, add pam_env support)
- Add dependency to pam-config later than 1.9.0 release
* Fri May 15 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.6.0-alt1
- Add drives policy for shared folders with cifs applier using autofs
- Update shortcuts policy with xdg-users-dir support for DESKTOP
* Wed Apr 22 2020 Evgeny Sinelnikov <sin@altlinux.org> 0.5.0-alt1
- Update samba format: local.xml -> Machine/Registry.pol.xml
- Add support of ad-domain-controller local policy profile

2
wiki

Submodule wiki updated: 79e4ba7f58...8cb284e0f7