# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from samba.gp.gpclass import gp_xml_ext, gp_misc_applier, drop_privileges
+import os
+import json
+from samba.gp.gpclass import gp_xml_ext, gp_misc_applier, drop_privileges, \
+ expand_pref_variables
+from subprocess import Popen, PIPE
+from samba.gp.gp_scripts_ext import fetch_crontab, install_crontab, \
+ install_user_crontab
+from samba.gp.util.logging import log
+from samba.gp import gp_scripts_ext
+gp_scripts_ext.intro = '''
+### autogenerated by samba
+#
+# This file is generated by the gp_drive_maps_user_ext Group Policy
+# Client Side Extension. To modify the contents of this file,
+# modify the appropriate Group Policy objects which apply
+# to this machine. DO NOT MODIFY THIS FILE DIRECTLY.
+#
+
+'''
+
+def mount_drive(uri):
+ log.debug('Mounting drive', uri)
+ out, err = Popen(['gio', 'mount', uri],
+ stdout=PIPE, stderr=PIPE).communicate()
+ if err:
+ if b'Location is already mounted' not in err:
+ raise SystemError(err)
+
+def unmount_drive(uri):
+ log.debug('Unmounting drive', uri)
+ return Popen(['gio', 'mount', uri, '--unmount']).wait()
class gp_drive_maps_user_ext(gp_xml_ext, gp_misc_applier):
+ def parse_value(self, val):
+ vals = super().parse_value(val)
+ if 'props' in vals.keys():
+ vals['props'] = json.loads(vals['props'])
+ if 'run_once' in vals.keys():
+ vals['run_once'] = json.loads(vals['run_once'])
+ return vals
+
+ def unapply(self, guid, uri, val):
+ vals = self.parse_value(val)
+ if 'props' in vals.keys() and \
+ vals['props']['action'] in ['C', 'R', 'U']:
+ unmount_drive(uri)
+ others, entries = fetch_crontab(self.username)
+ if 'crontab' in vals.keys() and vals['crontab'] in entries:
+ entries.remove(vals['crontab'])
+ install_user_crontab(self.username, others, entries)
+ self.cache_remove_attribute(guid, uri)
+
+ def apply(self, guid, uri, props, run_once, entry):
+ old_val = self.cache_get_attribute_value(guid, uri)
+ val = self.generate_value(props=json.dumps(props),
+ run_once=json.dumps(run_once),
+ crontab=entry)
+
+ # The policy has changed, unapply it first
+ if old_val:
+ self.unapply(guid, uri, old_val)
+
+ if props['action'] in ['C', 'R', 'U']:
+ mount_drive(uri)
+ elif props['action'] == 'D':
+ unmount_drive(uri)
+ if not run_once:
+ others, entries = fetch_crontab(self.username)
+ if entry not in entries:
+ entries.append(entry)
+ install_user_crontab(self.username, others, entries)
+ self.cache_add_attribute(guid, uri, val)
+
def __str__(self):
return 'Preferences/Drives'
def process_group_policy(self, deleted_gpo_list, changed_gpo_list):
- pass
+ for guid, settings in deleted_gpo_list:
+ if str(self) in settings:
+ for uri, val in settings[str(self)].items():
+ self.unapply(guid, uri, val)
+
+ for gpo in changed_gpo_list:
+ if gpo.file_sys_path:
+ xml = 'USER/Preferences/Drives/Drives.xml'
+ path = os.path.join(gpo.file_sys_path, xml)
+ xml_conf = drop_privileges('root', self.parse, path)
+ if not xml_conf:
+ continue
+ drives = xml_conf.findall('Drive')
+ attrs = []
+ for drive in drives:
+ prop = drive.find('Properties')
+ if prop is None:
+ log.warning('Drive is missing Properties', drive.attrib)
+ continue
+ if prop.attrib['thisDrive'] == 'HIDE':
+ log.warning('Drive is hidden', prop.attrib)
+ continue # Don't mount a hidden drive
+ run_once = False
+ filters = drive.find('Filters')
+ if filters:
+ run_once_filter = filters.find('FilterRunOnce')
+ if run_once_filter is not None:
+ run_once = True
+ uri = 'smb:{}'.format(prop.attrib['path'].replace('\\', '/'))
+ # Ensure we expand the preference variables, or fail if we
+ # are unable to (the uri is invalid if we fail).
+ gptpath = os.path.join(gpo.file_sys_path, 'USER')
+ try:
+ uri = expand_pref_variables(uri, gptpath, self.lp,
+ username=self.username)
+ except NameError as e:
+ # If we fail expanding variables, then the URI is
+ # invalid and we can't continue processing this drive
+ # map. We can continue processing other drives, as they
+ # may succeed. This is not a critical error, since some
+ # Windows specific policies won't apply here.
+ log.warn('Failed to expand drive map variables: %s' % e,
+ prop.attrib)
+ continue
+ attrs.append(uri)
+ entry = ''
+ if not run_once:
+ if prop.attrib['action'] in ['C', 'R', 'U']:
+ entry = '@hourly gio mount {}'.format(uri)
+ elif prop.attrib['action'] == 'D':
+ entry = '@hourly gio mount {} --unmount'.format(uri)
+ self.apply(gpo.name, uri, prop.attrib, run_once, entry)
+ self.clean(gpo.name, keep=attrs)
def rsop(self, gpo):
output = {}
+ if gpo.file_sys_path:
+ xml = 'USER/Preferences/Drives/Drives.xml'
+ path = os.path.join(gpo.file_sys_path, xml)
+ xml_conf = self.parse(path)
+ if not xml_conf:
+ return output
+ drives = xml_conf.findall('Drive')
+ for drive in drives:
+ prop = drive.find('Properties')
+ if prop is None:
+ continue
+ if prop.attrib['thisDrive'] == 'HIDE':
+ continue
+ uri = 'smb:{}'.format(prop.attrib['path'].replace('\\', '/'))
+ if prop.attrib['action'] in ['C', 'R', 'U']:
+ output[prop.attrib['label']] = 'gio mount {}'.format(uri)
+ elif prop.attrib['action'] == 'D':
+ output[prop.attrib['label']] = \
+ 'gio mount {} --unmount'.format(uri)
return output
from samba.dcerpc import security
import samba.security
from samba.dcerpc import netlogon
+from datetime import datetime
try:
raise exc
return out
+
+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