2021-05-18 15:35:24 +00:00
# gp_gnome_settings_ext samba gpo policy
# Copyright (C) David Mulder <dmulder@suse.com> 2020
#
# 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/>.
2021-05-18 15:37:10 +00:00
import os , re
2022-12-02 14:51:27 -07:00
from samba . gp . gpclass import gp_pol_ext , gp_file_applier
2021-05-18 15:37:10 +00:00
from tempfile import NamedTemporaryFile
import shutil
from configparser import ConfigParser
from subprocess import Popen , PIPE
2023-08-25 11:09:52 +12:00
from samba . common import get_string
2021-05-18 15:37:10 +00:00
from glob import glob
import xml . etree . ElementTree as etree
2022-03-02 02:23:51 -07:00
from samba . gp . util . logging import log
2021-05-18 15:37:10 +00:00
2022-03-02 02:23:51 -07:00
def dconf_update ( test_dir ) :
2021-05-18 15:37:10 +00:00
if test_dir is not None :
return
dconf = shutil . which ( ' dconf ' )
if dconf is None :
log . error ( ' Failed to update dconf. Command not found ' )
return
p = Popen ( [ dconf , ' update ' ] , stdout = PIPE , stderr = PIPE )
out , err = p . communicate ( )
if p . returncode != 0 :
2022-03-02 02:23:51 -07:00
log . error ( ' Failed to update dconf ' , get_string ( err ) )
2021-05-18 15:37:10 +00:00
def create_locks_dir ( test_dir ) :
locks_dir = ' /etc/dconf/db/local.d/locks '
if test_dir is not None :
locks_dir = os . path . join ( test_dir , locks_dir [ 1 : ] )
os . makedirs ( locks_dir , exist_ok = True )
return locks_dir
def create_user_profile ( test_dir ) :
user_profile = ' /etc/dconf/profile/user '
if test_dir is not None :
user_profile = os . path . join ( test_dir , user_profile [ 1 : ] )
if os . path . exists ( user_profile ) :
return
os . makedirs ( os . path . dirname ( user_profile ) , exist_ok = True )
with NamedTemporaryFile ( ' w ' , dir = os . path . dirname ( user_profile ) ,
delete = False ) as w :
w . write ( ' user-db:user \n system-db:local ' )
2022-12-14 14:24:24 -07:00
os . chmod ( w . name , 0o644 )
2021-05-18 15:37:10 +00:00
fname = w . name
shutil . move ( fname , user_profile )
def create_local_db ( test_dir ) :
local_db = ' /etc/dconf/db/local.d '
if test_dir is not None :
local_db = os . path . join ( test_dir , local_db [ 1 : ] )
os . makedirs ( local_db , exist_ok = True )
return local_db
def select_next_conf ( directory , fname = ' ' ) :
configs = [ re . match ( r ' ( \ d+) %s ' % fname , f ) for f in os . listdir ( directory ) ]
return max ( [ int ( m . group ( 1 ) ) for m in configs if m ] + [ 0 ] ) + 1
2021-05-18 15:35:24 +00:00
2022-12-02 14:51:27 -07:00
class gp_gnome_settings_ext ( gp_pol_ext , gp_file_applier ) :
2021-05-18 15:37:10 +00:00
def __init__ ( self , * args ) :
super ( ) . __init__ ( * args )
self . keys = [ ' Compose Key ' ,
' Dim Screen when User is Idle ' ,
' Lock Down Specific Settings ' ,
' Whitelisted Online Accounts ' ,
' Enabled Extensions ' ]
self . lock_down_settings = { }
self . test_dir = None
def __str__ ( self ) :
return ' GNOME Settings/Lock Down Settings '
def __add_lockdown_data ( self , k , e ) :
if k not in self . lock_down_settings :
self . lock_down_settings [ k ] = { }
self . lock_down_settings [ k ] [ e . valuename ] = e . data
def __enable_lockdown_data ( self , e ) :
if e . valuename not in self . lock_down_settings :
self . lock_down_settings [ e . valuename ] = { }
self . lock_down_settings [ e . valuename ] [ ' Enabled ' ] = e . data == 1
def __apply_compose_key ( self , data ) :
create_user_profile ( self . test_dir )
local_db_dir = create_local_db ( self . test_dir )
2022-12-02 14:51:27 -07:00
conf_id = select_next_conf ( local_db_dir , ' -input-sources ' )
local_db = os . path . join ( local_db_dir ,
' %010d -input-sources ' % conf_id )
2021-05-18 15:37:10 +00:00
data_map = { ' Right Alt ' : ' compose:ralt ' ,
' Left Win ' : ' compose:lwin ' ,
' 3rd level of Left Win ' : ' compose:lwin-altgr ' ,
' Right Win ' : ' compose:rwin ' ,
' 3rd level of Right Win ' : ' compose:rwin-altgr ' ,
' Menu ' : ' compose:menu ' ,
' 3rd level of Menu ' : ' compose:menu-altgr ' ,
' Left Ctrl ' : ' compose:lctrl ' ,
' 3rd level of Left Ctrl ' : ' compose:lctrl-altgr ' ,
' Right Ctrl ' : ' compose:rctrl ' ,
' 3rd level of Right Ctrl ' : ' compose:rctrl-altgr ' ,
' Caps Lock ' : ' compose:caps ' ,
' 3rd level of Caps Lock ' : ' compose:caps-altgr ' ,
' The " < > " key ' : ' compose:102 ' ,
' 3rd level of the " < > " key ' : ' compose:102-altgr ' ,
' Pause ' : ' compose:paus ' ,
' PrtSc ' : ' compose:prsc ' ,
' Scroll Lock ' : ' compose:sclk '
}
if data [ ' Key Name ' ] not in data_map . keys ( ) :
2022-03-02 02:23:51 -07:00
log . error ( ' Compose Key not recognized ' , data )
2021-05-18 15:37:10 +00:00
return
parser = ConfigParser ( )
section = ' org/gnome/desktop/input-sources '
parser . add_section ( section )
parser . set ( section , ' xkb-options ' ,
" [ ' %s ' ] " % data_map [ data [ ' Key Name ' ] ] )
with open ( local_db , ' w ' ) as w :
parser . write ( w )
# Lock xkb-options
locks_dir = create_locks_dir ( self . test_dir )
2022-12-02 14:51:27 -07:00
conf_id = select_next_conf ( locks_dir )
lock = os . path . join ( locks_dir , ' %010d -input-sources ' % conf_id )
2021-05-18 15:37:10 +00:00
with open ( lock , ' w ' ) as w :
w . write ( ' /org/gnome/desktop/input-sources/xkb-options ' )
2022-03-02 02:23:51 -07:00
dconf_update ( self . test_dir )
2022-12-02 14:51:27 -07:00
return [ local_db , lock ]
2021-05-18 15:37:10 +00:00
def __apply_dim_idle ( self , data ) :
create_user_profile ( self . test_dir )
local_db_dir = create_local_db ( self . test_dir )
2022-12-02 14:51:27 -07:00
conf_id = select_next_conf ( local_db_dir , ' -power ' )
local_power_db = os . path . join ( local_db_dir , ' %010d -power ' % conf_id )
2021-05-18 15:37:10 +00:00
parser = ConfigParser ( )
section = ' org/gnome/settings-daemon/plugins/power '
parser . add_section ( section )
parser . set ( section , ' idle-dim ' , ' true ' )
parser . set ( section , ' idle-brightness ' , str ( data [ ' Dim Idle Brightness ' ] ) )
with open ( local_power_db , ' w ' ) as w :
parser . write ( w )
2022-12-02 14:51:27 -07:00
conf_id = select_next_conf ( local_db_dir , ' -session ' )
local_session_db = os . path . join ( local_db_dir , ' %010d -session ' % conf_id )
2021-05-18 15:37:10 +00:00
parser = ConfigParser ( )
section = ' org/gnome/desktop/session '
parser . add_section ( section )
parser . set ( section , ' idle-delay ' , ' uint32 %d ' % data [ ' Delay ' ] )
with open ( local_session_db , ' w ' ) as w :
parser . write ( w )
# Lock power-saving
locks_dir = create_locks_dir ( self . test_dir )
2022-12-02 14:51:27 -07:00
conf_id = select_next_conf ( locks_dir )
lock = os . path . join ( locks_dir , ' %010d -power-saving ' % conf_id )
2021-05-18 15:37:10 +00:00
with open ( lock , ' w ' ) as w :
w . write ( ' /org/gnome/settings-daemon/plugins/power/idle-dim \n ' )
w . write ( ' /org/gnome/settings-daemon/plugins/power/idle-brightness \n ' )
w . write ( ' /org/gnome/desktop/session/idle-delay ' )
2022-03-02 02:23:51 -07:00
dconf_update ( self . test_dir )
2022-12-02 14:51:27 -07:00
return [ local_power_db , local_session_db , lock ]
2021-05-18 15:37:10 +00:00
def __apply_specific_settings ( self , data ) :
create_user_profile ( self . test_dir )
locks_dir = create_locks_dir ( self . test_dir )
2022-12-02 14:51:27 -07:00
conf_id = select_next_conf ( locks_dir , ' -group-policy ' )
policy_file = os . path . join ( locks_dir , ' %010d -group-policy ' % conf_id )
2021-05-18 15:37:10 +00:00
with open ( policy_file , ' w ' ) as w :
for key in data . keys ( ) :
w . write ( ' %s \n ' % key )
2022-03-02 02:23:51 -07:00
dconf_update ( self . test_dir )
2022-12-02 14:51:27 -07:00
return [ policy_file ]
2021-05-18 15:37:10 +00:00
def __apply_whitelisted_account ( self , data ) :
create_user_profile ( self . test_dir )
local_db_dir = create_local_db ( self . test_dir )
locks_dir = create_locks_dir ( self . test_dir )
val = " [ ' %s ' ] " % " ' , ' " . join ( data . keys ( ) )
policy_files = self . __lockdown ( local_db_dir , locks_dir , ' goa ' ,
2022-12-02 14:51:27 -07:00
' whitelisted-providers ' , val ,
2021-05-18 15:37:10 +00:00
' org/gnome/online-accounts ' )
2022-03-02 02:23:51 -07:00
dconf_update ( self . test_dir )
2022-12-02 14:51:27 -07:00
return policy_files
2021-05-18 15:37:10 +00:00
def __apply_enabled_extensions ( self , data ) :
create_user_profile ( self . test_dir )
local_db_dir = create_local_db ( self . test_dir )
2022-12-02 14:51:27 -07:00
conf_id = select_next_conf ( local_db_dir )
policy_file = os . path . join ( local_db_dir , ' %010d -extensions ' % conf_id )
2021-05-18 15:37:10 +00:00
parser = ConfigParser ( )
section = ' org/gnome/shell '
parser . add_section ( section )
exts = data . keys ( )
parser . set ( section , ' enabled-extensions ' , " [ ' %s ' ] " % " ' , ' " . join ( exts ) )
parser . set ( section , ' development-tools ' , ' false ' )
with open ( policy_file , ' w ' ) as w :
parser . write ( w )
2022-03-02 02:23:51 -07:00
dconf_update ( self . test_dir )
2022-12-02 14:51:27 -07:00
return [ policy_file ]
2021-05-18 15:37:10 +00:00
def __lockdown ( self , local_db_dir , locks_dir , name , key , val ,
2022-12-02 14:51:27 -07:00
section = ' org/gnome/desktop/lockdown ' ) :
policy_files = [ ]
conf_id = select_next_conf ( local_db_dir )
policy_file = os . path . join ( local_db_dir ,
' %010d - %s ' % ( conf_id , name ) )
policy_files . append ( policy_file )
conf_id = select_next_conf ( locks_dir )
lock = os . path . join ( locks_dir , ' %010d - %s ' % ( conf_id , name ) )
policy_files . append ( lock )
2021-05-18 15:37:10 +00:00
parser = ConfigParser ( )
parser . add_section ( section )
parser . set ( section , key , val )
with open ( policy_file , ' w ' ) as w :
parser . write ( w )
with open ( lock , ' w ' ) as w :
w . write ( ' / %s / %s ' % ( section , key ) )
return policy_files
def __apply_enabled ( self , k ) :
2022-12-02 14:51:27 -07:00
policy_files = [ ]
2021-05-18 15:37:10 +00:00
create_user_profile ( self . test_dir )
local_db_dir = create_local_db ( self . test_dir )
locks_dir = create_locks_dir ( self . test_dir )
if k == ' Lock Down Enabled Extensions ' :
2022-12-02 14:51:27 -07:00
conf_id = select_next_conf ( locks_dir )
policy_file = os . path . join ( locks_dir , ' %010d -extensions ' % conf_id )
policy_files . append ( policy_file )
2021-05-18 15:37:10 +00:00
with open ( policy_file , ' w ' ) as w :
w . write ( ' /org/gnome/shell/enabled-extensions \n ' )
w . write ( ' /org/gnome/shell/development-tools ' )
elif k == ' Disable Printing ' :
policy_files = self . __lockdown ( local_db_dir , locks_dir , ' printing ' ,
2022-12-02 14:51:27 -07:00
' disable-printing ' , ' true ' )
2021-05-18 15:37:10 +00:00
elif k == ' Disable File Saving ' :
policy_files = self . __lockdown ( local_db_dir , locks_dir ,
' filesaving ' ,
2022-12-02 14:51:27 -07:00
' disable-save-to-disk ' , ' true ' )
2021-05-18 15:37:10 +00:00
elif k == ' Disable Command-Line Access ' :
policy_files = self . __lockdown ( local_db_dir , locks_dir , ' cmdline ' ,
2022-12-02 14:51:27 -07:00
' disable-command-line ' , ' true ' )
2021-05-18 15:37:10 +00:00
elif k == ' Disallow Login Using a Fingerprint ' :
policy_files = self . __lockdown ( local_db_dir , locks_dir ,
' fingerprintreader ' ,
' enable-fingerprint-authentication ' ,
2022-12-02 14:51:27 -07:00
' false ' ,
2021-05-18 15:37:10 +00:00
section = ' org/gnome/login-screen ' )
elif k == ' Disable User Logout ' :
policy_files = self . __lockdown ( local_db_dir , locks_dir , ' logout ' ,
2022-12-02 14:51:27 -07:00
' disable-log-out ' , ' true ' )
2021-05-18 15:37:10 +00:00
elif k == ' Disable User Switching ' :
policy_files = self . __lockdown ( local_db_dir , locks_dir , ' logout ' ,
2022-12-02 14:51:27 -07:00
' disable-user-switching ' , ' true ' )
2021-05-18 15:37:10 +00:00
elif k == ' Disable Repartitioning ' :
actions = ' /usr/share/polkit-1/actions '
udisk2 = glob ( os . path . join ( actions ,
' org.freedesktop.[u|U][d|D]isks2.policy ' ) )
if len ( udisk2 ) == 1 :
udisk2 = udisk2 [ 0 ]
else :
udisk2 = os . path . join ( actions ,
' org.freedesktop.UDisks2.policy ' )
udisk2_etc = os . path . join ( ' /etc/share/polkit-1/actions ' ,
os . path . basename ( udisk2 ) )
if self . test_dir is not None :
udisk2_etc = os . path . join ( self . test_dir , udisk2_etc [ 1 : ] )
os . makedirs ( os . path . dirname ( udisk2_etc ) , exist_ok = True )
xml_data = etree . ElementTree ( etree . Element ( ' policyconfig ' ) )
if os . path . exists ( udisk2 ) :
2023-08-29 16:29:55 +12:00
with open ( udisk2 , ' rb ' ) as f :
data = f . read ( )
2021-05-18 15:37:10 +00:00
existing_xml = etree . ElementTree ( etree . fromstring ( data ) )
root = xml_data . getroot ( )
root . append ( existing_xml . find ( ' vendor ' ) )
root . append ( existing_xml . find ( ' vendor_url ' ) )
root . append ( existing_xml . find ( ' icon_name ' ) )
else :
vendor = etree . SubElement ( xml_data . getroot ( ) , ' vendor ' )
vendor . text = ' The Udisks Project '
vendor_url = etree . SubElement ( xml_data . getroot ( ) , ' vendor_url ' )
vendor_url . text = ' https://github.com/storaged-project/udisks '
icon_name = etree . SubElement ( xml_data . getroot ( ) , ' icon_name ' )
icon_name . text = ' drive-removable-media '
action = etree . SubElement ( xml_data . getroot ( ) , ' action ' )
action . attrib [ ' id ' ] = ' org.freedesktop.udisks2.modify-device '
description = etree . SubElement ( action , ' description ' )
description . text = ' Modify the drive settings '
message = etree . SubElement ( action , ' message ' )
message . text = ' Authentication is required to modify drive settings '
defaults = etree . SubElement ( action , ' defaults ' )
allow_any = etree . SubElement ( defaults , ' allow_any ' )
allow_any . text = ' no '
allow_inactive = etree . SubElement ( defaults , ' allow_inactive ' )
allow_inactive . text = ' no '
allow_active = etree . SubElement ( defaults , ' allow_active ' )
allow_active . text = ' yes '
with open ( udisk2_etc , ' wb ' ) as w :
xml_data . write ( w , encoding = ' UTF-8 ' , xml_declaration = True )
policy_files . append ( udisk2_etc )
else :
2022-03-02 02:23:51 -07:00
log . error ( ' Unable to apply ' , k )
2021-05-18 15:37:10 +00:00
return
2022-03-02 02:23:51 -07:00
dconf_update ( self . test_dir )
2022-12-02 14:51:27 -07:00
return policy_files
2021-05-18 15:37:10 +00:00
def __clean_data ( self , k ) :
data = self . lock_down_settings [ k ]
return { i : data [ i ] for i in data . keys ( ) if i != ' Enabled ' }
2021-05-18 15:35:24 +00:00
def process_group_policy ( self , deleted_gpo_list , changed_gpo_list ,
test_dir = None ) :
2021-05-18 15:37:10 +00:00
if test_dir is not None :
self . test_dir = test_dir
for guid , settings in deleted_gpo_list :
if str ( self ) in settings :
for attribute , value in settings [ str ( self ) ] . items ( ) :
2022-12-02 14:51:27 -07:00
self . unapply ( guid , attribute , value , sep = ' ; ' )
dconf_update ( test_dir )
2021-05-18 15:37:10 +00:00
for gpo in changed_gpo_list :
if gpo . file_sys_path :
section_name = ' GNOME Settings \\ Lock Down Settings '
pol_file = ' MACHINE/Registry.pol '
path = os . path . join ( gpo . file_sys_path , pol_file )
pol_conf = self . parse ( path )
if not pol_conf :
continue
for e in pol_conf . entries :
if e . keyname . startswith ( section_name ) and e . data and \
' **delvals. ' not in e . valuename :
for k in self . keys :
if e . keyname . endswith ( k ) :
self . __add_lockdown_data ( k , e )
break
else :
self . __enable_lockdown_data ( e )
for k in self . lock_down_settings . keys ( ) :
# Ignore disabled preferences
if not self . lock_down_settings [ k ] [ ' Enabled ' ] :
2022-12-02 14:51:27 -07:00
# Unapply the disabled preference if previously applied
self . clean ( gpo . name , remove = k )
2021-05-18 15:37:10 +00:00
continue
# Apply using the appropriate applier
2022-12-02 14:51:27 -07:00
data = str ( self . lock_down_settings [ k ] )
value_hash = self . generate_value_hash ( data )
2021-05-18 15:37:10 +00:00
if k == self . keys [ 0 ] :
2022-12-02 14:51:27 -07:00
self . apply ( gpo . name , k , value_hash ,
self . __apply_compose_key ,
self . __clean_data ( k ) , sep = ' ; ' )
2021-05-18 15:37:10 +00:00
elif k == self . keys [ 1 ] :
2022-12-02 14:51:27 -07:00
self . apply ( gpo . name , k , value_hash ,
self . __apply_dim_idle ,
self . __clean_data ( k ) , sep = ' ; ' )
2021-05-18 15:37:10 +00:00
elif k == self . keys [ 2 ] :
2022-12-02 14:51:27 -07:00
self . apply ( gpo . name , k , value_hash ,
self . __apply_specific_settings ,
self . __clean_data ( k ) , sep = ' ; ' )
2021-05-18 15:37:10 +00:00
elif k == self . keys [ 3 ] :
2022-12-02 14:51:27 -07:00
self . apply ( gpo . name , k , value_hash ,
self . __apply_whitelisted_account ,
self . __clean_data ( k ) , sep = ' ; ' )
2021-05-18 15:37:10 +00:00
elif k == self . keys [ 4 ] :
2022-12-02 14:51:27 -07:00
self . apply ( gpo . name , k , value_hash ,
self . __apply_enabled_extensions ,
self . __clean_data ( k ) , sep = ' ; ' )
2021-05-18 15:37:10 +00:00
else :
2022-12-02 14:51:27 -07:00
self . apply ( gpo . name , k , value_hash ,
self . __apply_enabled ,
k , sep = ' ; ' )
# Unapply any policy that has been removed
self . clean ( gpo . name , keep = self . lock_down_settings . keys ( ) )
2021-05-18 15:35:24 +00:00
def rsop ( self , gpo ) :
output = { }
2021-05-18 15:37:10 +00:00
if gpo . file_sys_path :
section_name = ' GNOME Settings \\ Lock Down Settings '
pol_file = ' MACHINE/Registry.pol '
path = os . path . join ( gpo . file_sys_path , pol_file )
pol_conf = self . parse ( path )
if not pol_conf :
return output
for e in pol_conf . entries :
if e . keyname . startswith ( section_name ) and e . data and \
' **delvals. ' not in e . valuename :
for k in self . keys :
if e . keyname . endswith ( k ) :
self . __add_lockdown_data ( k , e )
break
else :
self . __enable_lockdown_data ( e )
for k in self . lock_down_settings . keys ( ) :
if self . lock_down_settings [ k ] [ ' Enabled ' ] :
if len ( self . lock_down_settings [ k ] ) > 1 :
data = self . __clean_data ( k )
if all ( [ i == data [ i ] for i in data . keys ( ) ] ) :
output [ k ] = list ( data . keys ( ) )
else :
output [ k ] = data
else :
output [ k ] = self . lock_down_settings [ k ]
2021-05-18 15:35:24 +00:00
return output