2014-01-31 04:27:05 +04:00
# Reads important GPO parameters and updates Samba
# Copyright (C) Luke Morrison <luc785@.hotmail.com> 2013
#
# 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
2020-08-28 17:38:41 +03:00
import os , shutil
2018-01-08 17:17:29 +03:00
import errno
2017-05-25 16:27:27 +03:00
import tdb
2021-07-20 18:13:06 +03:00
import pwd
2014-01-31 04:27:05 +04:00
sys . path . insert ( 0 , " bin/python " )
2023-08-25 02:09:52 +03:00
from samba import WERRORError
2020-08-21 00:51:47 +03:00
from configparser import ConfigParser
2020-07-04 04:53:34 +03:00
from io import StringIO
2021-07-19 20:36:09 +03:00
import traceback
2020-09-11 23:29:46 +03:00
from samba . common import get_bytes
2017-02-25 00:19:48 +03:00
from abc import ABCMeta , abstractmethod
2017-06-08 20:47:57 +03:00
import xml . etree . ElementTree as etree
2017-11-21 13:44:12 +03:00
import re
2018-03-29 17:32:02 +03:00
from samba . net import Net
from samba . dcerpc import nbt
2018-12-14 00:37:11 +03:00
from samba . samba3 import libsmb_samba_internal as libsmb
2018-03-29 17:32:02 +03:00
import samba . gpo as gpo
2018-06-13 23:45:09 +03:00
from uuid import UUID
2018-01-08 17:17:29 +03:00
from tempfile import NamedTemporaryFile
2018-08-09 18:47:38 +03:00
from samba . dcerpc import preg
2023-08-25 02:09:52 +03:00
from samba . ndr import ndr_unpack
2020-06-03 15:02:37 +03:00
from samba . credentials import SMB_SIGNING_REQUIRED
2022-03-02 12:23:51 +03:00
from samba . gp . util . logging import log
2022-11-18 23:59:32 +03:00
from hashlib import blake2b
2022-12-09 19:40:34 +03:00
import numbers
from samba . common import get_string
2023-03-14 20:21:02 +03:00
from samba . samdb import SamDB
from samba . auth import system_session
import ldb
2023-08-25 02:09:52 +03:00
from samba . dsdb import UF_WORKSTATION_TRUST_ACCOUNT , UF_SERVER_TRUST_ACCOUNT , GPLINK_OPT_ENFORCE , GPLINK_OPT_DISABLE , GPO_BLOCK_INHERITANCE
2023-03-14 20:21:02 +03:00
from samba . auth import AUTH_SESSION_INFO_DEFAULT_GROUPS , AUTH_SESSION_INFO_AUTHENTICATED , AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
from samba . dcerpc import security
import samba . security
2024-02-21 11:10:47 +03:00
from samba . dcerpc import nbt
2023-03-11 00:30:17 +03:00
from datetime import datetime
2023-03-14 20:21:02 +03:00
2017-06-08 20:47:57 +03:00
2017-06-13 01:00:38 +03:00
try :
from enum import Enum
GPOSTATE = Enum ( ' GPOSTATE ' , ' APPLY ENFORCE UNAPPLY ' )
except ImportError :
class GPOSTATE :
APPLY = 1
ENFORCE = 2
UNAPPLY = 3
2018-07-30 09:20:39 +03:00
2017-06-08 20:47:57 +03:00
class gp_log :
2023-12-14 05:14:27 +03:00
""" Log settings overwritten by gpo apply
2017-11-20 00:28:33 +03:00
The gp_log is an xml file that stores a history of gpo changes ( and the
original setting value ) .
2017-06-08 20:47:57 +03:00
The log is organized like so :
< gp >
< user name = " KDC-1$ " >
< applylog >
< guid count = " 0 " value = " { 31B2F340-016D-11D2-945F-00C04FB984F9} " / >
< / applylog >
< guid value = " { 31B2F340-016D-11D2-945F-00C04FB984F9} " >
< gp_ext name = " System Access " >
< attribute name = " minPwdAge " > - 864000000000 < / attribute >
< attribute name = " maxPwdAge " > - 36288000000000 < / attribute >
< attribute name = " minPwdLength " > 7 < / attribute >
< attribute name = " pwdProperties " > 1 < / attribute >
< / gp_ext >
< gp_ext name = " Kerberos Policy " >
< attribute name = " ticket_lifetime " > 1 d < / attribute >
< attribute name = " renew_lifetime " / >
< attribute name = " clockskew " > 300 < / attribute >
< / gp_ext >
< / guid >
< / user >
< / gp >
2017-11-20 00:28:33 +03:00
Each guid value contains a list of extensions , which contain a list of
attributes . The guid value represents a GPO . The attributes are the values
of those settings prior to the application of the GPO .
The list of guids is enclosed within a user name , which represents the user
the settings were applied to . This user may be the samaccountname of the
local computer , which implies that these are machine policies .
The applylog keeps track of the order in which the GPOs were applied , so
that they can be rolled back in reverse , returning the machine to the state
prior to policy application .
2023-12-14 05:14:27 +03:00
"""
2017-06-08 20:47:57 +03:00
def __init__ ( self , user , gpostore , db_log = None ) :
2023-12-14 05:14:27 +03:00
""" Initialize the gp_log
2017-11-20 00:28:33 +03:00
param user - the username ( or machine name ) that policies are
being applied to
param gpostore - the GPOStorage obj which references the tdb which
contains gp_logs
2017-06-08 20:47:57 +03:00
param db_log - ( optional ) a string to initialize the gp_log
2023-12-14 05:14:27 +03:00
"""
2017-06-13 01:00:38 +03:00
self . _state = GPOSTATE . APPLY
2017-06-08 20:47:57 +03:00
self . gpostore = gpostore
self . username = user
if db_log :
self . gpdb = etree . fromstring ( db_log )
else :
self . gpdb = etree . Element ( ' gp ' )
2017-11-20 16:41:19 +03:00
self . user = user
user_obj = self . gpdb . find ( ' user[@name= " %s " ] ' % user )
if user_obj is None :
user_obj = etree . SubElement ( self . gpdb , ' user ' )
user_obj . attrib [ ' name ' ] = user
2017-06-08 20:47:57 +03:00
2017-06-13 01:00:38 +03:00
def state ( self , value ) :
2023-12-14 05:14:27 +03:00
""" Policy application state
2017-06-13 01:00:38 +03:00
param value - APPLY , ENFORCE , or UNAPPLY
2017-11-20 00:28:33 +03:00
The behavior of the gp_log depends on whether we are applying policy ,
enforcing policy , or unapplying policy . During an apply , old settings
are recorded in the log . During an enforce , settings are being applied
but the gp_log does not change . During an unapply , additions to the log
should be ignored ( since function calls to apply settings are actually
2017-06-13 01:00:38 +03:00
reverting policy ) , but removals from the log are allowed .
2023-12-14 05:14:27 +03:00
"""
2017-06-13 01:00:38 +03:00
# If we're enforcing, but we've unapplied, apply instead
if value == GPOSTATE . ENFORCE :
2017-11-20 16:41:19 +03:00
user_obj = self . gpdb . find ( ' user[@name= " %s " ] ' % self . user )
apply_log = user_obj . find ( ' applylog ' )
2017-06-13 01:00:38 +03:00
if apply_log is None or len ( apply_log ) == 0 :
self . _state = GPOSTATE . APPLY
else :
self . _state = value
else :
self . _state = value
2022-11-18 23:59:32 +03:00
def get_state ( self ) :
2023-12-14 05:14:27 +03:00
""" Check the GPOSTATE
"""
2022-11-18 23:59:32 +03:00
return self . _state
2017-06-08 20:47:57 +03:00
def set_guid ( self , guid ) :
2023-12-14 05:14:27 +03:00
""" Log to a different GPO guid
2017-11-20 00:28:33 +03:00
param guid - guid value of the GPO from which we ' re applying
policy
2023-12-14 05:14:27 +03:00
"""
2017-11-20 16:41:19 +03:00
self . guid = guid
user_obj = self . gpdb . find ( ' user[@name= " %s " ] ' % self . user )
obj = user_obj . find ( ' guid[@value= " %s " ] ' % guid )
if obj is None :
obj = etree . SubElement ( user_obj , ' guid ' )
obj . attrib [ ' value ' ] = guid
2017-06-13 01:00:38 +03:00
if self . _state == GPOSTATE . APPLY :
2017-11-20 16:41:19 +03:00
apply_log = user_obj . find ( ' applylog ' )
2017-06-13 01:00:38 +03:00
if apply_log is None :
2017-11-20 16:41:19 +03:00
apply_log = etree . SubElement ( user_obj , ' applylog ' )
2018-05-15 17:37:08 +03:00
prev = apply_log . find ( ' guid[@value= " %s " ] ' % guid )
if prev is None :
item = etree . SubElement ( apply_log , ' guid ' )
2018-07-30 09:18:25 +03:00
item . attrib [ ' count ' ] = ' %d ' % ( len ( apply_log ) - 1 )
2018-05-15 17:37:08 +03:00
item . attrib [ ' value ' ] = guid
2017-06-08 20:47:57 +03:00
def store ( self , gp_ext_name , attribute , old_val ) :
2023-12-14 05:14:27 +03:00
""" Store an attribute in the gp_log
2017-06-08 20:47:57 +03:00
param gp_ext_name - Name of the extension applying policy
param attribute - The attribute being modified
2017-11-20 00:28:33 +03:00
param old_val - The value of the attribute prior to policy
application
2023-12-14 05:14:27 +03:00
"""
2017-06-13 01:00:38 +03:00
if self . _state == GPOSTATE . UNAPPLY or self . _state == GPOSTATE . ENFORCE :
return None
2017-11-20 16:41:19 +03:00
user_obj = self . gpdb . find ( ' user[@name= " %s " ] ' % self . user )
guid_obj = user_obj . find ( ' guid[@value= " %s " ] ' % self . guid )
assert guid_obj is not None , " gpo guid was not set "
ext = guid_obj . find ( ' gp_ext[@name= " %s " ] ' % gp_ext_name )
2017-06-08 20:47:57 +03:00
if ext is None :
2017-11-20 16:41:19 +03:00
ext = etree . SubElement ( guid_obj , ' gp_ext ' )
2017-06-08 20:47:57 +03:00
ext . attrib [ ' name ' ] = gp_ext_name
attr = ext . find ( ' attribute[@name= " %s " ] ' % attribute )
if attr is None :
attr = etree . SubElement ( ext , ' attribute ' )
attr . attrib [ ' name ' ] = attribute
2017-12-01 21:18:55 +03:00
attr . text = old_val
2017-06-08 20:47:57 +03:00
def retrieve ( self , gp_ext_name , attribute ) :
2023-12-14 05:14:27 +03:00
""" Retrieve a stored attribute from the gp_log
2017-06-08 20:47:57 +03:00
param gp_ext_name - Name of the extension which applied policy
param attribute - The attribute being retrieved
2017-11-20 00:28:33 +03:00
return - The value of the attribute prior to policy
application
2023-12-14 05:14:27 +03:00
"""
2017-11-20 16:41:19 +03:00
user_obj = self . gpdb . find ( ' user[@name= " %s " ] ' % self . user )
guid_obj = user_obj . find ( ' guid[@value= " %s " ] ' % self . guid )
assert guid_obj is not None , " gpo guid was not set "
ext = guid_obj . find ( ' gp_ext[@name= " %s " ] ' % gp_ext_name )
2017-06-08 20:47:57 +03:00
if ext is not None :
attr = ext . find ( ' attribute[@name= " %s " ] ' % attribute )
if attr is not None :
return attr . text
return None
2022-11-18 23:59:32 +03:00
def retrieve_all ( self , gp_ext_name ) :
2023-12-14 05:14:27 +03:00
""" Retrieve all stored attributes for this user, GPO guid, and CSE
2022-11-18 23:59:32 +03:00
param gp_ext_name - Name of the extension which applied policy
return - The values of the attributes prior to policy
application
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
user_obj = self . gpdb . find ( ' user[@name= " %s " ] ' % self . user )
guid_obj = user_obj . find ( ' guid[@value= " %s " ] ' % self . guid )
assert guid_obj is not None , " gpo guid was not set "
ext = guid_obj . find ( ' gp_ext[@name= " %s " ] ' % gp_ext_name )
if ext is not None :
attrs = ext . findall ( ' attribute ' )
return { attr . attrib [ ' name ' ] : attr . text for attr in attrs }
return { }
2018-05-18 00:56:15 +03:00
def get_applied_guids ( self ) :
2023-12-14 05:14:27 +03:00
""" Return a list of applied ext guids
2018-05-18 00:56:15 +03:00
return - List of guids for gpos that have applied settings
to the system .
2023-12-14 05:14:27 +03:00
"""
2018-05-18 00:56:15 +03:00
guids = [ ]
user_obj = self . gpdb . find ( ' user[@name= " %s " ] ' % self . user )
if user_obj is not None :
apply_log = user_obj . find ( ' applylog ' )
2018-08-29 07:39:51 +03:00
if apply_log is not None :
guid_objs = apply_log . findall ( ' guid[@count] ' )
guids_by_count = [ ( g . get ( ' count ' ) , g . get ( ' value ' ) )
for g in guid_objs ]
guids_by_count . sort ( reverse = True )
guids . extend ( guid for count , guid in guids_by_count )
2018-05-18 00:56:15 +03:00
return guids
def get_applied_settings ( self , guids ) :
2023-12-14 05:14:27 +03:00
""" Return a list of applied ext guids
2018-05-18 00:56:15 +03:00
return - List of tuples containing the guid of a gpo , then
a dictionary of policies and their values prior
policy application . These are sorted so that the
most recently applied settings are removed first .
2023-12-14 05:14:27 +03:00
"""
2018-05-18 00:56:15 +03:00
ret = [ ]
user_obj = self . gpdb . find ( ' user[@name= " %s " ] ' % self . user )
for guid in guids :
guid_settings = user_obj . find ( ' guid[@value= " %s " ] ' % guid )
exts = guid_settings . findall ( ' gp_ext ' )
settings = { }
for ext in exts :
attr_dict = { }
attrs = ext . findall ( ' attribute ' )
for attr in attrs :
attr_dict [ attr . attrib [ ' name ' ] ] = attr . text
settings [ ext . attrib [ ' name ' ] ] = attr_dict
ret . append ( ( guid , settings ) )
return ret
2017-06-08 20:47:57 +03:00
def delete ( self , gp_ext_name , attribute ) :
2023-12-14 05:14:27 +03:00
""" Remove an attribute from the gp_log
2017-11-20 00:28:33 +03:00
param gp_ext_name - name of extension from which to remove the
attribute
2017-06-08 20:47:57 +03:00
param attribute - attribute to remove
2023-12-14 05:14:27 +03:00
"""
2017-11-20 16:41:19 +03:00
user_obj = self . gpdb . find ( ' user[@name= " %s " ] ' % self . user )
guid_obj = user_obj . find ( ' guid[@value= " %s " ] ' % self . guid )
assert guid_obj is not None , " gpo guid was not set "
ext = guid_obj . find ( ' gp_ext[@name= " %s " ] ' % gp_ext_name )
2017-06-08 20:47:57 +03:00
if ext is not None :
attr = ext . find ( ' attribute[@name= " %s " ] ' % attribute )
if attr is not None :
ext . remove ( attr )
if len ( ext ) == 0 :
2017-11-20 16:41:19 +03:00
guid_obj . remove ( ext )
2017-06-08 20:47:57 +03:00
def commit ( self ) :
2023-12-14 05:14:27 +03:00
""" Write gp_log changes to disk """
2017-06-08 20:47:57 +03:00
self . gpostore . store ( self . username , etree . tostring ( self . gpdb , ' utf-8 ' ) )
2018-07-30 09:20:39 +03:00
2017-06-08 20:47:57 +03:00
class GPOStorage :
def __init__ ( self , log_file ) :
if os . path . isfile ( log_file ) :
self . log = tdb . open ( log_file )
2017-05-25 16:27:27 +03:00
else :
2018-08-29 04:30:59 +03:00
self . log = tdb . Tdb ( log_file , 0 , tdb . DEFAULT , os . O_CREAT | os . O_RDWR )
2017-05-25 16:27:27 +03:00
2017-06-08 20:47:57 +03:00
def start ( self ) :
self . log . transaction_start ( )
def get_int ( self , key ) :
2017-05-25 16:27:27 +03:00
try :
2018-11-06 22:55:22 +03:00
return int ( self . log . get ( get_bytes ( key ) ) )
2017-05-25 16:27:27 +03:00
except TypeError :
2017-06-08 20:47:57 +03:00
return None
def get ( self , key ) :
2018-11-06 22:55:22 +03:00
return self . log . get ( get_bytes ( key ) )
2017-06-08 20:47:57 +03:00
def get_gplog ( self , user ) :
2018-11-06 22:55:22 +03:00
return gp_log ( user , self , self . log . get ( get_bytes ( user ) ) )
2017-06-08 20:47:57 +03:00
def store ( self , key , val ) :
2018-11-06 22:55:22 +03:00
self . log . store ( get_bytes ( key ) , get_bytes ( val ) )
2017-06-08 20:47:57 +03:00
def cancel ( self ) :
self . log . transaction_cancel ( )
2017-05-25 16:27:27 +03:00
2017-06-08 20:47:57 +03:00
def delete ( self , key ) :
2018-11-06 22:55:22 +03:00
self . log . delete ( get_bytes ( key ) )
2017-05-25 16:27:27 +03:00
def commit ( self ) :
2017-06-08 20:47:57 +03:00
self . log . transaction_commit ( )
2017-05-25 16:27:27 +03:00
def __del__ ( self ) :
2017-06-08 20:47:57 +03:00
self . log . close ( )
2017-05-25 16:27:27 +03:00
2018-07-30 09:20:39 +03:00
2014-01-31 04:27:05 +04:00
class gp_ext ( object ) :
2017-02-25 00:19:48 +03:00
__metaclass__ = ABCMeta
2022-03-02 12:23:51 +03:00
def __init__ ( self , lp , creds , username , store ) :
2018-05-16 19:58:29 +03:00
self . lp = lp
self . creds = creds
2021-07-20 18:13:06 +03:00
self . username = username
self . gp_db = store . get_gplog ( username )
2018-03-29 18:07:53 +03:00
2018-05-09 22:16:38 +03:00
@abstractmethod
def process_group_policy ( self , deleted_gpo_list , changed_gpo_list ) :
pass
2017-02-25 00:19:48 +03:00
@abstractmethod
2018-03-29 17:05:21 +03:00
def read ( self , policy ) :
2017-02-25 00:19:48 +03:00
pass
2014-01-31 04:27:05 +04:00
2018-05-16 19:58:29 +03:00
def parse ( self , afile ) :
2018-05-16 19:37:09 +03:00
local_path = self . lp . cache_path ( ' gpo_cache ' )
data_file = os . path . join ( local_path , check_safe_path ( afile ) . upper ( ) )
if os . path . exists ( data_file ) :
2018-08-09 18:47:38 +03:00
return self . read ( data_file )
2018-05-16 19:37:09 +03:00
return None
2018-03-29 17:05:21 +03:00
2017-06-08 20:47:57 +03:00
@abstractmethod
def __str__ ( self ) :
pass
2014-01-31 04:27:05 +04:00
2020-07-06 17:25:23 +03:00
@abstractmethod
def rsop ( self , gpo ) :
return { }
2018-07-30 09:20:39 +03:00
2018-03-29 17:25:05 +03:00
class gp_inf_ext ( gp_ext ) :
2018-08-09 18:47:38 +03:00
def read ( self , data_file ) :
2023-08-29 07:29:55 +03:00
with open ( data_file , ' rb ' ) as f :
policy = f . read ( )
2020-08-21 00:51:47 +03:00
inf_conf = ConfigParser ( interpolation = None )
2018-07-30 09:18:03 +03:00
inf_conf . optionxform = str
2018-03-29 17:25:05 +03:00
try :
2023-08-29 07:30:08 +03:00
inf_conf . read_file ( StringIO ( policy . decode ( ) ) )
2020-07-06 17:13:57 +03:00
except UnicodeDecodeError :
2023-08-29 07:30:08 +03:00
inf_conf . read_file ( StringIO ( policy . decode ( ' utf-16 ' ) ) )
2018-05-18 01:23:51 +03:00
return inf_conf
2018-03-29 17:25:05 +03:00
2018-07-30 09:21:29 +03:00
2018-08-09 18:47:38 +03:00
class gp_pol_ext ( gp_ext ) :
def read ( self , data_file ) :
2023-08-29 07:29:55 +03:00
with open ( data_file , ' rb ' ) as f :
raw = f . read ( )
2018-08-09 18:47:38 +03:00
return ndr_unpack ( preg . file , raw )
2020-11-03 20:44:27 +03:00
class gp_xml_ext ( gp_ext ) :
def read ( self , data_file ) :
2023-08-29 07:29:55 +03:00
with open ( data_file , ' rb ' ) as f :
raw = f . read ( )
2020-11-03 20:44:27 +03:00
try :
return etree . fromstring ( raw . decode ( ) )
except UnicodeDecodeError :
return etree . fromstring ( raw . decode ( ' utf-16 ' ) )
2022-11-18 23:59:32 +03:00
class gp_applier ( object ) :
2023-12-14 05:14:27 +03:00
""" Group Policy Applier/Unapplier/Modifier
2022-11-18 23:59:32 +03:00
The applier defines functions for monitoring policy application ,
removal , and modification . It must be a multi - derived class paired
with a subclass of gp_ext .
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
__metaclass__ = ABCMeta
def cache_add_attribute ( self , guid , attribute , value ) :
2023-12-14 05:14:27 +03:00
""" Add an attribute and value to the Group Policy cache
2022-11-18 23:59:32 +03:00
guid - The GPO guid which applies this policy
attribute - The attribute name of the policy being applied
value - The value of the policy being applied
Normally called by the subclass apply ( ) function after applying policy .
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
self . gp_db . set_guid ( guid )
self . gp_db . store ( str ( self ) , attribute , value )
self . gp_db . commit ( )
def cache_remove_attribute ( self , guid , attribute ) :
2023-12-14 05:14:27 +03:00
""" Remove an attribute from the Group Policy cache
2022-11-18 23:59:32 +03:00
guid - The GPO guid which applies this policy
attribute - The attribute name of the policy being unapplied
Normally called by the subclass unapply ( ) function when removing old
policy .
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
self . gp_db . set_guid ( guid )
self . gp_db . delete ( str ( self ) , attribute )
self . gp_db . commit ( )
def cache_get_attribute_value ( self , guid , attribute ) :
2023-12-14 05:14:27 +03:00
""" Retrieve the value stored in the cache for the given attribute
2022-11-18 23:59:32 +03:00
guid - The GPO guid which applies this policy
attribute - The attribute name of the policy
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
self . gp_db . set_guid ( guid )
return self . gp_db . retrieve ( str ( self ) , attribute )
def cache_get_all_attribute_values ( self , guid ) :
2023-12-14 05:14:27 +03:00
""" Retrieve all attribute/values currently stored for this gpo+policy
2022-11-18 23:59:32 +03:00
guid - The GPO guid which applies this policy
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
self . gp_db . set_guid ( guid )
return self . gp_db . retrieve_all ( str ( self ) )
def cache_get_apply_state ( self ) :
2023-12-14 05:14:27 +03:00
""" Return the current apply state
2022-11-18 23:59:32 +03:00
return - APPLY | ENFORCE | UNAPPLY
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
return self . gp_db . get_state ( )
def generate_attribute ( self , name , * args ) :
2023-12-14 05:14:27 +03:00
""" Generate an attribute name from arbitrary data
2022-11-18 23:59:32 +03:00
name - A name to ensure uniqueness
args - Any arbitrary set of args , str or bytes
return - A blake2b digest of the data , the attribute
The importance here is the digest of the data makes the attribute
reproducible and uniquely identifies it . Hashing the name with
2023-06-06 14:13:15 +03:00
the data ensures we don ' t falsely identify a match which is the same
2022-11-18 23:59:32 +03:00
text in a different file . Using this attribute generator is optional .
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
data = b ' ' . join ( [ get_bytes ( arg ) for arg in [ * args ] ] )
return blake2b ( get_bytes ( name ) + data ) . hexdigest ( )
def generate_value_hash ( self , * args ) :
2023-12-14 05:14:27 +03:00
""" Generate a unique value which identifies value changes
2022-11-18 23:59:32 +03:00
args - Any arbitrary set of args , str or bytes
return - A blake2b digest of the data , the value represented
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
data = b ' ' . join ( [ get_bytes ( arg ) for arg in [ * args ] ] )
return blake2b ( data ) . hexdigest ( )
@abstractmethod
def unapply ( self , guid , attribute , value ) :
2023-12-14 05:14:27 +03:00
""" Group Policy Unapply
2022-11-18 23:59:32 +03:00
guid - The GPO guid which applies this policy
attribute - The attribute name of the policy being unapplied
value - The value of the policy being unapplied
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
pass
@abstractmethod
def apply ( self , guid , attribute , applier_func , * args ) :
2023-12-14 05:14:27 +03:00
""" Group Policy Apply
2022-11-18 23:59:32 +03:00
guid - The GPO guid which applies this policy
attribute - The attribute name of the policy being applied
applier_func - An applier function which takes variable args
args - The variable arguments to pass to applier_func
The applier_func function MUST return the value of the policy being
applied . It ' s important that implementations of `apply` check for and
first unapply any changed policy . See for example calls to
` cache_get_all_attribute_values ( ) ` which searches for all policies
applied by this GPO for this Client Side Extension ( CSE ) .
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
pass
2023-02-23 05:54:37 +03:00
def clean ( self , guid , keep = None , remove = None , * * kwargs ) :
2023-12-14 05:14:27 +03:00
""" Cleanup old removed attributes
2022-11-18 23:59:32 +03:00
keep - A list of attributes to keep
remove - A single attribute to remove , or a list of attributes to
remove
kwargs - Additional keyword args required by the subclass unapply
function
This is only necessary for CSEs which provide multiple attributes .
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
# Clean syntax is, either provide a single remove attribute,
# or a list of either removal attributes or keep attributes.
2023-02-23 05:54:37 +03:00
if keep is None :
keep = [ ]
if remove is None :
remove = [ ]
2022-11-18 23:59:32 +03:00
if type ( remove ) != list :
value = self . cache_get_attribute_value ( guid , remove )
if value is not None :
self . unapply ( guid , remove , value , * * kwargs )
else :
old_vals = self . cache_get_all_attribute_values ( guid )
for attribute , value in old_vals . items ( ) :
if ( len ( remove ) > 0 and attribute in remove ) or \
( len ( keep ) > 0 and attribute not in keep ) :
self . unapply ( guid , attribute , value , * * kwargs )
2023-01-10 01:19:48 +03:00
class gp_misc_applier ( gp_applier ) :
2023-12-14 05:14:27 +03:00
""" Group Policy Miscellaneous Applier/Unapplier/Modifier
"""
2023-01-10 01:19:48 +03:00
def generate_value ( self , * * kwargs ) :
data = etree . Element ( ' data ' )
for k , v in kwargs . items ( ) :
arg = etree . SubElement ( data , k )
arg . text = get_string ( v )
return get_string ( etree . tostring ( data , ' utf-8 ' ) )
def parse_value ( self , value ) :
vals = { }
try :
data = etree . fromstring ( value )
except etree . ParseError :
# If parsing fails, then it's an old cache value
return { ' old_val ' : value }
except TypeError :
return { }
itr = data . iter ( )
next ( itr ) # Skip the top element
for item in itr :
vals [ item . tag ] = item . text
return vals
2022-11-18 23:59:32 +03:00
class gp_file_applier ( gp_applier ) :
2023-12-14 05:14:27 +03:00
""" Group Policy File Applier/Unapplier/Modifier
2022-11-18 23:59:32 +03:00
Subclass of abstract class gp_applier for monitoring policy applied
via a file .
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
def __generate_value ( self , value_hash , files , sep ) :
data = [ value_hash ]
data . extend ( files )
return sep . join ( data )
def __parse_value ( self , value , sep ) :
2023-12-14 05:14:27 +03:00
""" Parse a value
2022-11-18 23:59:32 +03:00
return - A unique HASH , followed by the file list
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
if value is None :
return None , [ ]
data = value . split ( sep )
if ' / ' in data [ 0 ] :
# The first element is not a hash, but a filename. This is a
# legacy value.
return None , data
else :
return data [ 0 ] , data [ 1 : ] if len ( data ) > 1 else [ ]
def unapply ( self , guid , attribute , files , sep = ' : ' ) :
# If the value isn't a list of files, parse value from the log
if type ( files ) != list :
_ , files = self . __parse_value ( files , sep )
for file in files :
if os . path . exists ( file ) :
os . unlink ( file )
self . cache_remove_attribute ( guid , attribute )
def apply ( self , guid , attribute , value_hash , applier_func , * args , sep = ' : ' ) :
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
applier_func MUST return a list of files created by the applier .
This applier is for policies which only apply to a single file ( with
a couple small exceptions ) . This applier will remove any policy applied
by this GPO which doesn ' t match the new policy.
2023-12-14 05:14:27 +03:00
"""
2022-11-18 23:59:32 +03:00
# If the policy has changed, unapply, then apply new policy
old_val = self . cache_get_attribute_value ( guid , attribute )
# Ignore removal if this policy is applied and hasn't changed
old_val_hash , old_val_files = self . __parse_value ( old_val , sep )
2023-08-25 04:55:23 +03:00
if ( old_val_hash != value_hash or
2022-12-07 19:51:12 +03:00
self . cache_get_apply_state ( ) == GPOSTATE . ENFORCE ) or \
not all ( [ os . path . exists ( f ) for f in old_val_files ] ) :
2022-11-18 23:59:32 +03:00
self . unapply ( guid , attribute , old_val_files )
else :
# If policy is already applied, skip application
return
# Apply the policy and log the changes
files = applier_func ( * args )
new_value = self . __generate_value ( value_hash , files , sep )
self . cache_add_attribute ( guid , attribute , new_value )
2023-12-14 05:14:27 +03:00
""" Fetch the hostname of a writable DC """
2018-07-30 09:20:39 +03:00
2018-03-29 17:32:02 +03:00
def get_dc_hostname ( creds , lp ) :
net = Net ( creds = creds , lp = lp )
cldap_ret = net . finddc ( domain = lp . get ( ' realm ' ) , flags = ( nbt . NBT_SERVER_LDAP |
2018-07-30 09:16:12 +03:00
nbt . NBT_SERVER_DS ) )
2018-03-29 17:32:02 +03:00
return cldap_ret . pdc_dns_name
2018-07-30 09:21:29 +03:00
2023-12-14 05:14:27 +03:00
""" Fetch a list of GUIDs for applicable GPOs """
2018-07-30 09:20:39 +03:00
2023-03-14 20:21:02 +03:00
def get_gpo ( samdb , gpo_dn ) :
g = gpo . GROUP_POLICY_OBJECT ( )
attrs = [
" cn " ,
" displayName " ,
" flags " ,
" gPCFileSysPath " ,
" gPCFunctionalityVersion " ,
" gPCMachineExtensionNames " ,
" gPCUserExtensionNames " ,
" gPCWQLFilter " ,
" name " ,
" nTSecurityDescriptor " ,
" versionNumber "
]
if gpo_dn . startswith ( " LDAP:// " ) :
gpo_dn = gpo_dn . lstrip ( " LDAP:// " )
sd_flags = ( security . SECINFO_OWNER |
security . SECINFO_GROUP |
security . SECINFO_DACL )
try :
res = samdb . search ( gpo_dn , ldb . SCOPE_BASE , " (objectclass=*) " , attrs ,
controls = [ ' sd_flags:1: %d ' % sd_flags ] )
except Exception :
log . error ( ' Failed to fetch gpo object with nTSecurityDescriptor ' )
2023-04-28 16:37:31 +03:00
raise
2023-03-14 20:21:02 +03:00
if res . count != 1 :
raise ldb . LdbError ( ldb . ERR_NO_SUCH_OBJECT ,
' get_gpo: search failed ' )
g . ds_path = gpo_dn
if ' versionNumber ' in res . msgs [ 0 ] . keys ( ) :
g . version = int ( res . msgs [ 0 ] [ ' versionNumber ' ] [ 0 ] )
if ' flags ' in res . msgs [ 0 ] . keys ( ) :
g . options = int ( res . msgs [ 0 ] [ ' flags ' ] [ 0 ] )
if ' gPCFileSysPath ' in res . msgs [ 0 ] . keys ( ) :
g . file_sys_path = res . msgs [ 0 ] [ ' gPCFileSysPath ' ] [ 0 ] . decode ( )
if ' displayName ' in res . msgs [ 0 ] . keys ( ) :
g . display_name = res . msgs [ 0 ] [ ' displayName ' ] [ 0 ] . decode ( )
if ' name ' in res . msgs [ 0 ] . keys ( ) :
g . name = res . msgs [ 0 ] [ ' name ' ] [ 0 ] . decode ( )
if ' gPCMachineExtensionNames ' in res . msgs [ 0 ] . keys ( ) :
g . machine_extensions = str ( res . msgs [ 0 ] [ ' gPCMachineExtensionNames ' ] [ 0 ] )
if ' gPCUserExtensionNames ' in res . msgs [ 0 ] . keys ( ) :
g . user_extensions = str ( res . msgs [ 0 ] [ ' gPCUserExtensionNames ' ] [ 0 ] )
if ' nTSecurityDescriptor ' in res . msgs [ 0 ] . keys ( ) :
g . set_sec_desc ( bytes ( res . msgs [ 0 ] [ ' nTSecurityDescriptor ' ] [ 0 ] ) )
return g
class GP_LINK :
def __init__ ( self , gPLink , gPOptions ) :
self . link_names = [ ]
self . link_opts = [ ]
self . gpo_parse_gplink ( gPLink )
self . gp_opts = int ( gPOptions )
def gpo_parse_gplink ( self , gPLink ) :
for p in gPLink . decode ( ) . split ( ' ] ' ) :
if not p :
continue
log . debug ( ' gpo_parse_gplink: processing link ' )
p = p . lstrip ( ' [ ' )
link_name , link_opt = p . split ( ' ; ' )
log . debug ( ' gpo_parse_gplink: link: {} ' . format ( link_name ) )
log . debug ( ' gpo_parse_gplink: opt: {} ' . format ( link_opt ) )
self . link_names . append ( link_name )
self . link_opts . append ( int ( link_opt ) )
def num_links ( self ) :
if len ( self . link_names ) != len ( self . link_opts ) :
raise RuntimeError ( ' Link names and opts mismatch ' )
return len ( self . link_names )
def find_samaccount ( samdb , samaccountname ) :
attrs = [ ' dn ' , ' userAccountControl ' ]
res = samdb . search ( samdb . get_default_basedn ( ) , ldb . SCOPE_SUBTREE ,
' (sAMAccountName= {} ) ' . format ( samaccountname ) , attrs )
if res . count != 1 :
raise ldb . LdbError ( ldb . ERR_NO_SUCH_OBJECT ,
" Failed to find samAccountName ' {} ' " . format ( samaccountname )
)
uac = int ( res . msgs [ 0 ] [ ' userAccountControl ' ] [ 0 ] )
dn = res . msgs [ 0 ] [ ' dn ' ]
log . info ( ' Found dn {} for samaccountname {} ' . format ( dn , samaccountname ) )
return uac , dn
def get_gpo_link ( samdb , link_dn ) :
res = samdb . search ( link_dn , ldb . SCOPE_BASE ,
' (objectclass=*) ' , [ ' gPLink ' , ' gPOptions ' ] )
if res . count != 1 :
raise ldb . LdbError ( ldb . ERR_NO_SUCH_OBJECT , ' get_gpo_link: no result ' )
2023-08-25 04:58:17 +03:00
if ' gPLink ' not in res . msgs [ 0 ] :
2023-03-14 20:21:02 +03:00
raise ldb . LdbError ( ldb . ERR_NO_SUCH_ATTRIBUTE ,
" get_gpo_link: no ' gPLink ' attribute found for ' {} ' " . format ( link_dn )
)
gPLink = res . msgs [ 0 ] [ ' gPLink ' ] [ 0 ]
gPOptions = 0
if ' gPOptions ' in res . msgs [ 0 ] :
gPOptions = res . msgs [ 0 ] [ ' gPOptions ' ] [ 0 ]
else :
log . debug ( " get_gpo_link: no ' gPOptions ' attribute found " )
return GP_LINK ( gPLink , gPOptions )
def add_gplink_to_gpo_list ( samdb , gpo_list , forced_gpo_list , link_dn , gp_link ,
link_type , only_add_forced_gpos , token ) :
for i in range ( gp_link . num_links ( ) - 1 , - 1 , - 1 ) :
is_forced = ( gp_link . link_opts [ i ] & GPLINK_OPT_ENFORCE ) != 0
if gp_link . link_opts [ i ] & GPLINK_OPT_DISABLE :
log . debug ( ' skipping disabled GPO ' )
continue
if only_add_forced_gpos :
if not is_forced :
log . debug ( " skipping nonenforced GPO link "
" because GPOPTIONS_BLOCK_INHERITANCE "
" has been set " )
continue
else :
log . debug ( " adding enforced GPO link although "
" the GPOPTIONS_BLOCK_INHERITANCE "
" has been set " )
try :
new_gpo = get_gpo ( samdb , gp_link . link_names [ i ] )
except ldb . LdbError as e :
( enum , estr ) = e . args
log . debug ( " failed to get gpo: %s " % gp_link . link_names [ i ] )
if enum == ldb . ERR_NO_SUCH_OBJECT :
log . debug ( " skipping empty gpo: %s " % gp_link . link_names [ i ] )
continue
return
else :
try :
sec_desc = ndr_unpack ( security . descriptor ,
new_gpo . get_sec_desc_buf ( ) )
samba . security . access_check ( sec_desc , token ,
security . SEC_STD_READ_CONTROL |
security . SEC_ADS_LIST |
security . SEC_ADS_READ_PROP )
except Exception as e :
log . debug ( " skipping GPO \" %s \" as object "
" has no access to it " % new_gpo . display_name )
continue
new_gpo . link = str ( link_dn )
new_gpo . link_type = link_type
if is_forced :
forced_gpo_list . insert ( 0 , new_gpo )
else :
gpo_list . insert ( 0 , new_gpo )
log . debug ( " add_gplink_to_gpo_list: added GPLINK # %d %s "
" to GPO list " % ( i , gp_link . link_names [ i ] ) )
2023-09-15 03:07:25 +03:00
def merge_with_system_token ( token_1 ) :
2023-03-14 20:21:02 +03:00
sids = token_1 . sids
2023-09-15 03:07:25 +03:00
system_token = system_session ( ) . security_token
sids . extend ( system_token . sids )
2023-03-14 20:21:02 +03:00
token_1 . sids = sids
2023-09-15 03:07:25 +03:00
token_1 . rights_mask | = system_token . rights_mask
token_1 . privilege_mask | = system_token . privilege_mask
# There are no claims in the system token, so it is safe not to merge the claims
2023-03-14 20:21:02 +03:00
return token_1
2024-02-21 11:10:47 +03:00
2023-03-14 20:21:02 +03:00
def site_dn_for_machine ( samdb , dc_hostname , lp , creds , hostname ) :
# [MS-GPOL] 3.2.5.1.4 Site Search
2024-02-21 11:10:47 +03:00
# The netr_DsRGetSiteName() needs to run over local rpc, however we do not
# have the call implemented in our rpc_server.
# What netr_DsRGetSiteName() actually does is an ldap query to get
# the sitename, we can do the same.
# NtVer=(NETLOGON_NT_VERSION_IP|NETLOGON_NT_VERSION_WITH_CLOSEST_SITE|
# NETLOGON_NT_VERSION_5EX) [0x20000014]
expr = " (&(DnsDomain= %s .)(User= %s )(NtVer= \\ 14 \\ 00 \\ 00 \\ 20)) " % (
samdb . domain_dns_name ( ) ,
hostname )
res = samdb . search (
base = ' ' ,
scope = ldb . SCOPE_BASE ,
expression = expr ,
attrs = [ " Netlogon " ] )
if res . count != 1 :
raise RuntimeError ( ' site_dn_for_machine: No result ' )
samlogon_response = ndr_unpack ( nbt . netlogon_samlogon_response ,
bytes ( res . msgs [ 0 ] [ ' Netlogon ' ] [ 0 ] ) )
2024-04-15 08:32:02 +03:00
if not ( samlogon_response . ntver & nbt . NETLOGON_NT_VERSION_5EX ) :
2024-02-21 11:10:47 +03:00
raise RuntimeError ( ' site_dn_for_machine: Invalid NtVer in '
+ ' netlogon_samlogon_response ' )
# We want NETLOGON_NT_VERSION_5EX out of the union!
samlogon_response . ntver = nbt . NETLOGON_NT_VERSION_5EX
samlogon_response_ex = samlogon_response . data
client_site = " Default-First-Site-Name "
if ( samlogon_response_ex . client_site
and len ( samlogon_response_ex . client_site ) > 1 ) :
client_site = samlogon_response_ex . client_site
site_dn = samdb . get_config_basedn ( )
site_dn . add_child ( " CN=Sites " )
site_dn . add_child ( " CN= %s " % ( client_site ) )
return site_dn
2023-03-14 20:21:02 +03:00
2021-07-20 18:13:06 +03:00
def get_gpo_list ( dc_hostname , creds , lp , username ) :
2023-12-14 05:14:27 +03:00
""" Get the full list of GROUP_POLICY_OBJECTs for a given username.
2023-03-14 20:21:02 +03:00
Push GPOs to gpo_list so that the traversal order of the list matches
the order of application :
( L ) ocal ( S ) ite ( D ) omain ( O ) rganizational ( U ) nit
For different domains and OUs : parent - to - child .
Within same level of domains and OUs : Link order .
Since GPOs are pushed to the front of gpo_list , GPOs have to be
pushed in the opposite order of application ( OUs first , local last ,
child - to - parent ) .
Forced GPOs are appended in the end since they override all others .
2023-12-14 05:14:27 +03:00
"""
2023-03-14 20:21:02 +03:00
gpo_list = [ ]
forced_gpo_list = [ ]
url = ' ldap:// ' + dc_hostname
samdb = SamDB ( url = url ,
session_info = system_session ( ) ,
credentials = creds , lp = lp )
# username is DOM\\SAM, but get_gpo_list expects SAM
uac , dn = find_samaccount ( samdb , username . split ( ' \\ ' ) [ - 1 ] )
add_only_forced_gpos = False
# Fetch the security token
session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
AUTH_SESSION_INFO_AUTHENTICATED )
if url . startswith ( ' ldap ' ) :
session_info_flags | = AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
session = samba . auth . user_session ( samdb , lp_ctx = lp , dn = dn ,
session_info_flags = session_info_flags )
gpo_list_machine = False
if uac & UF_WORKSTATION_TRUST_ACCOUNT or uac & UF_SERVER_TRUST_ACCOUNT :
gpo_list_machine = True
2023-09-15 03:07:25 +03:00
token = merge_with_system_token ( session . security_token )
2023-03-14 20:21:02 +03:00
else :
token = session . security_token
# (O)rganizational(U)nit
parent_dn = dn . parent ( )
while True :
if str ( parent_dn ) == str ( samdb . get_default_basedn ( ) . parent ( ) ) :
break
# An account can be a member of more OUs
if parent_dn . get_component_name ( 0 ) == ' OU ' :
try :
log . debug ( " get_gpo_list: query OU: [ %s ] for GPOs " % parent_dn )
gp_link = get_gpo_link ( samdb , parent_dn )
except ldb . LdbError as e :
( enum , estr ) = e . args
log . debug ( estr )
else :
add_gplink_to_gpo_list ( samdb , gpo_list , forced_gpo_list ,
parent_dn , gp_link ,
gpo . GP_LINK_OU ,
add_only_forced_gpos , token )
# block inheritance from now on
if gp_link . gp_opts & GPO_BLOCK_INHERITANCE :
add_only_forced_gpos = True
parent_dn = parent_dn . parent ( )
# (D)omain
parent_dn = dn . parent ( )
while True :
if str ( parent_dn ) == str ( samdb . get_default_basedn ( ) . parent ( ) ) :
break
# An account can just be a member of one domain
if parent_dn . get_component_name ( 0 ) == ' DC ' :
try :
log . debug ( " get_gpo_list: query DC: [ %s ] for GPOs " % parent_dn )
gp_link = get_gpo_link ( samdb , parent_dn )
except ldb . LdbError as e :
( enum , estr ) = e . args
log . debug ( estr )
else :
add_gplink_to_gpo_list ( samdb , gpo_list , forced_gpo_list ,
parent_dn , gp_link ,
gpo . GP_LINK_DOMAIN ,
add_only_forced_gpos , token )
# block inheritance from now on
if gp_link . gp_opts & GPO_BLOCK_INHERITANCE :
add_only_forced_gpos = True
parent_dn = parent_dn . parent ( )
# (S)ite
if gpo_list_machine :
try :
2024-01-05 18:47:07 +03:00
site_dn = site_dn_for_machine ( samdb , dc_hostname , lp , creds , username )
try :
log . debug ( " get_gpo_list: query SITE: [ %s ] for GPOs " % site_dn )
gp_link = get_gpo_link ( samdb , site_dn )
except ldb . LdbError as e :
( enum , estr ) = e . args
log . debug ( estr )
else :
add_gplink_to_gpo_list ( samdb , gpo_list , forced_gpo_list ,
site_dn , gp_link ,
gpo . GP_LINK_SITE ,
add_only_forced_gpos , token )
except ldb . LdbError :
# [MS-GPOL] 3.2.5.1.4 Site Search: If the method returns
# ERROR_NO_SITENAME, the remainder of this message MUST be skipped
# and the protocol sequence MUST continue at GPO Search
pass
2023-03-14 20:21:02 +03:00
# (L)ocal
gpo_list . insert ( 0 , gpo . GROUP_POLICY_OBJECT ( " Local Policy " ,
" Local Policy " ,
gpo . GP_LINK_LOCAL ) )
# Append |forced_gpo_list| at the end of |gpo_list|,
# so that forced GPOs are applied on top of non enforced GPOs.
return gpo_list + forced_gpo_list
2018-03-29 17:32:02 +03:00
2018-01-08 17:17:29 +03:00
def cache_gpo_dir ( conn , cache , sub_dir ) :
loc_sub_dir = sub_dir . upper ( )
local_dir = os . path . join ( cache , loc_sub_dir )
try :
os . makedirs ( local_dir , mode = 0o755 )
except OSError as e :
if e . errno != errno . EEXIST :
raise
for fdata in conn . list ( sub_dir ) :
2018-12-14 00:37:11 +03:00
if fdata [ ' attrib ' ] & libsmb . FILE_ATTRIBUTE_DIRECTORY :
2018-01-08 17:17:29 +03:00
cache_gpo_dir ( conn , cache , os . path . join ( sub_dir , fdata [ ' name ' ] ) )
else :
local_name = fdata [ ' name ' ] . upper ( )
f = NamedTemporaryFile ( delete = False , dir = local_dir )
fname = os . path . join ( sub_dir , fdata [ ' name ' ] ) . replace ( ' / ' , ' \\ ' )
f . write ( conn . loadfile ( fname ) )
f . close ( )
os . rename ( f . name , os . path . join ( local_dir , local_name ) )
def check_safe_path ( path ) :
dirs = re . split ( ' /| \\ \\ ' , path )
2021-03-09 21:13:40 +03:00
if ' sysvol ' in path . lower ( ) :
ldirs = re . split ( ' /| \\ \\ ' , path . lower ( ) )
dirs = dirs [ ldirs . index ( ' sysvol ' ) + 1 : ]
2018-07-30 09:22:34 +03:00
if ' .. ' not in dirs :
2018-01-08 17:17:29 +03:00
return os . path . join ( * dirs )
raise OSError ( path )
2018-07-30 09:20:39 +03:00
2018-01-08 17:17:29 +03:00
def check_refresh_gpo_list ( dc_hostname , lp , creds , gpos ) :
2020-06-03 15:02:37 +03:00
# Force signing for the connection
saved_signing_state = creds . get_smb_signing ( )
creds . set_smb_signing ( SMB_SIGNING_REQUIRED )
2022-07-18 18:19:24 +03:00
conn = libsmb . Conn ( dc_hostname , ' sysvol ' , lp = lp , creds = creds )
2020-06-03 15:02:37 +03:00
# Reset signing state
creds . set_smb_signing ( saved_signing_state )
2018-01-08 17:17:29 +03:00
cache_path = lp . cache_path ( ' gpo_cache ' )
2023-02-24 04:52:40 +03:00
for gpo_obj in gpos :
if not gpo_obj . file_sys_path :
2018-01-08 17:17:29 +03:00
continue
2023-02-24 04:52:40 +03:00
cache_gpo_dir ( conn , cache_path , check_safe_path ( gpo_obj . file_sys_path ) )
2018-01-08 17:17:29 +03:00
2018-07-30 09:20:39 +03:00
2018-05-15 23:00:07 +03:00
def get_deleted_gpos_list ( gp_db , gpos ) :
applied_gpos = gp_db . get_applied_guids ( )
current_guids = set ( [ p . name for p in gpos ] )
deleted_gpos = [ guid for guid in applied_gpos if guid not in current_guids ]
return gp_db . get_applied_settings ( deleted_gpos )
2018-01-08 17:17:29 +03:00
def gpo_version ( lp , path ) :
# gpo.gpo_get_sysvol_gpt_version() reads the GPT.INI from a local file,
# read from the gpo client cache.
gpt_path = lp . cache_path ( os . path . join ( ' gpo_cache ' , path ) )
return int ( gpo . gpo_get_sysvol_gpt_version ( gpt_path ) [ 1 ] )
2018-07-30 09:20:39 +03:00
2022-03-02 12:23:51 +03:00
def apply_gp ( lp , creds , store , gp_extensions , username , target , force = False ) :
2021-07-20 18:13:06 +03:00
gp_db = store . get_gplog ( username )
2018-03-29 17:32:02 +03:00
dc_hostname = get_dc_hostname ( creds , lp )
2021-07-20 18:13:06 +03:00
gpos = get_gpo_list ( dc_hostname , creds , lp , username )
2018-05-15 23:00:07 +03:00
del_gpos = get_deleted_gpos_list ( gp_db , gpos )
2018-01-08 17:17:29 +03:00
try :
check_refresh_gpo_list ( dc_hostname , lp , creds , gpos )
except :
2022-03-02 12:23:51 +03:00
log . error ( ' Failed downloading gpt cache from \' %s \' using SMB '
% dc_hostname )
2018-01-08 17:17:29 +03:00
return
2018-03-29 17:32:02 +03:00
2018-05-16 18:54:38 +03:00
if force :
changed_gpos = gpos
gp_db . state ( GPOSTATE . ENFORCE )
else :
changed_gpos = [ ]
for gpo_obj in gpos :
if not gpo_obj . file_sys_path :
continue
guid = gpo_obj . name
path = check_safe_path ( gpo_obj . file_sys_path ) . upper ( )
version = gpo_version ( lp , path )
if version != store . get_int ( guid ) :
2022-03-02 12:23:51 +03:00
log . info ( ' GPO %s has changed ' % guid )
2018-05-16 18:54:38 +03:00
changed_gpos . append ( gpo_obj )
gp_db . state ( GPOSTATE . APPLY )
2018-05-09 22:16:38 +03:00
store . start ( )
for ext in gp_extensions :
try :
2022-03-02 12:23:51 +03:00
ext = ext ( lp , creds , username , store )
2021-07-20 18:13:06 +03:00
if target == ' Computer ' :
ext . process_group_policy ( del_gpos , changed_gpos )
else :
2023-06-07 17:29:31 +03:00
drop_privileges ( username , ext . process_group_policy ,
2021-07-20 18:13:06 +03:00
del_gpos , changed_gpos )
2018-05-09 22:16:38 +03:00
except Exception as e :
2022-03-02 12:23:51 +03:00
log . error ( ' Failed to apply extension %s ' % str ( ext ) )
2023-02-09 20:34:24 +03:00
_ , _ , tb = sys . exc_info ( )
filename , line_number , _ , _ = traceback . extract_tb ( tb ) [ - 1 ]
log . error ( ' %s : %d : %s : %s ' % ( filename , line_number ,
type ( e ) . __name__ , str ( e ) ) )
2018-05-09 22:16:38 +03:00
continue
for gpo_obj in gpos :
if not gpo_obj . file_sys_path :
continue
guid = gpo_obj . name
path = check_safe_path ( gpo_obj . file_sys_path ) . upper ( )
version = gpo_version ( lp , path )
2018-03-29 17:32:02 +03:00
store . store ( guid , ' %i ' % version )
2018-05-09 22:16:38 +03:00
store . commit ( )
2018-03-29 17:32:02 +03:00
2018-07-30 09:20:39 +03:00
2022-03-02 12:23:51 +03:00
def unapply_gp ( lp , creds , store , gp_extensions , username , target ) :
2021-07-20 18:13:06 +03:00
gp_db = store . get_gplog ( username )
2018-03-29 17:32:02 +03:00
gp_db . state ( GPOSTATE . UNAPPLY )
2018-05-18 01:48:47 +03:00
# Treat all applied gpos as deleted
del_gpos = gp_db . get_applied_settings ( gp_db . get_applied_guids ( ) )
store . start ( )
for ext in gp_extensions :
try :
2022-03-02 12:23:51 +03:00
ext = ext ( lp , creds , username , store )
2021-07-20 18:13:06 +03:00
if target == ' Computer ' :
ext . process_group_policy ( del_gpos , [ ] )
else :
drop_privileges ( username , ext . process_group_policy ,
del_gpos , [ ] )
2018-05-18 01:48:47 +03:00
except Exception as e :
2022-03-02 12:23:51 +03:00
log . error ( ' Failed to unapply extension %s ' % str ( ext ) )
log . error ( ' Message was: ' + str ( e ) )
2018-05-18 01:48:47 +03:00
continue
store . commit ( )
2018-03-29 17:32:02 +03:00
2018-07-30 09:20:39 +03:00
2020-07-06 17:25:23 +03:00
def __rsop_vals ( vals , level = 4 ) :
if type ( vals ) == dict :
ret = [ ' ' * level + ' [ %s ] = %s ' % ( k , __rsop_vals ( v , level + 2 ) )
for k , v in vals . items ( ) ]
2021-06-28 18:06:09 +03:00
return ' \n ' + ' \n ' . join ( ret )
2020-07-06 17:25:23 +03:00
elif type ( vals ) == list :
ret = [ ' ' * level + ' [ %s ] ' % __rsop_vals ( v , level + 2 ) for v in vals ]
2021-06-28 18:06:09 +03:00
return ' \n ' + ' \n ' . join ( ret )
2020-07-06 17:25:23 +03:00
else :
2022-12-09 19:40:34 +03:00
if isinstance ( vals , numbers . Number ) :
return ' ' * ( level + 2 ) + str ( vals )
else :
return ' ' * ( level + 2 ) + get_string ( vals )
2020-07-06 17:25:23 +03:00
2022-03-02 12:23:51 +03:00
def rsop ( lp , creds , store , gp_extensions , username , target ) :
2020-07-06 17:25:23 +03:00
dc_hostname = get_dc_hostname ( creds , lp )
2021-07-20 18:13:06 +03:00
gpos = get_gpo_list ( dc_hostname , creds , lp , username )
2020-07-06 17:25:23 +03:00
check_refresh_gpo_list ( dc_hostname , lp , creds , gpos )
print ( ' Resultant Set of Policy ' )
print ( ' %s Policy \n ' % target )
2020-08-28 17:38:41 +03:00
term_width = shutil . get_terminal_size ( fallback = ( 120 , 50 ) ) [ 0 ]
2023-02-24 04:52:40 +03:00
for gpo_obj in gpos :
if gpo_obj . display_name . strip ( ) == ' Local Policy ' :
2021-02-26 19:43:30 +03:00
continue # We never apply local policy
2023-02-24 04:52:40 +03:00
print ( ' GPO: %s ' % gpo_obj . display_name )
2020-07-06 17:25:23 +03:00
print ( ' = ' * term_width )
for ext in gp_extensions :
2022-03-02 12:23:51 +03:00
ext = ext ( lp , creds , username , store )
2023-02-24 04:53:36 +03:00
cse_name_m = re . findall ( r " ' ([ \ w \ .]+) ' " , str ( type ( ext ) ) )
2021-02-26 19:43:30 +03:00
if len ( cse_name_m ) > 0 :
cse_name = cse_name_m [ - 1 ] . split ( ' . ' ) [ - 1 ]
else :
cse_name = ext . __module__ . split ( ' . ' ) [ - 1 ]
print ( ' CSE: %s ' % cse_name )
2020-07-06 17:25:23 +03:00
print ( ' ' + ( ' - ' * int ( term_width / 2 ) ) )
2023-02-24 04:52:40 +03:00
for section , settings in ext . rsop ( gpo_obj ) . items ( ) :
2020-07-06 17:25:23 +03:00
print ( ' Policy Type: %s ' % section )
print ( ' ' + ( ' - ' * int ( term_width / 2 ) ) )
2021-06-28 18:06:09 +03:00
print ( __rsop_vals ( settings ) . lstrip ( ' \n ' ) )
2020-07-06 17:25:23 +03:00
print ( ' ' + ( ' - ' * int ( term_width / 2 ) ) )
print ( ' ' + ( ' - ' * int ( term_width / 2 ) ) )
print ( ' %s \n ' % ( ' = ' * term_width ) )
2018-06-13 23:45:09 +03:00
def parse_gpext_conf ( smb_conf ) :
2022-11-25 16:04:17 +03:00
from samba . samba3 import param as s3param
lp = s3param . get_context ( )
2018-06-13 23:45:09 +03:00
if smb_conf is not None :
lp . load ( smb_conf )
else :
lp . load_default ( )
ext_conf = lp . state_path ( ' gpext.conf ' )
2020-08-21 00:51:47 +03:00
parser = ConfigParser ( interpolation = None )
2018-06-13 23:45:09 +03:00
parser . read ( ext_conf )
return lp , parser
2018-07-30 09:20:39 +03:00
2018-06-13 23:45:09 +03:00
def atomic_write_conf ( lp , parser ) :
ext_conf = lp . state_path ( ' gpext.conf ' )
2018-11-06 22:55:22 +03:00
with NamedTemporaryFile ( mode = " w+ " , delete = False , dir = os . path . dirname ( ext_conf ) ) as f :
2018-06-13 23:45:09 +03:00
parser . write ( f )
os . rename ( f . name , ext_conf )
2018-07-30 09:20:39 +03:00
2018-06-13 23:45:09 +03:00
def check_guid ( guid ) :
# Check for valid guid with curly braces
if guid [ 0 ] != ' { ' or guid [ - 1 ] != ' } ' or len ( guid ) != 38 :
return False
try :
UUID ( guid , version = 4 )
except ValueError :
return False
return True
2018-07-30 09:20:39 +03:00
2018-06-13 23:45:09 +03:00
def register_gp_extension ( guid , name , path ,
smb_conf = None , machine = True , user = True ) :
# Check that the module exists
if not os . path . exists ( path ) :
return False
if not check_guid ( guid ) :
return False
lp , parser = parse_gpext_conf ( smb_conf )
2018-07-30 09:22:34 +03:00
if guid not in parser . sections ( ) :
2018-06-13 23:45:09 +03:00
parser . add_section ( guid )
parser . set ( guid , ' DllName ' , path )
parser . set ( guid , ' ProcessGroupPolicy ' , name )
2018-11-06 22:55:22 +03:00
parser . set ( guid , ' NoMachinePolicy ' , " 0 " if machine else " 1 " )
parser . set ( guid , ' NoUserPolicy ' , " 0 " if user else " 1 " )
2018-06-13 23:45:09 +03:00
atomic_write_conf ( lp , parser )
return True
2018-06-13 23:46:05 +03:00
2018-07-30 09:20:39 +03:00
2018-06-13 23:46:30 +03:00
def list_gp_extensions ( smb_conf = None ) :
_ , parser = parse_gpext_conf ( smb_conf )
results = { }
for guid in parser . sections ( ) :
results [ guid ] = { }
results [ guid ] [ ' DllName ' ] = parser . get ( guid , ' DllName ' )
results [ guid ] [ ' ProcessGroupPolicy ' ] = \
parser . get ( guid , ' ProcessGroupPolicy ' )
results [ guid ] [ ' MachinePolicy ' ] = \
not int ( parser . get ( guid , ' NoMachinePolicy ' ) )
results [ guid ] [ ' UserPolicy ' ] = not int ( parser . get ( guid , ' NoUserPolicy ' ) )
return results
2018-07-30 09:20:39 +03:00
2018-06-13 23:46:05 +03:00
def unregister_gp_extension ( guid , smb_conf = None ) :
if not check_guid ( guid ) :
return False
lp , parser = parse_gpext_conf ( smb_conf )
if guid in parser . sections ( ) :
parser . remove_section ( guid )
atomic_write_conf ( lp , parser )
return True
2021-07-20 18:13:06 +03:00
def set_privileges ( username , uid , gid ) :
2023-12-14 05:14:27 +03:00
"""
2021-07-20 18:13:06 +03:00
Set current process privileges
2023-12-14 05:14:27 +03:00
"""
2021-07-20 18:13:06 +03:00
os . setegid ( gid )
os . seteuid ( uid )
def drop_privileges ( username , func , * args ) :
2023-12-14 05:14:27 +03:00
"""
2021-07-20 18:13:06 +03:00
Run supplied function with privileges for specified username .
2023-12-14 05:14:27 +03:00
"""
2021-07-20 18:13:06 +03:00
current_uid = os . getuid ( )
if not current_uid == 0 :
raise Exception ( ' Not enough permissions to drop privileges ' )
user_uid = pwd . getpwnam ( username ) . pw_uid
user_gid = pwd . getpwnam ( username ) . pw_gid
# Drop privileges
set_privileges ( username , user_uid , user_gid )
# We need to catch exception in order to be able to restore
# privileges later in this function
out = None
exc = None
try :
out = func ( * args )
except Exception as e :
exc = e
# Restore privileges
set_privileges ( ' root ' , current_uid , 0 )
if exc :
raise exc
return out
2023-03-11 00:30:17 +03:00
def expand_pref_variables ( text , gpt_path , lp , username = None ) :
utc_dt = datetime . utcnow ( )
dt = datetime . now ( )
cache_path = lp . cache_path ( os . path . join ( ' gpo_cache ' ) )
# These are all the possible preference variables that MS supports. The
# variables set to 'None' here are currently unsupported by Samba, and will
# prevent the individual policy from applying.
variables = { ' AppDataDir ' : os . path . expanduser ( ' ~/.config ' ) ,
' BinaryComputerSid ' : None ,
' BinaryUserSid ' : None ,
' CommonAppdataDir ' : None ,
' CommonDesktopDir ' : None ,
' CommonFavoritesDir ' : None ,
' CommonProgramsDir ' : None ,
' CommonStartUpDir ' : None ,
' ComputerName ' : lp . get ( ' netbios name ' ) ,
' CurrentProccessId ' : None ,
' CurrentThreadId ' : None ,
' DateTime ' : utc_dt . strftime ( ' % Y- % m- %d % H: % M: % S UTC ' ) ,
' DateTimeEx ' : str ( utc_dt ) ,
' DesktopDir ' : os . path . expanduser ( ' ~/Desktop ' ) ,
' DomainName ' : lp . get ( ' realm ' ) ,
' FavoritesDir ' : None ,
' GphPath ' : None ,
' GptPath ' : os . path . join ( cache_path ,
check_safe_path ( gpt_path ) . upper ( ) ) ,
' GroupPolicyVersion ' : None ,
' LastDriveMapped ' : None ,
' LastError ' : None ,
' LastErrorText ' : None ,
' LdapComputerSid ' : None ,
' LdapUserSid ' : None ,
' LocalTime ' : dt . strftime ( ' % H: % M: % S ' ) ,
' LocalTimeEx ' : dt . strftime ( ' % H: % M: % S. %f ' ) ,
' LogonDomain ' : lp . get ( ' realm ' ) ,
' LogonServer ' : None ,
' LogonUser ' : username ,
' LogonUserSid ' : None ,
' MacAddress ' : None ,
' NetPlacesDir ' : None ,
' OsVersion ' : None ,
' ProgramFilesDir ' : None ,
' ProgramsDir ' : None ,
' RecentDocumentsDir ' : None ,
' ResultCode ' : None ,
' ResultText ' : None ,
' ReversedComputerSid ' : None ,
' ReversedUserSid ' : None ,
' SendToDir ' : None ,
' StartMenuDir ' : None ,
' StartUpDir ' : None ,
' SystemDir ' : None ,
' SystemDrive ' : ' / ' ,
' TempDir ' : ' /tmp ' ,
' TimeStamp ' : str ( datetime . timestamp ( dt ) ) ,
' TraceFile ' : None ,
' WindowsDir ' : None
}
for exp_var , val in variables . items ( ) :
exp_var_fmt = ' %% %s %% ' % exp_var
if exp_var_fmt in text :
if val is None :
raise NameError ( ' Expansion variable %s is undefined ' % exp_var )
text = text . replace ( exp_var_fmt , val )
return text