1
0
mirror of https://github.com/altlinux/gpupdate.git synced 2025-03-21 18:50:38 +03:00

Merge pull request #165 from altlinux/scripts_logon_etc_rebased

Scripts logon etc rebased
This commit is contained in:
Evgeny Sinelnikov 2022-06-08 12:38:12 +04:00 committed by GitHub
commit 23be105462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 720 additions and 86 deletions

12
dist/gpupdate-scripts-run-user.service vendored Normal file
View File

@ -0,0 +1,12 @@
[Unit]
Description=Run Group Policy scripts for a user
After=gpupdate-user.service
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/libexec/gpupdate/scripts_runner --mode USER --action LOGON --user %u
ExecStop=/usr/libexec/gpupdate/scripts_runner --mode USER --action LOGOFF --user %u
[Install]
WantedBy=default.target

15
dist/gpupdate-scripts-run.service vendored Normal file
View File

@ -0,0 +1,15 @@
[Unit]
Description=Running Group Policy Scripts
After=gpupdate.service
[Service]
Environment=PATH=/bin:/sbin:/usr/bin:/usr/sbin
UnsetEnvironment=LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/libexec/gpupdate/scripts_runner --mode MACHINE --action STARTUP
ExecStop=/usr/libexec/gpupdate/scripts_runner --mode MACHINE --action SHUTDOWN
StandardOutput=journal
[Install]
WantedBy=multi-user.target

View File

@ -6,12 +6,8 @@ Description=gpupdate in userspace
[Service]
Environment=PATH=/bin:/sbin:/usr/bin:/usr/sbin
UnsetEnvironment=LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION
Type=simple
RestartSec=3600
TimeoutSec=3000
Restart=always
ExecStart=/usr/sbin/gpoa
Type=oneshot
ExecStart=/usr/bin/gpupdate --target USER
[Install]
WantedBy=default.target

9
dist/gpupdate-user.timer vendored Normal file
View File

@ -0,0 +1,9 @@
[Unit]
Description=Run gpupdate-user every hour
[Timer]
OnStartupSec=1
OnUnitActiveSec=60min
[Install]
WantedBy=timers.target

View File

@ -5,10 +5,7 @@ After=syslog.target network-online.target sssd.service
[Service]
Environment=PATH=/bin:/sbin:/usr/bin:/usr/sbin
UnsetEnvironment=LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION
Type=simple
RestartSec=3600
TimeoutSec=3000
Restart=always
Type=oneshot
ExecStart=/usr/bin/gpupdate
StandardOutput=journal

9
dist/gpupdate.timer vendored Normal file
View File

@ -0,0 +1,9 @@
[Unit]
Description=Run gpupdate every hour
[Timer]
OnStartupSec=1
OnUnitActiveSec=60min
[Install]
WantedBy=timers.target

View File

@ -25,7 +25,7 @@ from gpt.gpt import gpt, get_local_gpt
from util.util import (
get_machine_name
)
from util.windows import get_sid
from util.sid import get_sid
import util.preg
from util.logging import slogm

View File

@ -31,7 +31,7 @@ from util.kerberos import (
machine_kinit
, machine_kdestroy
)
from util.windows import get_sid
from util.sid import get_sid
import util.preg
from util.logging import log

View File

