def enable_global(enable, logger): """ Enables or disables the Location Services system globally. :param enable: a boolean describing whether the LS system should be enabled :param logger: a management_tools.loggers logger for recording output """ # Get the Universally Unique Identifier for the hardware. This determines # the location of the locationd system. uuid = get_uuid() ls_dir = '/var/db/locationd/Library/Preferences/ByHost/' ls_plist = ls_dir + 'com.apple.locationd.' + str(uuid) + '.plist' logger.info("Modifying global values in '" + ls_plist + "'.") # Depending on settings, there may be a few possible files to use as the # locationd plist. if not os.path.isfile(ls_plist): ls_plist = None # Find everything starting with 'com.apple.locationd' and ending with # the '.plist' extension. potentials = [ x.lstrip('com.apple.locationd.').rstrip('.plist') for x in os.listdir(ls_dir) if str(x).endswith('.plist') and str(x).startswith('com.apple.locationd.') ] potentials = [x for x in potentials if not x.find('.') >= 0] # Must handle things differently depending on the number of results. if len(potentials) > 1: # Out of all the matches, try to find the one that matches the UUID. for id in potentials: if uuid == id or uuid.lower() == id or uuid.upper() == id: ls_plist = ('{}com.apple.locationd.{}.plist'.format(ls_dir, id)) break for part in uuid.split('-'): if part == id or part.lower() == id or part.upper() == id: ls_plist = ('{}com.apple.locationd.{}.plist'.format(ls_dir, id)) break # If we've found it, break out of this loop. if ls_plist: break elif len(potentials) == 1: # Only one result - that's easy! ls_plist = (ls_dir + 'com.apple.locationd.{}.plist'.format(potentials[0])) else: raise RuntimeError("No Location Services global property list found at '{}'.".format(ls_plist)) # Write the location services status (enabled or not). if ls_plist: ls_plist = PlistEditor(ls_plist) value = 1 if enable else 0 ls_plist.write("LocationServicesEnabled", value, "int") else: raise RuntimeError("Could not locate Location Services plist file at '{}.".format(ls_plist))
def __init__(self, logger, no_check=False, no_check_type=None): # Set the logger for output. self.logger = logger # Set the administrative override flag. self.no_check = no_check # Check what type of application we're adding. if self.no_check and no_check_type == 'app': raise RuntimeError("Location Services does not support adding applications with the `--no-check-app` flag.") # Only root may modify the Location Services system. if os.geteuid() != 0: raise RuntimeError("Must be root to modify Location Services!") # Check the version of OS X before continuing; only Darwin versions 10 # and above support the location services system. try: version = int(os.uname()[2].split('.')[0]) except: raise RuntimeError("Could not acquire the OS X version.") if version < 10: raise RuntimeError("Location Services is not supported in this version of OS X.") self.version = version # Disable the locationd launchd item. (Changes will not be properly # cached if this is not done.) self.__disable() # This is where the applications' authorizations are stored. self.plist = PlistEditor('/var/db/locationd/clients') self.logger.info("Modifying service 'location' at '{}'.".format(self.plist.path))
class LSEdit(object): """ Provides a class for modifying the Location Services permissions. This class was designed to be used in a 'with' statement to ensure proper updating of the locationd system. For example: with LSEdit() as e: # do some stuff to the database e.foo() # do more stuff bar(baz) """ def __init__(self, logger, admin=False): # Set the logger for output. self.logger = logger # Set the administrative override flag. self.admin = admin # Only root may modify the Location Services system. if os.geteuid() != 0: raise RuntimeError("Must be root to modify Location Services!") # Check the version of OS X before continuing; only Darwin versions 10 # and above support the location services system. try: version = int(os.uname()[2].split('.')[0]) except: raise RuntimeError("Could not acquire the OS X version.") if version < 10: raise RuntimeError( "Location Services is not supported in this version of OS X.") self.version = version # Disable the locationd launchd item. (Changes will not be properly # cached if this is not done.) self.__disable() # This is where the applications' authorizations are stored. self.plist = PlistEditor('/var/db/locationd/clients') self.logger.info("Modifying service 'location' at '{}'.".format( self.plist.path)) def insert(self, target): """ Enable the specified target for location services. :param target: an application or file to modify permissions for """ # If no application is given, then we're modifying the global Location # Services system. if not target: self.logger.info("Enabling service 'location' globally.") enable_global(True, self.logger) self.logger.info("Globally enabled successfully.") return # If we're in admin mode, we can't look up the application as a bundle. if self.admin: self.__insert_executable(target) else: self.__insert_app(target) def remove(self, target): """ Remove an item from Location Services. If no item is given, then disable Location Services globally. :param target: an application or file to modify permissions for """ # If no application is given, then we're modifying the global Location # Services system. if not target: self.logger.info("Disabling service 'location' globally...") enable_global(False, self.logger) self.logger.info("Globally disabled successfully.") return if self.admin: name = target target = 'com.apple.locationd.executable-{}'.format(target) else: name = AppInfo(target).name target = AppInfo(target).bid # Verbosity self.logger.info( "Removing '{}' from service 'location'...".format(target)) # Otherwise, just delete its entry in the plist. result = self.plist.delete(target) if result: raise RuntimeError("Failed to remove {}.".format(name)) self.logger.info("Removed successfully.") def disable(self, target): """ Mark the application or file as being disallowed from utilizing Location Services. If the target is not already in the plist, it will be added and then disabled. :param target: an application or file to modify permissions for """ # If no application is given, then we're modifying the global Location # Services system. if not target: self.logger.info("Disabling service 'location' globally...") enable_global(False, self.logger) self.logger.info("Globally disabled successfully.") return if self.admin: name = target target = 'com.apple.locationd.executable-{}'.format(target) else: name = AppInfo(target).name target = AppInfo(target).bid # Verboseness self.logger.info( "Disabling '{}' in service 'location'...".format(target)) # If the application isn't already in locationd, add it. if not self.plist.read(target): self.insert(target) # Then deauthorize the application. result = self.plist.dict_add(target, "Authorized", "FALSE", "bool") if result: raise RuntimeError("Failed to disable {}.".format(name)) self.logger.info("Disabled successfully.") def __insert_app(self, target): """ Inserts the specified target application into the locationd plist. """ # Get the AppInfo object for more information. app = AppInfo(target) # Verbosity! self.logger.info("Inserting '{}' into service 'location'...".format( app.bid)) # This is used for... something. Don't know what, but it's necessary. requirement = ("identifier \"{}\" and anchor {}".format( app.bid, app.bid.split('.')[1])) # Write the changes to the locationd plist. result = 0 result += self.plist.dict_add(app.bid, "Authorized", "TRUE", "bool") result += self.plist.dict_add(app.bid, "BundleID", app.bid) result += self.plist.dict_add(app.bid, "BundleId", app.bid) result += self.plist.dict_add(app.bid, "BundlePath", app.path) result += self.plist.dict_add(app.bid, "Executable", app.executable) result += self.plist.dict_add(app.bid, "Registered", app.executable) result += self.plist.dict_add(app.bid, "Hide", 0, "int") result += self.plist.dict_add(app.bid, "Requirement", requirement) result += self.plist.dict_add(app.bid, "Whitelisted", "FALSE", "bool") if result: # Clearly there was an error... raise RuntimeError("Failed to insert {}.".format(app.name)) self.logger.info("Inserted successfully.") def __insert_executable(self, target): """ Inserts the specified target executable into the locationd plist. """ # Verbosity! self.logger.info( "Inserting executable '{}' into service 'location'...".format( target)) # Reformat the target name. key = 'com.apple.locationd.executable-{}'.format(target) # Find the Code Directory Hash (CDHash). # If the command exits with a non-zero exit status, report to the user. # This generally indicates it does not have a code signature. try: codesign = subprocess.check_output( ['/usr/bin/codesign', '--display', '--verbose=4', target], stderr=subprocess.STDOUT).split('\n') except subprocess.CalledProcessError: self.logger.warn( "Executable '{}' is not signed. Adding anyway...".format( target)) codesign = [] # Filter the results. codesign = [x for x in codesign if x is not None and x != ''] codesign = [x for x in codesign if '=' in x] # After this, 'cdhash' will either be an empty list (no match), or else # it should have one element with the cdhash in it. cdhash = [x.split('=')[1] for x in codesign if 'CDHash' in x] # Build the requirement string from the cdhash if we have one. if len(cdhash) == 1: requirement = ("cdhash H\"{cdhash}\"".format(cdhash=cdhash[0])) else: requirement = None # Write the changes to the locationd plist. result = 0 result += self.plist.dict_add(key, "Authorized", "TRUE", "bool") result += self.plist.dict_add(key, "BundleID", key) result += self.plist.dict_add(key, "BundleId", key) result += self.plist.dict_add(key, "Executable", target) result += self.plist.dict_add(key, "Registered", target) result += self.plist.dict_add(key, "Hide", 0, "int") if requirement: result += self.plist.dict_add(key, "Requirement", requirement) result += self.plist.dict_add(key, "Whitelisted", "FALSE", "bool") if result: # There was an error. raise RuntimeError( "Failed to insert executable {}.".format(target)) self.logger.info("Inserted successfully.") def __enter__(self): """ Allows for the LSEdit object to be used in a 'with' clause. """ return self def __exit__(self, type, value, traceback): """ Allows for the LSEdit object to be used in a 'with' clause. """ # Make sure that the locationd launchd item is reactivated. self.__enable() def __enable(self): """ Enables the locationd system. """ enable() self.logger.info("Enabled locationd system.") def __disable(self): """ Disables the locationd system. """ disable() self.logger.info( "Disabled locationd system. (This is normal. DON'T PANIC.)")
class LSEdit(object): """ Provides a class for modifying the Location Services permissions. This class was designed to be used in a 'with' statement to ensure proper updating of the locationd system. For example: with LSEdit() as e: # do some stuff to the database e.foo() # do more stuff bar(baz) """ def __init__(self, logger, no_check=False, no_check_type=None): # Set the logger for output. self.logger = logger # Set the administrative override flag. self.no_check = no_check # Check what type of application we're adding. if self.no_check and no_check_type == 'app': raise RuntimeError("Location Services does not support adding applications with the `--no-check-app` flag.") # Only root may modify the Location Services system. if os.geteuid() != 0: raise RuntimeError("Must be root to modify Location Services!") # Check the version of OS X before continuing; only Darwin versions 10 # and above support the location services system. try: version = int(os.uname()[2].split('.')[0]) except: raise RuntimeError("Could not acquire the OS X version.") if version < 10: raise RuntimeError("Location Services is not supported in this version of OS X.") self.version = version # Disable the locationd launchd item. (Changes will not be properly # cached if this is not done.) self.__disable() # This is where the applications' authorizations are stored. self.plist = PlistEditor('/var/db/locationd/clients') self.logger.info("Modifying service 'location' at '{}'.".format(self.plist.path)) def insert(self, target): """ Enable the specified target for location services. :param target: an application or file to modify permissions for """ # If no application is given, then we're modifying the global Location # Services system. if not target: self.logger.info("Enabling service 'location' globally.") enable_global(True, self.logger) self.logger.info("Globally enabled successfully.") return # If we're in admin mode, we can't look up the application as a bundle. if self.no_check: self.__insert_executable(target) else: self.__insert_app(target) def remove(self, target): """ Remove an item from Location Services. If no item is given, then disable Location Services globally. :param target: an application or file to modify permissions for """ # If no application is given, then we're modifying the global Location # Services system. if not target: self.logger.info("Disabling service 'location' globally...") enable_global(False, self.logger) self.logger.info("Globally disabled successfully.") return if self.no_check: name = target target = 'com.apple.locationd.executable-{}'.format(target) else: name = AppInfo(target).name target = AppInfo(target).bid # Verbosity self.logger.info("Removing '{}' from service 'location'...".format(target)) # Otherwise, just delete its entry in the plist. result = self.plist.delete(target) if result: raise RuntimeError("Failed to remove {}.".format(name)) self.logger.info("Removed successfully.") def disable(self, target): """ Mark the application or file as being disallowed from utilizing Location Services. If the target is not already in the plist, it will be added and then disabled. :param target: an application or file to modify permissions for """ # If no application is given, then we're modifying the global Location # Services system. if not target: self.logger.info("Disabling service 'location' globally...") enable_global(False, self.logger) self.logger.info("Globally disabled successfully.") return if self.no_check: name = target target = 'com.apple.locationd.executable-{}'.format(target) else: name = AppInfo(target).name target = AppInfo(target).bid # Verboseness self.logger.info("Disabling '{}' in service 'location'...".format(target)) # If the application isn't already in locationd, add it. if not self.plist.read(target): self.insert(target) # Then deauthorize the application. result = self.plist.dict_add(target, "Authorized", "FALSE", "bool") if result: raise RuntimeError("Failed to disable {}.".format(name)) self.logger.info("Disabled successfully.") def __insert_app(self, target): """ Inserts the specified target application into the locationd plist. """ # Get the AppInfo object for more information. app = AppInfo(target) # Verbosity! self.logger.info("Inserting '{}' into service 'location'...".format(app.bid)) # This is used for... something. Don't know what, but it's necessary. requirement = ("identifier \"{}\" and anchor {}".format( app.bid, app.bid.split('.')[1] )) # Write the changes to the locationd plist. result = 0 result += self.plist.dict_add(app.bid, "Authorized", "TRUE", "bool") result += self.plist.dict_add(app.bid, "BundleID", app.bid) result += self.plist.dict_add(app.bid, "BundleId", app.bid) result += self.plist.dict_add(app.bid, "BundlePath", app.path) result += self.plist.dict_add(app.bid, "Executable", app.executable) result += self.plist.dict_add(app.bid, "Registered", app.executable) result += self.plist.dict_add(app.bid, "Hide", 0, "int") result += self.plist.dict_add(app.bid, "Requirement", requirement) result += self.plist.dict_add(app.bid, "Whitelisted", "FALSE", "bool") if result: # Clearly there was an error... raise RuntimeError("Failed to insert {}.".format(app.name)) self.logger.info("Inserted successfully.") def __insert_executable(self, target): """ Inserts the specified target executable into the locationd plist. """ # Verbosity! self.logger.info("Inserting executable '{}' into service 'location'...".format(target)) # Reformat the target name. key = 'com.apple.locationd.executable-{}'.format(target) # Find the Code Directory Hash (CDHash). # If the command exits with a non-zero exit status, report to the user. # This generally indicates it does not have a code signature. try: codesign = subprocess.check_output( ['/usr/bin/codesign', '--display', '--verbose=4', target], stderr=subprocess.STDOUT ).split('\n') except subprocess.CalledProcessError: self.logger.warn("Executable '{}' is not signed. Adding anyway...".format(target)) codesign = [] # Filter the results. codesign = [x for x in codesign if x is not None and x != ''] codesign = [x for x in codesign if '=' in x] # After this, 'cdhash' will either be an empty list (no match), or else # it should have one element with the cdhash in it. cdhash = [x.split('=')[1] for x in codesign if 'CDHash' in x] # Build the requirement string from the cdhash if we have one. if len(cdhash) == 1: requirement = ("cdhash H\"{cdhash}\"".format(cdhash=cdhash[0])) else: requirement = None # Write the changes to the locationd plist. result = 0 result += self.plist.dict_add(key, "Authorized", "TRUE", "bool") result += self.plist.dict_add(key, "BundleID", key) result += self.plist.dict_add(key, "BundleId", key) result += self.plist.dict_add(key, "Executable", target) result += self.plist.dict_add(key, "Registered", target) result += self.plist.dict_add(key, "Hide", 0, "int") if requirement: result += self.plist.dict_add(key, "Requirement", requirement) result += self.plist.dict_add(key, "Whitelisted", "FALSE", "bool") if result: # There was an error. raise RuntimeError("Failed to insert executable {}.".format(target)) self.logger.info("Inserted successfully.") def __enter__(self): """ Allows for the LSEdit object to be used in a 'with' clause. """ return self def __exit__(self, type, value, traceback): """ Allows for the LSEdit object to be used in a 'with' clause. """ # Make sure that the locationd launchd item is reactivated. self.__enable() def __enable(self): """ Enables the locationd system. """ enable() self.logger.info("Enabled locationd system.") def __disable(self): """ Disables the locationd system. """ disable() self.logger.info("Disabled locationd system. (This is normal. DON'T PANIC.)")