"""Various system configuration related functions. Largely copied from http://code.google.com/p/pymacadmin/source/browse/lib/PyMacAdmin/SCUtilities/SCPreferences.py and http://code.google.com/p/pymacadmin/source/browse/examples/crankd/socks-proxy/ProxyManager.py """ import logging import os import struct # pylint: disable=g-import-not-at-top from . import gmacpyutil from . import defaults try: from Foundation import NSMutableDictionary from Foundation import NSString from SystemConfiguration import SCDynamicStoreAddValue from SystemConfiguration import SCDynamicStoreCopyValue from SystemConfiguration import SCDynamicStoreCreate from SystemConfiguration import SCDynamicStoreSetValue from SystemConfiguration import SCPreferencesApplyChanges from SystemConfiguration import SCPreferencesCommitChanges from SystemConfiguration import SCPreferencesCreate from SystemConfiguration import SCPreferencesPathGetValue from SystemConfiguration import SCPreferencesPathSetValue except ImportError: if os.uname()[0] == 'Linux': logging.debug('Skipping Mac imports for later mock purposes.') # pylint: disable=g-bad-name NSMutableDictionary = NSString = None SCDynamicStoreAddValue = SCDynamicStoreCopyValue = None SCDynamicStoreCreate = SCDynamicStoreSetValue = None SCPreferencesApplyChanges = SCPreferencesCommitChanges = None SCPreferencesCreate = None SCPreferencesPathGetValue = SCPreferencesPathSetValue = None # pylint: enable=g-bad-name else: raise # pylint: enable=g-import-not-at-top CORP_PROXY = defaults.CORP_PROXY NI_PLIST = '/Library/Preferences/SystemConfiguration/NetworkInterfaces.plist' class SysconfigError(Exception): """Module specific exception class.""" class SystemProfilerError(Exception): """Error retrieving results from system_profiler.""" class InterfaceError(Exception): """Error reading network interface data.""" class SCDynamicPreferences(object): """Utility Class for working with the SystemConfiguration Dynamic store.""" store = None def __init__(self): super(SCDynamicPreferences, self).__init__() self.store = SCDynamicStoreCreate(None, 'gmacpyutil', None, None) def ReadProxySettings(self): """Read proxy setting from SCDynamicStore.""" return SCDynamicStoreCopyValue(self.store, 'State:/Network/Global/Proxies') def SetProxy(self, enable=True, pac=CORP_PROXY): """Set proxy autoconfig.""" proxies = NSMutableDictionary.dictionaryWithDictionary_( self.ReadProxySettings()) logging.debug('initial proxy settings: %s', proxies) proxies['ProxyAutoConfigURLString'] = pac if enable: proxies['ProxyAutoConfigEnable'] = 1 else: proxies['ProxyAutoConfigEnable'] = 0 logging.debug('Setting ProxyAutoConfigURLString to %s and ' 'ProxyAutoConfigEnable to %s', pac, enable) result = SCDynamicStoreSetValue(self.store, 'State:/Network/Global/Proxies', proxies) logging.debug('final proxy settings: %s', self.ReadProxySettings()) return result def SetCorpSetupKey(self, key, value): """Set key-value pair under the CORP_SETUP tree. Args: key: String, key to add value: string/integer if single value, dict if multiple values. Returns: Boolean, True if successful. Raises: SysconfigError: On failure (SCDynamicStore* does return False on failure, we don't get any better errors). """ long_key = '%s%s' % (CORP_SETUP, key) if SCDynamicStoreCopyValue(self.store, long_key) is not None: key_set = SCDynamicStoreSetValue(self.store, long_key, value) if key_set: logging.debug('Setting %s to value %s', long_key, value) return True else: raise SysconfigError('Failed setting %s with value %s' % ( long_key, value)) else: add = SCDynamicStoreAddValue(self.store, long_key, value) if add: logging.debug('Adding %s with value %s', long_key, value) return True else: raise SysconfigError('Failed adding %s with value %s' % ( long_key, value)) def GetCorpSetupKey(self, key): """Get key-value pair from the CORP_SETUP tree. Args: key: String, key to look up Returns: value: String if single value, dictionary if plist. Raises: SysconfigError: On failure (see above). """ long_key = '%s%s' % (CORP_SETUP, key) key = SCDynamicStoreCopyValue(self.store, long_key) if key is not None: return key else: logging.debug('Failed retrieving %s', long_key) raise SysconfigError('Failed retrieving %s' % long_key) class SCPreferences(object): """Utility class for working with the SystemConfiguration framework.""" session = None def __init__(self): super(SCPreferences, self).__init__() self.session = SCPreferencesCreate(None, 'gmacpyutil', None) def Save(self): """Commits changes to permanent store, applies to running config.""" if not self.session: return if not SCPreferencesCommitChanges(self.session): raise SysconfigError('Unable to save SystemConfiguration changes.') if not SCPreferencesApplyChanges(self.session): raise SysconfigError('Unable to apply SystemConfiguration changes.') def GetPathValue(self, path): """Gets the preferences path value for a given path.""" base = os.path.basename(path) tree = os.path.dirname(path) settings = SCPreferencesPathGetValue(self.session, tree) if not settings: # SCPreferencesPathGetValue returns a dict or None if the path doesn't # exist. Just pass along None in the second case. return None if base is '': # If base is '', the path is '/' so we just return the whole tree return settings if base in settings: return settings[base] else: return None def SetPathValue(self, path, value): """Sets the path value for a given path.""" base = os.path.basename(path) if not base: raise SysconfigError('Updating %s not permitted.' % path) tree = os.path.dirname(path) settings = SCPreferencesPathGetValue(self.session, tree) if not settings: settings = NSMutableDictionary.alloc().init() settings[base] = value SCPreferencesPathSetValue(self.session, tree, settings) def SetProxy(self, enable=True, pac=CORP_PROXY): """Sets the proxy autoconfiguration URL and enables or disables it.""" interfaces = self.GetPathValue(u'/NetworkServices') for interface in interfaces: # Some interfaces, (e.g. some 3G modem dummy interfaces) don't # always have a proxy key, so we simply ignore them. if 'Proxies' not in interfaces[interface]: continue if enable: interfaces[interface]['Proxies']['ProxyAutoConfigEnable'] = 1 interfaces[interface]['Proxies']['ProxyAutoConfigURLString'] = pac else: interfaces[interface]['Proxies']['ProxyAutoConfigEnable'] = 0 self.SetPathValue(u'/NetworkServices', interfaces) def GetComputerName(self): """Gets the current ComputerName.""" return self.GetPathValue(u'/System/System/ComputerName') def GetLocalName(self): """Gets the current LocalName.""" return self.GetPathValue(u'/System/Network/HostNames/LocalHostName') def GetHostName(self): """Gets the current HostName.""" return self.GetPathValue(u'/System/System/HostName') def SetComputerName(self, computername): """Sets the Local name for the machine.""" current_computername = self.GetPathValue(u'/System/System/ComputerName') if current_computername != computername: self.SetPathValue(u'/System/System/ComputerName', computername) def SetLocalName(self, localname): """Sets the Computer name for the machine.""" current_localname = self.GetPathValue( u'/System/Network/HostNames/LocalHostName') if current_localname != localname: self.SetPathValue(u'/System/Network/HostNames/LocalHostName', localname) def SetHostName(self, hostname): """Sets the Hostname for the machine.""" current_hostname = self.GetPathValue(u'/System/System/HostName') if current_hostname != hostname: self.SetPathValue(u'/System/System/HostName', hostname) class SystemProfiler(object): """Utility Class for parsing system_profiler data.""" _cache = {} def _GetSystemProfilerOutput(self, sp_type): logging.debug('Getting system_profiler output for %s', sp_type) argv = ['/usr/sbin/system_profiler', '-XML', sp_type] stdout, unused_stderr, returncode = gmacpyutil.RunProcess(argv) if returncode is not 0: raise SystemProfilerError('Could not run %s' % argv) else: return stdout def _GetSystemProfile(self, sp_type): # pylint: disable=global-statement if sp_type not in self._cache: logging.debug('%s not cached', sp_type) sp_xml = self._GetSystemProfilerOutput(sp_type) self._cache[sp_type] = NSString.stringWithString_(sp_xml).propertyList() return self._cache[sp_type] def GetMBSerialNumber(self): """Retrieves the Mainboard serial number. Returns: string of serial number """ sp_type = 'SPHardwareDataType' for data in self._GetSystemProfile(sp_type): if data.get('_dataType', None) == sp_type: for item in data['_items']: if 'serial_number' in item: logging.debug('serial_number: %s', item['serial_number']) return item['serial_number'] return None def GetMBModelNumber(self): """Retrieves the Mainboard machine model. Returns: string of model number """ sp_type = 'SPHardwareDataType' for data in self._GetSystemProfile(sp_type): if data.get('_dataType', None) == sp_type: for item in data['_items']: if 'machine_model' in item: logging.debug('machine_model: %s', item['machine_model']) return item['machine_model'] return None def GetHWUUID(self): """Retrieves the Hardware UUID. Returns: string of UUID """ sp_type = 'SPHardwareDataType' for data in self._GetSystemProfile(sp_type): if data.get('_dataType', None) == sp_type: for item in data['_items']: if 'platform_UUID' in item: logging.debug('platform_UUID: %s', item['platform_UUID']) return item['platform_UUID'] return None def GetDiskSerialNumber(self): """Retrieves the primary disk serial number. Returns: string of serial number Raises: SystemProfilerError: when disk0 is not found on SATA bus. """ # the order is important so we prefer SATA, NVMe, RAID then finally PATA. sp_types = ['SPSerialATADataType', 'SPNVMeDataType', 'SPHardwareRAIDDataType', 'SPParallelATADataType'] for sp_type in sp_types: for data in self._GetSystemProfile(sp_type): if data.get('_dataType', None) == sp_type: for controller in data.get('_items', []): for device in controller.get('_items', []): if device.get('bsd_name', '').find('disk0') > -1: logging.debug('device_serial: %s', device['device_serial']) return device['device_serial'] raise SystemProfilerError('Could not find disk0') def GetMacAddresses(): """Retrieves the MAC addresses of all *built-in* interfaces. Returns: List of: uppercase string of MAC address without ':' """ mac_addresses = [] for interface in GetDot1xInterfaces(): cur_mac = interface['mac'] if cur_mac: mac_addresses.append(cur_mac.replace(':', '').upper()) return mac_addresses def _GetMACFromData(data): """Unpacks and formats MAC address data. Args: data: buffer, usually an NSCFData object Returns: string containing the MAC address Raises: InterfaceError: if data can't be unpacked """ try: unpacked = struct.unpack_from('BBBBBB', data) except struct.error as e: logging.error('Could not unpack MAC address data: %s', e) raise InterfaceError(e) return ':'.join(['{:02x}'.format(i) for i in unpacked]) def GetNetworkInterfaces(): """Retrieves attributes of all network interfaces. Returns: Array of dict or empty array """ interfaces = [] ni_data = gmacpyutil.GetPlist(NI_PLIST) for cur_int in ni_data['Interfaces']: interface = {} interface['type'] = unicode(cur_int['SCNetworkInterfaceType']) interface['mac'] = _GetMACFromData(cur_int['IOMACAddress']) interface['name'] = unicode( cur_int['SCNetworkInterfaceInfo']['UserDefinedName']) interface['dev'] = unicode(cur_int['BSD Name']) interface['bus'] = unicode(cur_int['IOPathMatch']) interface['builtin'] = cur_int['IOBuiltin'] interfaces.append(interface) return interfaces def GetDot1xInterfaces(): """Retrieves attributes of all dot1x compatible interfaces. Returns: Array of dict or empty array """ interfaces = [] for interface in GetNetworkInterfaces(): if interface['type'] == 'IEEE80211' or interface['type'] == 'Ethernet': if (interface['builtin'] and 'AppleThunderboltIPPort' not in interface['bus']): interfaces.append(interface) return interfaces def ConfigureSystemProxy(proxy=CORP_PROXY, enable=True): """Sets the system proxy to the specified value.""" scd_prefs = SCDynamicPreferences() if not scd_prefs.SetProxy(enable=enable, pac=proxy): logging.error('Could not change proxy settings.') def GetLocalHostname(): """Gets the .local hostname. Returns: string with local hostname, 'noname' if none is set. """ session = SCPreferencesCreate(None, 'gmacpyutil', None) try: hostname = SCPreferencesPathGetValue( session, '/System/Network/HostNames/')['LocalHostName'] except TypeError: # LocalHostName (scutil --get LocalHostName) is not set. hostname = None if hostname: return hostname else: return 'noname' def GetLocalName(): """Get the local name.""" sc_prefs = SCPreferences() return sc_prefs.GetLocalName() def GetComputerName(): """Get the computer name.""" sc_prefs = SCPreferences() return sc_prefs.GetComputerName() def GetHostName(): """Get the hostname.""" sc_prefs = SCPreferences() return sc_prefs.GetHostName() def ConfigureLocalName(localname): """Sets the local name to the specified value.""" sc_prefs = SCPreferences() sc_prefs.SetLocalName(localname) sc_prefs.Save() def ConfigureComputerName(computername): """Sets the computer name to the specified value.""" sc_prefs = SCPreferences() sc_prefs.SetComputerName(computername) sc_prefs.Save() def ConfigureHostName(hostname): """Sets the hostname to a specified value.""" sc_prefs = SCPreferences() sc_prefs.SetHostName(hostname) sc_prefs.Save()