@ -24,9 +24,8 @@ from .applier_frontend import (
import logging
import json
import os
from util.logging import slogm, log
from util.util import is_machine_name
from util.util import is_machine_name, get_homedir, mk_homedir_path
class chromium_applier(applier_frontend):
__module_name = 'ChromiumApplier'
@ -57,7 +56,7 @@ class chromium_applier(applier_frontend):
def get_hkcu_string_entry(self, hive_subkey):
query_str = '{}\\{}'.format(self.__registry_branch, hive_subkey)
return self.storage.get_hkcu_entry(sid, query_str)
return self.storage.get_hkcu_entry(self.sid, query_str)
def get_hklm_string_entry_default(self, hive_subkey, default):
'''
@ -94,11 +93,11 @@ class chromium_applier(applier_frontend):
a good practice and used mostly by various malware.
'''
if not self._is_machine_name:
prefdir = os.path.join(util.get_homedir(self.username), self.__user_settings)
prefdir = os.path.join(get_homedir(self.username), self.__user_settings)
os.makedirs(prefdir, exist_ok=True)
prefpath = os.path.join(prefdir, 'Preferences')
util.mk_homedir_path(self.username, self.__user_settings)
mk_homedir_path(self.username, self.__user_settings)
settings = dict()
try:
with open(prefpath, 'r') as f:

View File

@ -35,7 +35,7 @@ from .applier_frontend import (
, check_enabled
)
from util.logging import slogm, log
from util.util import is_machine_name
from util.util import is_machine_name, get_homedir
class firefox_applier(applier_frontend):
__module_name = 'FirefoxApplier'
@ -63,7 +63,7 @@ class firefox_applier(applier_frontend):
'''
Get directory names of Firefox profiles for specified username.
'''
profiles_ini = os.path.join(util.get_homedir(self.username), self.__user_settings_dir, 'profiles.ini')
profiles_ini = os.path.join(get_homedir(self.username), self.__user_settings_dir, 'profiles.ini')
config = configparser.ConfigParser()
config.read(profiles_ini)
@ -204,7 +204,7 @@ class firefox_applier(applier_frontend):
def user_apply(self):
profiles = self.get_profiles()
profiledir = os.path.join(util.get_homedir(self.username), self.__user_settings_dir)
profiledir = os.path.join(get_homedir(self.username), self.__user_settings_dir)
for profile in profiles:
logdata = dict()
logdata['profiledir'] = profiledir

View File

@ -51,7 +51,12 @@ from .envvar_applier import (
envvar_applier
, envvar_applier_user
)
from util.windows import get_sid
from .scripts_applier import (
scripts_applier
, scripts_applier_user
)
from util.sid import get_sid
from util.users import (
is_root,
get_process_user,
@ -111,6 +116,13 @@ class frontend_manager:
self.file_cache = fs_file_cache('file_cache')
self.machine_appliers = dict()
self.user_appliers = dict()
if is_machine:
self._init_machine_appliers()
else:
self._init_user_appliers()
def _init_machine_appliers(self):
self.machine_appliers['control'] = control_applier(self.storage)
self.machine_appliers['polkit'] = polkit_applier(self.storage)
self.machine_appliers['systemd'] = systemd_applier(self.storage)
@ -124,10 +136,11 @@ class frontend_manager:
self.machine_appliers['package'] = package_applier(self.storage)
self.machine_appliers['ntp'] = ntp_applier(self.storage)
self.machine_appliers['envvar'] = envvar_applier(self.storage, self.sid)
self.machine_appliers['scripts'] = scripts_applier(self.storage, self.sid)
def _init_user_appliers(self):
# 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.file_cache, self.sid, self.username)
@ -141,6 +154,7 @@ class frontend_manager:
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['envvar'] = envvar_applier_user(self.storage, self.sid, self.username)
self.user_appliers['scripts'] = scripts_applier_user(self.storage, self.sid, self.username)
def machine_apply(self):
'''

View File

@ -0,0 +1,159 @@
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2022 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 shutil
from pathlib import Path
import pysss_nss_idmap
from django.template import base
from util.logging import log
from .appliers.folder import remove_dir_tree
from .applier_frontend import (
applier_frontend
, check_enabled
)
class scripts_applier(applier_frontend):
__module_name = 'ScriptsApplier'
__module_experimental = True
__module_enabled = False
__cache_scripts = '/var/cache/gpupdate_scripts_cache/machine/'
def __init__(self, storage, sid):
self.storage = storage
self.sid = sid
self.startup_scripts = self.storage.get_scripts(self.sid, 'STARTUP')
self.shutdown_scripts = self.storage.get_scripts(self.sid, 'SHUTDOWN')
self.folder_path = Path(self.__cache_scripts)
self.__module_enabled = check_enabled(self.storage
, self.__module_name
, self.__module_experimental
)
def cleaning_cache(self):
log('D160')
try:
remove_dir_tree(self.folder_path, True, True, True,)
except FileNotFoundError as exc:
log('D154')
except Exception as exc:
logdata = dict()
logdata['exc'] = exc
log('E64', logdata)
def filling_cache(self):
'''
Creating and updating folder directories for scripts and copying them
'''
self.folder_path.mkdir(parents=True, exist_ok=True)
for ts in self.startup_scripts:
script_path = os.path.join(self.__cache_scripts, 'STARTUP')
install_script(ts, script_path, '700')
for ts in self.shutdown_scripts:
script_path = os.path.join(self.__cache_scripts, 'SHUTDOWN')
install_script(ts, script_path, '700')
def run(self):
self.filling_cache()
def apply(self):
self.cleaning_cache()
if self.__module_enabled:
log('D156')
self.run()
else:
log('D157')
class scripts_applier_user(applier_frontend):
__module_name = 'ScriptsApplierUser'
__module_experimental = True
__module_enabled = False
__cache_scripts = '/var/cache/gpupdate_scripts_cache/users/'
def __init__(self, storage, sid, username):
self.storage = storage
self.sid = sid
self.logon_scripts = self.storage.get_scripts(self.sid, 'LOGON')
self.logoff_scripts = self.storage.get_scripts(self.sid, 'LOGOFF')
self.username = username
self.folder_path = Path(self.__cache_scripts + self.username)
self.__module_enabled = check_enabled(self.storage
, self.__module_name
, self.__module_experimental
)
self.filling_cache()
def cleaning_cache(self):
log('D161')
try:
remove_dir_tree(self.folder_path, True, True, True,)
except FileNotFoundError as exc:
log('D155')
except Exception as exc:
logdata = dict()
logdata['exc'] = exc
log('E65', logdata)
def filling_cache(self):
'''
Creating and updating folder directories for scripts and copying them
'''
self.folder_path.mkdir(parents=True, exist_ok=True)
for ts in self.logon_scripts:
script_path = os.path.join(self.__cache_scripts, self.username, 'LOGON')
install_script(ts, script_path, '755')
for ts in self.logoff_scripts:
script_path = os.path.join(self.__cache_scripts, self.username, 'LOGOFF')
install_script(ts, script_path, '755')
def user_context_apply(self):
pass
def run(self):
self.filling_cache()
def admin_context_apply(self):
self.cleaning_cache()
if self.__module_enabled:
log('D158')
self.run()
else:
log('D159')
def install_script(storage_script_entry, script_dir, access_permissions):
'''
Copy scripts to specific directories and
if given arguments
create directories for them and copy them there
'''
dir_cr = Path(script_dir)
dir_cr.mkdir(parents=True, exist_ok=True)
script_name = str(int(storage_script_entry.number)).zfill(5) + '_' + os.path.basename(storage_script_entry.path)
script_file = os.path.join(script_dir, script_name)
shutil.copyfile(storage_script_entry.path, script_file)
os.chmod(script_file, int(access_permissions, base = 8))
if storage_script_entry.arg:
dir_path = script_dir + '/' + script_name + '.arg'
dir_arg = Path(dir_path)
dir_arg.mkdir(parents=True, exist_ok=True)
file_arg = open(dir_path + '/arg', 'w')
file_arg.write(storage_script_entry.arg)
file_arg.close()

View File

@ -64,7 +64,10 @@ from .tasks import (
read_tasks
, merge_tasks
)
from .scriptsini import (
read_scripts
, merge_scripts
)
import util
import util.preg
from util.paths import (
@ -87,6 +90,7 @@ class FileType(Enum):
INIFILES = 'inifiles.xml'
SERVICES = 'services.xml'
PRINTERS = 'printers.xml'
SCRIPTS = 'scripts.ini'
def get_preftype(path_to_file):
fpath = Path(path_to_file)
@ -112,6 +116,7 @@ def pref_parsers():
parsers[FileType.INIFILES] = read_inifiles
parsers[FileType.SERVICES] = read_services
parsers[FileType.PRINTERS] = read_printers
parsers[FileType.SCRIPTS] = read_scripts
return parsers
@ -132,6 +137,7 @@ def pref_mergers():
mergers[FileType.INIFILES] = merge_inifiles
mergers[FileType.SERVICES] = merge_services
mergers[FileType.PRINTERS] = merge_printers
mergers[FileType.SCRIPTS] = merge_scripts
return mergers
@ -145,13 +151,14 @@ class gpt:
self.sid = sid
self.storage = registry_factory('registry')
self.name = ''
self.guid = self.path.rpartition('/')[2]
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._scripts_machine_path = find_dir(self._machine_path, 'Scripts')
self._scripts_user_path = find_dir(self._user_path, 'Scripts')
self.settings_list = [
'shortcuts'
@ -163,6 +170,7 @@ class gpt:
, 'inifiles'
, 'services'
, 'scheduledtasks'
, 'scripts'
]
self.settings = dict()
self.settings['machine'] = dict()
@ -179,6 +187,10 @@ class gpt:
log('D23', ulogdata)
self.settings['user'][setting] = user_preffile
self.settings['machine']['scripts'] = find_file(self._scripts_machine_path, 'scripts.ini')
self.settings['user']['scripts'] = find_file(self._scripts_user_path, 'scripts.ini')
def set_name(self, name):
'''
Set human-readable GPT name.

145
gpoa/gpt/scriptsini.py Normal file
View File

@ -0,0 +1,145 @@
#
# 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 configparser
import os
def read_scripts(scripts_file):
scripts = Scripts_lists()
logon_scripts = dict()
logoff_scripts = dict()
startup_scripts = dict()
shutdown_scripts = dict()
config = configparser.ConfigParser()
config.read(scripts_file, encoding = 'utf-16')
scripts_file_dir = os.path.dirname(scripts_file)
actions = config.sections()
for act in actions:
act_upper = act.upper()
if act_upper == 'LOGON':
section_scripts = logon_scripts
elif act_upper == 'LOGOFF':
section_scripts = logoff_scripts
elif act_upper == 'STARTUP':
section_scripts = startup_scripts
elif act_upper == 'SHUTDOWN':
section_scripts = shutdown_scripts
else:
continue
for key in config[act]:
key_lower = key.lower()
key_split = key_lower.split('cmdline')
if len(key_split) > 1 and not key_split[1]:
if key_split[0].isdigit():
key_index = int(key_split[0])
section_scripts[key_index] = Script(act, scripts_file_dir, config[act][key])
key_split = key_lower.split('parameters')
if len(key_split) > 1 and not key_split[1]:
if key_split[0].isdigit():
key_index = int(key_split[0])
section_scripts[key_index].set_args(config[act][key])
if logon_scripts:
for i in sorted(logon_scripts.keys()):
scripts.add_script(act_upper, logon_scripts[i])
if logoff_scripts:
for i in sorted(logoff_scripts.keys()):
scripts.add_script(act_upper, logoff_scripts[i])
if startup_scripts:
for i in sorted(startup_scripts.keys()):
scripts.add_script(act_upper, startup_scripts[i])
if shutdown_scripts:
for i in sorted(shutdown_scripts.keys()):
scripts.add_script(act_upper, shutdown_scripts[i])
return scripts
def merge_scripts(storage, sid, scripts_objects, policy_name):
for script in scripts_objects.get_logon_scripts():
storage.add_script(sid, script, policy_name)
for script in scripts_objects.get_logoff_scripts():
storage.add_script(sid, script, policy_name)
for script in scripts_objects.get_startup_scripts():
storage.add_script(sid, script, policy_name)
for script in scripts_objects.get_shutdown_scripts():
storage.add_script(sid, script, policy_name)
class Scripts_lists:
def __init__ (self):
self.__logon_scripts = list()
self.__logoff_scripts = list()
self.__startup_scripts = list()
self.__shutdown_scripts = list()
def get_logon_scripts(self):
return self.__logon_scripts
def get_logoff_scripts(self):
return self.__logoff_scripts
def get_startup_scripts(self):
return self.__startup_scripts
def get_shutdown_scripts(self):
return self.__shutdown_scripts
def add_script(self, action, script):
if action == 'LOGON':
self.get_logon_scripts().append(script)
elif action == 'LOGOFF':
self.get_logoff_scripts().append(script)
elif action == 'STARTUP':
self.get_startup_scripts().append(script)
elif action == 'SHUTDOWN':
self.get_shutdown_scripts().append(script)
class Script:
__logon_counter = 0
__logoff_counter = 0
__startup_counter = 0
__shutdown_counter = 0
def __init__(self, action, script_dir, script_filename):
action_upper = action.upper()
self.action = action_upper
self.path = os.path.join(script_dir, action_upper, script_filename.upper())
self.args = None
if action_upper == 'LOGON':
self.number = Script.__logon_counter
Script.__logon_counter += 1
elif action_upper == 'LOGOFF':
self.number = Script.__logoff_counter
Script.__logoff_counter += 1
elif action_upper == 'STARTUP':
self.number = Script.__startup_counter
Script.__startup_counter += 1
elif action_upper == 'SHUTDOWN':
self.number = Script.__shutdown_counter
Script.__shutdown_counter += 1
def set_args(self, args):
self.args = args

View File

@ -50,7 +50,7 @@ class service:
self.serviceaction = None
def set_clsid(self, clsid):
self.guid = uid
self.guid = clsid
def set_usercontext(self, usercontext=False):
ctx = False

View File

@ -61,7 +61,8 @@ def parse_arguments():
parser_disable = subparsers.add_parser('disable',
help='Disable Group Policy subsystem')
parser_update = subparsers.add_parser('update',
help='Update state')
parser_write = subparsers.add_parser('write',
help='Operate on Group Policies (enable or disable)')
parser_set_backend = subparsers.add_parser('set-backend',
@ -105,6 +106,16 @@ def parse_arguments():
choices=['local', 'samba'],
help='Backend (source of settings) name')
parser_update.add_argument('--local-policy',
default=None,
help='Name of local policy to enable')
parser_update.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):
@ -173,7 +184,11 @@ def disable_gp():
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_disable_gpupdate_timer = ['/bin/systemctl', 'disable', 'gpupdate.timer']
cmd_disable_gpupdate_user_timer = ['/bin/systemctl', '--global', 'disable', 'gpupdate-user.timer']
cmd_control_system_auth = ['/usr/sbin/control', 'system-auth']
cmd_disable_gpupdate_scripts_service = ['/bin/systemctl', 'disable', 'gpupdate-scripts-run.service']
cmd_disable_gpupdate_scripts_user_service = ['/bin/systemctl', '--global', 'disable', 'gpupdate-scripts-run-user.service']
config = GPConfig()
@ -189,6 +204,10 @@ def disable_gp():
runcmd(cmd_set_local_policy)
runcmd(cmd_disable_gpupdate_service)
runcmd(cmd_disable_gpupdate_user_service)
runcmd(cmd_disable_gpupdate_timer)
runcmd(cmd_disable_gpupdate_user_timer)
runcmd(cmd_disable_gpupdate_scripts_service)
runcmd(cmd_disable_gpupdate_scripts_user_service)
config.set_local_policy_template()
config.set_backend()
@ -200,6 +219,10 @@ def enable_gp(policy_name, backend_type):
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']
cmd_enable_gpupdate_timer = ['/bin/systemctl', 'enable', 'gpupdate.timer']
cmd_enable_gpupdate_user_timer = ['/bin/systemctl', '--global', 'enable', 'gpupdate-user.timer']
cmd_enable_gpupdate_scripts_service = ['/bin/systemctl', 'enable', 'gpupdate-scripts-run.service']
cmd_enable_gpupdate_user_scripts_service = ['/bin/systemctl', '--global', 'enable', 'gpupdate-scripts-run-user.service']
config = GPConfig()
@ -235,6 +258,32 @@ def enable_gp(policy_name, backend_type):
disable_gp()
return
# Enable gpupdate-scripts-run.service
if not rollback_on_error(cmd_enable_gpupdate_scripts_service):
return
if not is_unit_enabled('gpupdate-scripts-run.service'):
disable_gp()
return
# Enable gpupdate-scripts-run-user.service for all users
if not rollback_on_error(cmd_enable_gpupdate_user_scripts_service):
return
if not is_unit_enabled('gpupdate-scripts-run-user.service', unit_global=True):
disable_gp()
return
# Enable gpupdate.timer
if not rollback_on_error(cmd_enable_gpupdate_timer):
return
if not is_unit_enabled('gpupdate.timer'):
disable_gp()
return
# Enable gpupdate-setup.timer for all users
if not rollback_on_error(cmd_enable_gpupdate_user_timer):
return
if not is_unit_enabled('gpupdate-user.timer', unit_global=True):
disable_gp()
return
def act_list():
'''
Show list of available templates of Local Policy
@ -306,6 +355,7 @@ def main():
action['set-backend'] = act_set_backend
action['write'] = act_write
action['enable'] = act_enable
action['update'] = act_enable
action['disable'] = disable_gp
action['active-policy'] = act_active_policy
action['active-backend'] = act_active_backend
@ -313,6 +363,9 @@ def main():
if arguments.action == None:
action['status']()
elif arguments.action == 'update':
if get_status():
action[arguments.action](arguments.local_policy, arguments.backend)
elif arguments.action == 'enable':
action[arguments.action](arguments.local_policy, arguments.backend)
elif arguments.action == 'write':

View File

@ -225,6 +225,12 @@ msgstr "Ошибка при запуске pkcon_runner асинхронно д
msgid "Error merging user GPT (from machine GPO)"
msgstr "Ошибка слияния пользовательской групповой политики (машинная часть)"
msgid "Error cleaning directory for machine"
msgstr "Ошибка очистки каталога для машины"
msgid "Error cleaning directory for user"
msgstr "Ошибка очистки каталога для пользователя"
# Error_end
# Debug
@ -660,6 +666,33 @@ msgstr "Не удалось найти настройки gsettings пользо
msgid "Configure user Group Policy loopback processing mode"
msgstr "Настройка режима обработки замыкания пользовательской групповой политики"
msgid "Saving information about script"
msgstr "Сохранение информации о скрипте"
msgid "No machine scripts directory to clean up"
msgstr "Нет каталога машинных скриптов для очистки"
msgid "No user scripts directory to clean up"
msgstr "Нет каталога пользовательских скриптов для очистки"
msgid "Prepare Scripts applier for machine"
msgstr "Подготовка к применению машинных скриптов"
msgid "Scripts applier for machine will not be started"
msgstr "Применение машинных скриптов не запускается"
msgid "Prepare Scripts applier for user in user context"
msgstr "Подготовка к применению скриптов пользователя в его контексте"
msgid "Scripts applier for user in user context will not be started"
msgstr "Применение скриптов пользователя в его контексте не запускается"
msgid "Clean machine scripts directory"
msgstr "Очистка каталога машинных скриптов"
msgid "Clean user scripts directory"
msgstr "Очистка каталога пользовательских скриптов"
# Debug_end
# Warning

View File

@ -98,6 +98,8 @@ def error_code(code):
error_ids[61] = 'Error running pkcon_runner async for machine'
error_ids[62] = 'Error running pkcon_runner async for user'
error_ids[63] = 'Error merging user GPT (from machine GPO)'
error_ids[64] = 'Error to cleanup directory for machine'
error_ids[65] = 'Error to cleanup directory for user'
return error_ids.get(code, 'Unknown error code')
@ -256,6 +258,15 @@ def debug_code(code):
debug_ids[150] = 'Failed to found gsettings for machine'
debug_ids[151] = 'Failed to found user gsettings'
debug_ids[152] = 'Configure user Group Policy loopback processing mode'
debug_ids[153] = 'Saving information about script'
debug_ids[154] = 'No machine scripts directory to clean up'
debug_ids[155] = 'No user scripts directory to clean up'
debug_ids[156] = 'Prepare Scripts applier for machine'
debug_ids[157] = 'Scripts applier for machine will not be started'
debug_ids[158] = 'Prepare Scripts applier for user in user context'
debug_ids[159] = 'Scripts applier for user in user context will not be started'
debug_ids[160] = 'Clean machine scripts directory'
debug_ids[161] = 'Clean user scripts directory'
return debug_ids.get(code, 'Unknown debug code')

124
gpoa/scripts_runner Executable file
View File

@ -0,0 +1,124 @@
#!/usr/bin/python3
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2022 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 subprocess
import argparse
import os
from pathlib import Path
class Scripts_runner:
'''
A class for an object that iterates over directories with scripts
in the desired sequence and launches them
'''
def __init__(self, work_mode = None, user_name = None, action = None):
self.dir_scripts_machine = '/var/cache/gpupdate_scripts_cache/machine/'
self.dir_scripts_users = '/var/cache/gpupdate_scripts_cache/users/'
self.user_name = user_name
self.list_with_all_commands = list()
stack_dir = None
if work_mode and work_mode.upper() == 'MACHINE':
stack_dir = self.machine_runner_fill()
elif work_mode and work_mode.upper() == 'USER':
stack_dir = self.user_runner_fill()
else:
print('Invalid arguments entered')
return
if action:
self.action = action.upper()
else:
print('Action needed')
return
self.find_action(stack_dir)
for it_cmd in self.list_with_all_commands:
print(self.run_cmd_subprocess(it_cmd))
def user_runner_fill(self):
return self.get_stack_dir(self.dir_scripts_users + self.user_name)
def machine_runner_fill(self):
return self.get_stack_dir(self.dir_scripts_machine)
def get_stack_dir(self, path_dir):
stack_dir = list()
try:
dir_script = Path(path_dir)
for it_dir in dir_script.iterdir():
stack_dir.append(str(it_dir))
return stack_dir
except Exception as exc:
print(exc)
return None
def find_action(self, stack_dir):
if not stack_dir:
return
list_tmp = list()
while stack_dir:
path_turn = stack_dir.pop()
basename = os.path.basename(path_turn)
if basename == self.action:
list_tmp = self.get_stack_dir(path_turn)
if list_tmp:
self.fill_list_cmd(list_tmp)
def fill_list_cmd(self, list_tmp):
list_tmp = sorted(list_tmp)
for file_in_task_dir in list_tmp:
suffix = os.path.basename(file_in_task_dir)[-4:]
if suffix == '.arg':
try:
arg = self.read_args(file_in_task_dir)
for it_arg in arg.split():
self.list_with_all_commands[-1].append(it_arg)
except Exception as exc:
print('Argument read for {}: {}'.format(self.list_with_all_commands.pop(), exc))
else:
cmd = list()
cmd.append(file_in_task_dir)
self.list_with_all_commands.append(cmd)
def read_args(self, path):
with open(path + '/arg') as f:
args = f.readlines()
return args[0]
def run_cmd_subprocess(self, cmd):
try:
res = subprocess.check_output(cmd, encoding='utf-8')
return 'Script output: {}'.format(res)
except Exception as exc:
return exc
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Scripts runner')
parser.add_argument('--mode', type = str, help = 'MACHINE or USER', nargs = '?', default = None)
parser.add_argument('--user', type = str, help = 'User name ', nargs = '?', default = None)
parser.add_argument('--action', type = str, help = 'MACHINE : [STARTUP or SHUTDOWN], USER : [LOGON or LOGOFF]', nargs = '?', default = None)
args = parser.parse_args()
try:
Scripts_runner(args.mode, args.user, args.action)
except Exception as exc:
print(exc)

View File

@ -174,3 +174,27 @@ class envvar_entry(object):
return fields
class script_entry(object):
'''
Object mapping representing scripts.ini
'''
def __init__(self, sid, scrobj, policy_name):
self.sid = sid
self.policy_name = policy_name
self.action = scrobj.action
self.number = scrobj.number
self.path = scrobj.path
self.arg = scrobj.args
def update_fields(self):
'''
Return list of fields to update
'''
fields = dict()
fields['policy_name'] = self.policy_name
fields['action'] = self.action
fields['number'] = self.number
fields['path'] = self.path
fields['arg'] = self.arg
return fields

View File

@ -44,6 +44,7 @@ from .record_types import (
, drive_entry
, folder_entry
, envvar_entry
, script_entry
)
class sqlite_registry(registry):
@ -143,6 +144,18 @@ class sqlite_registry(registry):
, Column('value', String)
, UniqueConstraint('sid', 'name')
)
self.__scripts = Table(
'Scripts'
, self.__metadata
, Column('id', Integer, primary_key=True)
, Column('sid', String)
, Column('policy_name', String)
, Column('number', String)
, Column('action', String)
, Column('path', String)
, Column('arg', String)
, UniqueConstraint('sid', 'path', 'arg')
)
self.__metadata.create_all(self.db_cnt)
Session = sessionmaker(bind=self.db_cnt)
self.db_session = Session()
@ -155,6 +168,7 @@ class sqlite_registry(registry):
mapper(drive_entry, self.__drives)
mapper(folder_entry, self.__folders)
mapper(envvar_entry, self.__envvars)
mapper(script_entry, self.__scripts)
except:
pass
#logging.error('Error creating mapper')
@ -365,6 +379,21 @@ class sqlite_registry(registry):
.filter(envvar_entry.name == ev_entry.name)
.update(ev_entry.update_fields()))
self.db_session.commit()
def add_script(self, sid, scrobj, policy_name):
scr_entry = script_entry(sid, scrobj, policy_name)
logdata = dict()
logdata['script path'] = scrobj.path
logdata['sid'] = sid
log('D153', logdata)
try:
self._add(scr_entry)
except Exception as exc:
(self
._filter_sid_obj(script_entry, sid)
.filter(script_entry.path == scr_entry.path)
.update(scr_entry.update_fields()))
self.db_session.commit()
def _filter_sid_obj(self, row_object, sid):
res = (self
@ -397,6 +426,19 @@ class sqlite_registry(registry):
def get_envvars(self, sid):
return self._filter_sid_list(envvar_entry, sid)
def _filter_scripts_list(self, row_object, sid, action):
res = (self
.db_session
.query(row_object)
.filter(row_object.sid == sid)
.filter(row_object.action == action)
.order_by(row_object.id)
.all())
return res
def get_scripts(self, sid, action):
return self._filter_scripts_list(script_entry, sid, action)
def get_hkcu_entry(self, sid, hive_key):
res = (self
.db_session
@ -446,6 +488,7 @@ class sqlite_registry(registry):
self._wipe_sid(ad_shortcut, sid)
self._wipe_sid(printer_entry, sid)
self._wipe_sid(drive_entry, sid)
self._wipe_sid(script_entry, sid)
def _wipe_sid(self, row_object, sid):
(self

View File

@ -19,6 +19,12 @@
from enum import Enum
import pwd
import logging
import subprocess
import pysss_nss_idmap
from .logging import log
def wbinfo_getsid(domain, user):
'''
@ -39,25 +45,35 @@ def wbinfo_getsid(domain, user):
return sid
def get_sid(domain, username):
def get_local_sid_prefix():
return "S-1-5-21-0-0-0"
def get_sid(domain, username, is_machine = False):
'''
Lookup SID not only using wbinfo or sssd but also using own cache
'''
domain_username = '{}\\{}'.format(domain, username)
sid = 'local-{}'.format(username)
# local user
if not domain:
found_uid = 0
if not is_machine:
found_uid = pwd.getpwnam(username).pw_uid
return '{}-{}'.format(get_local_sid_prefix(), found_uid)
# domain user
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)))
logdata = dict({'sid': sid})
log('E16', logdata)
logging.debug(slogm('Working with SID: {}'.format(sid)))
logdata = dict({'sid': sid})
log('D21', logdata)
return sid
class IssuingAuthority(Enum):
SECURITY_NULL_SID_AUTHORITY = 0
SECURITY_WORLD_SID_AUTHORITY = 1

View File

@ -18,14 +18,12 @@
import os
import pwd
import subprocess
from samba import getopt as options
from samba import NTSTATUSError
from samba.gpclass import get_dc_hostname, check_refresh_gpo_list
from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
import samba.gpo
import pysss_nss_idmap
from storage import cache_factory
from messages import message_with_code
@ -145,55 +143,6 @@ class smbcreds (smbopts):
raise exc
return gpos
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_local_sid_prefix():
return "S-1-5-21-0-0-0"
def get_sid(domain, username, is_machine = False):
'''
Lookup SID not only using wbinfo or sssd but also using own cache
'''
sid = 'local-{}'.format(username)
# local user
if not domain:
found_uid = 0
if not is_machine:
found_uid = pwd.getpwnam(username).pw_uid
return '{}-{}'.format(get_local_sid_prefix(), found_uid)
# domain user
try:
sid = wbinfo_getsid(domain, username)
except:
logdata = dict({'sid': sid})
log('E16', logdata)
logdata = dict({'sid': sid})
log('D21', logdata)
return sid
def expand_windows_var(text, username=None):
'''
Scan the line for percent-encoded variables and expand them.

View File

@ -18,7 +18,7 @@
import os
from messages import message_with_code
from .util import get_homedir
from .logging import log

View File

@ -57,6 +57,7 @@ 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 \
%buildroot%_sbindir/gpupdate-setup
@ -65,6 +66,8 @@ mkdir -p \
ln -s %python3_sitelibdir/gpoa/pkcon_runner \
%buildroot%_prefix/libexec/%name/pkcon_runner
ln -s %python3_sitelibdir/gpoa/scripts_runner \
%buildroot%_prefix/libexec/%name/scripts_runner
mkdir -p %buildroot%_datadir/%name
mv %buildroot%python3_sitelibdir/gpoa/templates \
@ -74,7 +77,11 @@ mkdir -p %buildroot%_sysconfdir/%name
touch %buildroot%_sysconfdir/%name/environment
install -Dm0644 dist/%name.service %buildroot%_unitdir/%name.service
install -Dm0644 dist/%name.timer %buildroot%_unitdir/%name.timer
install -Dm0644 dist/%name-scripts-run.service %buildroot%_unitdir/%name-scripts-run.service
install -Dm0644 dist/%name-user.service %buildroot/usr/lib/systemd/user/%name-user.service
install -Dm0644 dist/%name-scripts-run-user.service %buildroot/usr/lib/systemd/user/%name-scripts-run-user.service
install -Dm0644 dist/%name-user.timer %buildroot/usr/lib/systemd/user/%name-user.timer
install -Dm0644 dist/system-policy-%name %buildroot%_sysconfdir/pam.d/system-policy-%name
install -Dm0644 dist/%name-remote-policy %buildroot%_sysconfdir/pam.d/%name-remote-policy
install -Dm0644 dist/%name.ini %buildroot%_sysconfdir/%name/%name.ini
@ -94,6 +101,7 @@ done
%post
%post_service gpupdate
gpupdate-setup update
# Remove storage in case we've lost compatibility between versions.
# The storage will be regenerated on GPOA start.
@ -109,17 +117,23 @@ fi
%_sbindir/gpoa
%_sbindir/gpupdate-setup
%_bindir/gpupdate
%_prefix/libexec/%name/scripts_runner
%_prefix/libexec/%name/pkcon_runner
%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
%attr(755,root,root) %python3_sitelibdir/gpoa/scripts_runner
%attr(755,root,root) %python3_sitelibdir/gpoa/pkcon_runner
%python3_sitelibdir/gpoa
%_datadir/%name
%_unitdir/%name.service
%_unitdir/%name-scripts-run.service
%_unitdir/%name.timer
%_man1dir/gpoa.1.*
%_man1dir/gpupdate.1.*
/usr/lib/systemd/user/%name-user.service
/usr/lib/systemd/user/%name-user.timer
/usr/lib/systemd/user/%name-scripts-run-user.service
%dir %_sysconfdir/%name
%_sysconfdir/control.d/facilities/*
%config(noreplace) %_sysconfdir/%name/environment