class zzzTestConfigureRemoteManagement(RuleTest): def setUp(self): RuleTest.setUp(self) self.rule = ConfigureRemoteManagement(self.config, self.environ, self.logdispatch, self.statechglogger) self.cmdhelper = CommandHelper(self.logdispatch) self.rulename = self.rule.rulename self.rulenumber = self.rule.rulenumber def tearDown(self): pass def runTest(self): self.simpleRuleTest() def setConditionsForRule(self): ''' Configure system for the unit test @param self: essential if you override this definition @return: boolean - If successful True; If failure False @author: ekkehard j. koch ''' success = True setupdict = {"ARD_AllLocalUsers": "True", "ScreenSharingReqPermEnabled": "False", "VNCLegacyConnectionsEnabled": "True", "LoadRemoteManagementMenuExtra": "False"} for key in setupdict: self.cmdhelper.executeCommand("defaults write /Library/Preferences/com.apple.RemoteManagement " + key + " -bool " + setupdict[key]) errorout = self.cmdhelper.getError() if errorout: success = False return success def checkReportForRule(self, pCompliance, pRuleSuccess): ''' check on whether report was correct @param self: essential if you override this definition @param pCompliance: the self.iscompliant value of rule @param pRuleSuccess: did report run successfully @return: boolean - If successful True; If failure False @author: ekkehard j. koch ''' self.logdispatch.log(LogPriority.DEBUG, "pCompliance = " + \ str(pCompliance) + ".") self.logdispatch.log(LogPriority.DEBUG, "pRuleSuccess = " + \ str(pRuleSuccess) + ".") success = True return success def checkFixForRule(self, pRuleSuccess): ''' check on whether fix was correct @param self: essential if you override this definition @param pRuleSuccess: did report run successfully @return: boolean - If successful True; If failure False @author: ekkehard j. koch ''' self.logdispatch.log(LogPriority.DEBUG, "pRuleSuccess = " + \ str(pRuleSuccess) + ".") success = True return success def checkUndoForRule(self, pRuleSuccess): ''' check on whether undo was correct @param self: essential if you override this definition @param pRuleSuccess: did report run successfully @return: boolean - If successful True; If failure False @author: ekkehard j. koch ''' self.logdispatch.log(LogPriority.DEBUG, "pRuleSuccess = " + \ str(pRuleSuccess) + ".") success = True return success
class SystemIntegrityProtectionObject(): '''the SystemIntegrityProtectionObject gets System Integrity Protection(SIP) data from the local system @author: ekkehard ''' def __init__(self, logdispatcher): ''' initialize lanlMacInfo @author: ekkehard ''' # Make sure we have the full path for all commands self.logdispatch = logdispatcher self.ch = CommandHelper(self.logdispatch) self.csrutil = "/usr/bin/csrutil" self.sw_vers = "/usr/bin/sw_vers" self.osx_major_version = 0 self.osx_minor_version = 0 self.osx_version_string = "" self.initializeOSXVersionBoolean = False self.initializeSIPStatusBoolean = False self.sipstatus = "" # Reset messages self.messageReset() def getSIPStatus(self): '''get the current System Integrity Protection (SIP) status @author: ekkehard :returns: dictionary entry ''' osx_version = self.initializeOSXVersion() sipstatus = self.initializeSIPStatus() msg = "SIP status is " + str( sipstatus) + ". OS X Version string is " + str(osx_version) + "." self.logdispatch.log(LogPriority.DEBUG, msg) return self.sipstatus def initializeOSXVersion(self, forceInitializtion=False): '''initialize OS X version info @author: ekkehard :param forceInitializtion: (Default value = False) :returns: boolean - True ''' success = True if forceInitializtion: self.initializeOSXVersionBoolean = False if not self.initializeOSXVersionBoolean: try: self.initializeOSXVersionBoolean = True # Get the major version of OS X command = self.sw_vers + " -productVersion | awk -F. '{print $1}'" self.ch.executeCommand(command) errorcode = self.ch.getError() osxmajorversion = self.ch.getOutputString().strip() self.osx_major_version = int(osxmajorversion) msg = "(" + str(errorcode) + ") OS X Major Version is " + str( self.osx_major_version) self.logdispatch.log(LogPriority.DEBUG, msg) # Get the minor version of OS X command = self.sw_vers + " -productVersion | awk -F. '{print $2}'" self.ch.executeCommand(command) errorcode = self.ch.getError() osxminorversion = self.ch.getOutputString().strip() self.osx_minor_version = int(osxminorversion) msg = "(" + str(errorcode) + ") OS X Minor Version is " + str( self.osx_minor_version) self.logdispatch.log(LogPriority.DEBUG, msg) # Get full version string command = self.sw_vers + " -productVersion" self.ch.executeCommand(command) errorcode = self.ch.getError() self.osx_version_string = self.ch.getOutputString().strip() self.osx_minor_version = int(osxminorversion) msg = "(" + str(errorcode) + ") OS X Version string is " + str( self.osx_minor_version) self.logdispatch.log(LogPriority.DEBUG, msg) except (KeyboardInterrupt, SystemExit): raise except Exception: msg = traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, msg) success = False return success def initializeSIPStatus(self, forceInitializtion=False): '''Initialize SIP Status @author: ekkehard :param forceInitializtion: (Default value = False) :returns: boolean - True ''' success = True if forceInitializtion: self.initializeSIPStatusBoolean = False if not self.initializeSIPStatusBoolean: try: self.initializeSIPStatusBoolean = True if self.osx_major_version < 10: self.sipstatus = "Not Applicable" elif self.osx_minor_version < 11: command = self.sw_vers + " -productVersion" self.ch.executeCommand(command) errorcode = self.ch.getError() self.sipstatus = "Not Applicable For " + self.osx_version_string msg = "(" + str(errorcode) + ") SIP status is " + str( self.sipstatus) self.logdispatch.log(LogPriority.DEBUG, msg) elif os.path.exists(self.csrutil): command = self.csrutil + " status | awk '/status/ {print $5}' | sed 's/\.$//'" self.ch.executeCommand(command) errorcode = self.ch.getError() sipstatus = self.ch.getOutputString().strip() if sipstatus == "disabled": self.sipstatus = "Disabled" elif sipstatus == "enabled": self.sipstatus = "Enabled" else: self.sipstatus = "Not Applicable - " + str( sipstatus) + " not a know status value." msg = "(" + str(errorcode) + ") SIP status is " + str( self.sipstatus) self.logdispatch.log(LogPriority.DEBUG, msg) else: self.sipstatus = "Not Applicable - " + str( self.csrutil) + " not available!" msg = "The System Ingegrity Protection status is " + str( self.sipstatus) self.logdispatch.log(LogPriority.DEBUG, msg) except (KeyboardInterrupt, SystemExit): raise except Exception: msg = traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, msg) success = False return success def report(self): '''report if the SIP status is anabled or disabled. :param self: essential if you override this definition :returns: boolean @note: None ''' compliant = True sipstatus = self.getSIPStatus() if sipstatus == "Disabled": compliant = False msg = self.messageAppend( "- System Ingegrity Protection (SIP) is disabled but should be enabled!" ) self.logdispatch.log(LogPriority.DEBUG, msg) elif sipstatus == "Enabled": msg = self.messageAppend( "- System Ingegrity Protection (SIP) is enabled!") self.logdispatch.log(LogPriority.DEBUG, msg) else: msg = self.messageAppend("- " + str(sipstatus)) self.logdispatch.log(LogPriority.DEBUG, msg) return compliant def fix(self): fixed = True return fixed def messageGet(self): '''get the formatted message string. @author: ekkehard j. koch :param self: essential if you override this definition :returns: string @note: None ''' return self.msg def messageAppend(self, pMessage=""): '''append and format message to the message string. @author: ekkehard j. koch :param self: essential if you override this definition :param pMessage: (Default value = "") :returns: boolean - true @note: None ''' datatype = type(pMessage) if datatype == str: if not (pMessage == ""): msg = pMessage if (self.msg == ""): self.msg = msg else: self.msg = self.msg + "\n" + \ msg elif datatype == list: if not (pMessage == []): for item in pMessage: msg = item if (self.msg == ""): self.msg = msg else: self.msg = self.msg + "\n" + \ msg else: raise TypeError("pMessage with value" + str(pMessage) + \ "is of type " + str(datatype) + " not of " + \ "type " + str(str) + \ " or type " + str(list) + \ " as expected!") return self.msg def messageReset(self): '''reset the message string. @author: ekkehard j. koch :param self: essential if you override this definition :returns: boolean - true @note: none ''' self.msg = "" return self.msg
class networksetup(): '''This objects encapsulates the complexities of the networksetup command on macOS (OS X) @author: ekkehard j. koch ''' ############################################################################### def __init__(self, logdispatcher): self.location = "" self.locationIsValidWiFiLocation = False self.locationInitialized = False self.ns = {} self.nsInitialized = False self.nso = {} self.nsInitialized = False self.resultReset() self.nsc = "/usr/sbin/networksetup" self.logdispatch = logdispatcher # This class can, in no way, continue if # These constants are undefined, or set to # None if not DNS: self.logdispatch.log(LogPriority.DEBUG, "Please ensure that the following constants, in localize.py, are correctly defined and are not None: DNS, PROXY, PROXYCONFIGURATIONFILE, PROXYDOMAIN. Networksetup.py will not function without these!") return None elif DNS == None: self.logdispatch.log(LogPriority.DEBUG, "Please ensure that the following constants, in localize.py, are correctly defined and are not None: DNS, PROXY, PROXYCONFIGURATIONFILE, PROXYDOMAIN. Networksetup.py will not function without these!") return None if not PROXY: self.logdispatch.log(LogPriority.DEBUG, "Please ensure that the following constants, in localize.py, are correctly defined and are not None: DNS, PROXY, PROXYCONFIGURATIONFILE, PROXYDOMAIN. Networksetup.py will not function without these!") return None elif PROXY == None: self.logdispatch.log(LogPriority.DEBUG, "Please ensure that the following constants, in localize.py, are correctly defined and are not None: DNS, PROXY, PROXYCONFIGURATIONFILE, PROXYDOMAIN. Networksetup.py will not function without these!") return None if not PROXYCONFIGURATIONFILE: self.logdispatch.log(LogPriority.DEBUG, "Please ensure that the following constants, in localize.py, are correctly defined and are not None: DNS, PROXY, PROXYCONFIGURATIONFILE, PROXYDOMAIN. Networksetup.py will not function without these!") return None elif PROXYCONFIGURATIONFILE == None: self.logdispatch.log(LogPriority.DEBUG, "Please ensure that the following constants, in localize.py, are correctly defined and are not None: DNS, PROXY, PROXYCONFIGURATIONFILE, PROXYDOMAIN. Networksetup.py will not function without these!") return None if not PROXYDOMAIN: self.logdispatch.log(LogPriority.DEBUG, "Please ensure that the following constants, in localize.py, are correctly defined and are not None: DNS, PROXY, PROXYCONFIGURATIONFILE, PROXYDOMAIN. Networksetup.py will not function without these!") return None elif PROXYDOMAIN == None: self.logdispatch.log(LogPriority.DEBUG, "Please ensure that the following constants, in localize.py, are correctly defined and are not None: DNS, PROXY, PROXYCONFIGURATIONFILE, PROXYDOMAIN. Networksetup.py will not function without these!") return None fullproxy = PROXY self.ps = fullproxy.split(":")[-2].strip('//') self.pp = fullproxy.split(":")[-1] self.pf = PROXYCONFIGURATIONFILE self.dns = DNS self.searchdomain = PROXYDOMAIN self.domainByPass = PROXYDOMAINBYPASS self.ch = CommandHelper(self.logdispatch) self.initialized = False self.nameofdevice = "" self.notinservicelist = False self.detailedresults = "" ############################################################################### def report(self): '''report is designed to implement the report portion of the stonix rule :param self: essential if you override this definition @author: ekkehard j. koch @change: Breen Malmberg - 12/21/2016 - doc string revision; minor refactor; try/except :returns: compliant :rtype: bool ''' self.logdispatch.log(LogPriority.DEBUG, "Entering networksetup.report()...\n") compliant = True if not self.initialize(): self.logdispatch.log(LogPriority.DEBUG, "self.initialize() failed!") self.resultReset() try: if self.locationIsValidWiFiLocation: self.resultAppend("WiFi Network Setup for " + \ "services for location named " + \ str(self.location)) else: self.resultAppend("Non-WiFi Network Setup for " + \ "services for location named " + \ str(self.location)) for key in sorted(self.nso): network = self.nso[key] networkvalues = self.ns[network] networkname = networkvalues["name"] networktype = networkvalues["type"] networkenabled = networkvalues["enabled"] self.logdispatch.log(LogPriority.DEBUG, "key is " + str(key) + "\n") self.logdispatch.log(LogPriority.DEBUG, "network name is " + str(networkname) + "\n") self.logdispatch.log(LogPriority.DEBUG, "networktype is " + str(networktype) + "\n") self.logdispatch.log(LogPriority.DEBUG, "networkenabled is " + str(networkenabled) + "\n") self.logdispatch.log(LogPriority.DEBUG, "self.locationIsValidWiFiLocation is " + str(self.locationIsValidWiFiLocation) + "\n") if networktype == "bluetooth" and networkenabled: self.logdispatch.log(LogPriority.DEBUG, "networktype is bluetooth and it is enabled. Setting compliant to False!") compliant = False networkvalues["compliant"] = False elif networktype == "wi-fi" and networkenabled and not self.locationIsValidWiFiLocation: self.logdispatch.log(LogPriority.DEBUG, "networktype is wi-fi and it is enabled. This is not a valid wi-fi location. Setting compliant to False!") compliant = False networkvalues["compliant"] = False else: networkvalues["compliant"] = True if networkvalues["compliant"]: messagestring = str(networkname) + " is compliant " + \ ": " + str(networkvalues) else: messagestring = str(networkname) + " is NOT " + \ "compliant : " + str(networkvalues) self.resultAppend(str(key) + " - " + messagestring) self.logdispatch.log(LogPriority.DEBUG, "Exiting networksetup.report() with compliant = " + str(compliant) + "\n") except Exception: raise return compliant ############################################################################### def fix(self): '''fix is designed to implement the fix portion of the stonix rule :returns: fixed :rtype: bool @author: ekkehard j. koch @change: Breen Malmberg - 1/12/2017 - added debug logging; doc string edit; added try/except ''' self.logdispatch.log(LogPriority.DEBUG, "Entering networksetup.fix()...") fixed = True self.logdispatch.log(LogPriority.DEBUG, "Running self.initialize()...") if not self.initialize(): self.logdispatch.log(LogPriority.DEBUG, "self.initialize() failed!") self.resultReset() messagestring = "for location = " + str(self.location) try: for key in sorted(self.nso): network = self.nso[key] networkvalues = self.ns[network] networkname = networkvalues["name"] networktype = networkvalues["type"] networkenabled = networkvalues["enabled"] self.logdispatch.log(LogPriority.DEBUG, "ns(key, network, networktype, networkenabled) = (" + str(key) + ", " + str(network) + ", " + str(networktype) + ", " + str(networkenabled) + ")") self.logdispatch.log(LogPriority.DEBUG, "self.locationIsValidWiFiLocation is " + str(self.locationIsValidWiFiLocation) + "\n") if networktype == "bluetooth" and networkenabled: self.logdispatch.log(LogPriority.DEBUG, "Running disableNetworkService(" + str(networkname) + ")...") fixedWorked = self.disableNetworkService(networkname) if fixedWorked: networkvalues["compliant"] = True messagestring = str(networkname) + " fixed " + \ ": " + str(networkvalues) else: fixed = False elif networktype == "wi-fi" and networkenabled and not self.locationIsValidWiFiLocation: self.logdispatch.log(LogPriority.DEBUG, "Running disableNetworkService(" + str(networkname) + ")...") fixedWorked = self.disableNetworkService(networkname) if fixedWorked: self.logdispatch.log(LogPriority.DEBUG, "Fix worked!") networkvalues["compliant"] = True messagestring = str(networkname) + " fixed " + \ ": " + str(networkvalues) else: self.logdispatch.log(LogPriority.DEBUG, "Fix did NOT work!") fixed = False elif networktype == "wi-fi" and not networkenabled and self.locationIsValidWiFiLocation: self.logdispatch.log(LogPriority.DEBUG, "Running enableNetwork(" + str(networkname) + ")...") fixedWorked = self.enableNetwork(networkname) if fixedWorked: networkvalues["compliant"] = True messagestring = str(networkname) + " fixed " + \ ": " + str(networkvalues) else: fixed = False else: networkvalues["compliant"] = True messagestring = "" if not messagestring == "": self.resultAppend(messagestring) except Exception: raise return fixed ############################################################################### def disableNetworkService(self, pNetworkName): '''disable network service @author: ekkehard j. koch :param self: essential if you override this definition :param pNetworkName: name of network :returns: boolean - true @note: None @change: Breen Malmberg - 3/23/2016 - wifi will now be disabled via setairportpower if not found in the service list. @change: Breen Malmberg - 12/20/2016 - minor refactor; parameter validation ;logging @change: Breen Malmberg - 1/12/2017 - added more debug logging ''' self.logdispatch.log(LogPriority.DEBUG, "Entering networksetup.disableNetworkService()...") success = True networkName = pNetworkName.strip() try: if not isinstance(pNetworkName, str): self.logdispatch.log(LogPriority.DEBUG, "Specified parameter: pNetworkName must be of type: string. Got: " + str(type(pNetworkName))) success = False if not pNetworkName: self.logdispatch.log(LogPriority.DEBUG, "Specified parameter: pNetworkName is blank or None!") success = False self.logdispatch.log(LogPriority.DEBUG, "\nnetworkName = " + str(networkName).strip().lower() + "\n") self.logdispatch.log(LogPriority.DEBUG, "\nself.nameofdevice = " + str(self.nameofdevice).strip().lower() + "\n") if str(networkName).strip().lower() == str(self.nameofdevice).strip().lower(): self.logdispatch.log(LogPriority.DEBUG, "networkName matches self.nameofdevice. Running airportpower disable command...") disablecommand = [self.nsc, "-setairportpower", networkName, "off"] self.ch.executeCommand(disablecommand) if self.ch.getReturnCode() != 0: success = False self.logdispatch.log(LogPriority.DEBUG, "Execution of command failed: " + str(disablecommand)) else: self.logdispatch.log(LogPriority.DEBUG, "Command executed successfully: " + str(disablecommand)) else: if success: command = [self.nsc, "-setnetworkserviceenabled", networkName, "off"] self.ch.executeCommand(command) if self.ch.getReturnCode() != 0: success = False self.logdispatch.log(LogPriority.DEBUG, "Execution of command failed: " + str(command)) if not success: self.logdispatch.log(LogPriority.DEBUG, "networksetup.disableNetworkService() Failed") else: self.logdispatch.log(LogPriority.DEBUG, "networksetup.disableNetworkService() was Successful") self.logdispatch.log(LogPriority.DEBUG, "Exiting networksetup.disableNetworkService()") except Exception: success = False raise return success ############################################################################### def enableNetwork(self, pNetworkName): '''enable network service @author: ekkehard j. koch :param self: essential if you override this definition :param pNetworkName: name of network :returns: boolean - true @note: None @change: Breen Malmberg - 3/23/2016 - wifi will now be enabled via setairportpower if not found in the service list. @change: Breen Malmberg - 12/20/2016 - minor refactor; parameter validation ;logging ''' self.logdispatch.log(LogPriority.DEBUG, "Entering networksetup.enableNetwork()...") success = True networkName = pNetworkName.strip() try: if not isinstance(pNetworkName, str): self.logdispatch.log(LogPriority.DEBUG, "Specified parameter: pNetworkName must be of type: string. Got: " + str(type(pNetworkName))) success = False if not pNetworkName: self.logdispatch.log(LogPriority.DEBUG, "Specified parameter: pNetworkName is blank or None!") success = False if str(networkName).strip().lower() == str(self.nameofdevice).strip().lower() and self.notinservicelist: enablecommand = [self.nsc, "-setairportpower", networkName, "on"] self.ch.executeCommand(enablecommand) if self.ch.getReturnCode() != 0: success = False self.logdispatch.log(LogPriority.DEBUG, "Execution of command failed: " + str(enablecommand)) else: if networkName == "": success = False if success: command = [self.nsc, "-setnetworkserviceenabled", networkName, "on"] self.ch.executeCommand(command) if self.ch.getReturnCode() != 0: success = False self.logdispatch.log(LogPriority.DEBUG, "Execution of command failed: " + str(command)) if not success: self.logdispatch.log(LogPriority.DEBUG, "networksetup.enableNetwork() Failed") else: self.logdispatch.log(LogPriority.DEBUG, "networksetup.enableNetwork() was Successful") self.logdispatch.log(LogPriority.DEBUG, "Exiting networksetup.enableNetwork()") except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: raise return success ############################################################################### def getDetailedresults(self): '''get the detailed results text @author: ekkehard j. koch :param self: essential if you override this definition :param pLocationName: location name :returns: string: detailedresults @note: None ''' return self.detailedresults ############################################################################### def getLocation(self): '''get the location used by on the mac @author: ekkehard j. koch :param self: essential if you override this definition :returns: boolean - true @note: None ''' try: success = True command = [self.nsc, "-getcurrentlocation"] self.ch.executeCommand(command) for line in self.ch.getOutput(): lineprocessed = line.strip() self.location = lineprocessed self.locationInitialized = True self.locationIsValidWiFiLocation = self.isValidLocationName(self.location) self.logdispatch.log(LogPriority.DEBUG, "Is this a valid WiFi location? " + str(self.locationIsValidWiFiLocation)) except (KeyboardInterrupt, SystemExit): raise except Exception: success = False raise return success ############################################################################### def initialize(self): '''initialize the object @author: ekkehard j. koch :returns: self.initalized :rtype: bool @change: Breen Malmberg - 1/12/2017 doc string fix; default init self.initialized to False; added try/except ''' self.initialized = False try: if not self.initialized: self.getLocation() self.updateCurrentNetworkConfigurationDictionary() self.initialized = True except Exception: raise return self.initialized ############################################################################### def isValidLocationName(self, pLocationName=""): '''determine if this is a valid wifi location @author: ekkehard j. koch :param self: essential if you override this definition :param pLocationName: location name (Default value = "") :returns: boolean - true @note: None ''' success = False pLocationName = pLocationName.strip() if pLocationName == "" or re.match("^\s+$", pLocationName): locationName = self.location.lower().strip() else: locationName = pLocationName.lower() if 'wi-fi' in locationName: success = True elif 'wifi' in locationName: success = True elif 'wireless' in locationName: success = True elif 'airport' in locationName: success = True elif 'off-site' in locationName: success = True elif 'offsite' in locationName: success = True else: success = False return success ############################################################################### def networksetupistnetworkserviceorderoutputprocessing(self, outputLines): success = True order = -1 networkenabled = False newserviceonnexline = False newservice = False servicename = "" networktype = False for line in outputLines: lineprocessed = line.strip() if newserviceonnexline: newservice = True newserviceonnexline = False else: newservice = False newserviceonnexline = False if lineprocessed == "An asterisk (*) denotes that a network service is disabled.": infoOnThisLine = False newserviceonnexline = True elif lineprocessed == "": infoOnThisLine = False newserviceonnexline = True else: infoOnThisLine = True if newservice and infoOnThisLine: self.logdispatch.log(LogPriority.DEBUG, "New service and info line: " + str(line)) order = order + 1 # see if network is enabled if lineprocessed[:3] == "(*)": networkenabled = False else: networkenabled = True linearray = lineprocessed.split() linearray = linearray[1:] servicename = "" for item in linearray: if servicename == "": servicename = item else: servicename = servicename + " " + item if "ethernet" in servicename.lower(): networktype = "ethernet" elif "lan" in servicename.lower(): ##### # The belkin dongles LANL has chosen to use for Apple # laptops does not identify itself vi convention, # so this is the choice roy is making to indicate the # mapping between "Belkin USB-C LAN" and ethernet. networktype = "ethernet" elif "bluetooth" in servicename.lower(): networktype = "bluetooth" elif "usb" in servicename.lower(): networktype = "usb" elif "wi-fi" in item.lower(): networktype = "wi-fi" elif "firewire" in servicename.lower(): networktype = "firewire" elif "thunderbolt" in servicename.lower(): networktype = "thunderbolt" else: networktype = "unknown" self.ns[servicename] = {"name": servicename.strip(), "hardware port": servicename.strip(), "enabled": networkenabled, "type": networktype.strip()} # determine network type elif infoOnThisLine: self.logdispatch.log(LogPriority.DEBUG, "Info line: " + str(line)) lineprocessed = lineprocessed.strip("(") lineprocessed = lineprocessed.strip(")") linearray = lineprocessed.split(",") for item in linearray: lineprocessed = item.strip() itemarray = lineprocessed.split(":") if servicename != "": if len(itemarray) > 1: self.ns[servicename][itemarray[0].strip().lower()] = itemarray[1].strip() # update dictionary entry for network self.logdispatch.log(LogPriority.DEBUG, "(servicename, enabled, networktype): (" + \ str(servicename).strip() + ", " + str(networkenabled) + ", " + \ str(networktype).strip() + ")") # create an ordered list to look up later orderkey = str(order).zfill(4) self.nso[orderkey] = servicename.strip() self.updateNetworkConfigurationDictionaryEntry(servicename.strip()) self.setNetworkServiceOrder() return success ############################################################################### def networksetuplistallhardwareportsoutputprocessing(self, outputLines): success = True newserviceonnexline = False newservice = False servicename = "" # noinfo = False for line in outputLines: lineprocessed = line.strip() if newserviceonnexline: newservice = True newserviceonnexline = False else: newservice = False newserviceonnexline = False if lineprocessed == "": infoOnThisLine = False newserviceonnexline = True else: infoOnThisLine = True # Get info from first new service line if newserviceonnexline and not servicename == "": self.updateNetworkConfigurationDictionaryEntry(servicename) elif lineprocessed == "VLAN Configurations": break elif newservice and infoOnThisLine: self.logdispatch.log(LogPriority.DEBUG, "New service and info line: " + str(line)) linearray = lineprocessed.split(":") linearray = linearray[1:] servicename = "" for item in linearray: if servicename == "": servicename = item.strip() else: servicename = servicename + " " + item.strip() if "ethernet" in servicename.lower(): networktype = "ethernet" elif "lan" in servicename.lower(): ##### # The belkin dongles LANL has chosen to use for Apple # laptops does not identify itself vi convention, # so this is the choice roy is making to indicate the # mapping between "Belkin USB-C LAN" and ethernet. networktype = "ethernet" elif "bluetooth" in servicename.lower(): networktype = "bluetooth" elif "usb" in servicename.lower(): networktype = "usb" elif "wi-fi" in servicename.lower(): networktype = "wi-fi" elif "firewire" in servicename.lower(): networktype = "firewire" elif "thunderbolt" in servicename.lower(): networktype = "thunderbolt" else: networktype = "unknown" self.ns[servicename] = {"name": servicename.strip(), "hardware port": servicename.strip(), "type": networktype.strip()} # determine network type elif infoOnThisLine: self.logdispatch.log(LogPriority.DEBUG, "Info line: " + str(line)) linearray = lineprocessed.split() colonFound = False nameOfItem = "" valueOfItem = "" for item in linearray: processedItem = item.strip() if not colonFound: if ":" in item: colonFound = True processedItem = item.strip(":") if nameOfItem == "": nameOfItem = processedItem.lower() else: nameOfItem = nameOfItem + " " + processedItem.lower() else: if valueOfItem == "": valueOfItem = processedItem else: valueOfItem = valueOfItem + " " + processedItem if not valueOfItem == "" and not nameOfItem == "": self.ns[servicename][nameOfItem] = valueOfItem.strip() return success ############################################################################### def resultAppend(self, pMessage=""): '''reset the current kveditor values to their defaults. @author: ekkehard j. koch :param self: essential if you override this definition :param pMessage: message to be appended (Default value = "") :returns: boolean - true @note: None ''' datatype = type(pMessage) if datatype == str: if not (pMessage == ""): messagestring = pMessage if (self.detailedresults == ""): self.detailedresults = messagestring else: self.detailedresults = self.detailedresults + "\n" + \ messagestring elif datatype == list: if not (pMessage == []): for item in pMessage: messagestring = item if (self.detailedresults == ""): self.detailedresults = messagestring else: self.detailedresults = self.detailedresults + "\n" + \ messagestring else: raise TypeError("pMessage with value" + str(pMessage) + \ "is of type " + str(datatype) + " not of " + \ "type " + str(str) + \ " or type " + str(list) + \ " as expected!") ############################################################################### def resultReset(self): '''reset the current kveditor values to their defaults. @author: ekkehard j. koch :param self: essential if you override this definition :returns: boolean - true @note: kveditorName is essential ''' self.detailedresults = "" ############################################################################### def setAdvancedNetworkSetup(self, pHardwarePort = None): '''Set proxies up for normal first configuration that has a network connection. @author: Roy Nielsen :param self: essential if you override this definition :param pNetworkName: name of the network to fix :param pHardwarePort: (Default value = None) :returns: boolean - true @note: None ''' success = True if pHardwarePort == None: self.initialize() self.setNetworkServiceOrder() for key in sorted(self.nso): network = self.nso[key] networkvalues = self.ns[network] networkname = networkvalues["name"] networktype = networkvalues["type"] networkhardwarePort = networkvalues["hardware port"] networkenabled = networkvalues["enabled"] msg = "networkname " + str(networkname) + "; networktype " + str(networktype) + \ "; networkhardwarePort " + str(networkhardwarePort) + "; networkenabled " + \ str(networkenabled) self.logdispatch.log(LogPriority.DEBUG, msg) if networkenabled and (networktype == "wifi" or networktype == "ethernet"): msg = "Enabled Network Found; " + msg self.logdispatch.log(LogPriority.DEBUG, msg) break else: networkhardwarePort = pHardwarePort.strip() networkenabled = True # Set the DNS servers if not networkhardwarePort == "" and networkenabled: command = self.nsc + " -setdnsservers '" + str(networkhardwarePort) + "' " + self.dns self.ch.executeCommand(command) if self.ch.getError(): msg = command + " output: " + str(self.ch.getOutput()) self.logdispatch.log(LogPriority.DEBUG, msg) success = False # Set the Search Domain command = self.nsc + " -setsearchdomains '" + str(networkhardwarePort) + "' " + self.searchdomain self.ch.executeCommand(command) if self.ch.getError(): msg = command + " output: " + str(self.ch.getOutput()) self.logdispatch.log(LogPriority.DEBUG, msg) success = False # set up the auto proxy URL command = self.nsc + " -setautoproxyurl '" + str(networkhardwarePort) + "' " + self.pf self.ch.executeCommand(command) if self.ch.getError(): msg = command + " output: " + str(self.ch.getOutput()) self.logdispatch.log(LogPriority.DEBUG, msg) success = False # Set up the FTP proxy command = self.nsc + " -setftpproxy '" + str(networkhardwarePort) + "' " + self.ps + " " + self.pp self.ch.executeCommand(command) if self.ch.getError(): msg = command + " output: " + str(self.ch.getOutput()) self.logdispatch.log(LogPriority.DEBUG, msg) success = False # Set up the HTTPS proxy command = self.nsc + " -setsecurewebproxy '" + str(networkhardwarePort) + "' " + self.ps + " " + self.pp self.ch.executeCommand(command) if self.ch.getError(): msg = command + " output: " + str(self.ch.getOutput()) self.logdispatch.log(LogPriority.DEBUG, msg) success = False # Set up the web proxy command = self.nsc + " -setwebproxy '" + str(networkhardwarePort) + "' " + self.ps + " " + self.pp self.ch.executeCommand(command) if self.ch.getError(): msg = command + " output: " + str(self.ch.getOutput()) self.logdispatch.log(LogPriority.DEBUG, msg) success = False # Get current proxy bypass domains and add self.searchdomain command = self.nsc + " -getproxybypassdomains '" + str(networkhardwarePort) + "' " self.ch.executeCommand(command) if not self.ch.getError(): command = self.nsc + " -setproxybypassdomains '" + str(networkhardwarePort) + "'" for item in self.ch.getOutput() : if not re.match("^\s*$", item) : command = command + " " + str(item.strip()) if not self.domainByPass in command: command = command + " " + str(self.domainByPass) self.ch.executeCommand(command) if not self.ch.getError(): success = False else: msg = command + " output: " + str(self.ch.getOutput()) self.logdispatch.log(LogPriority.DEBUG, msg) success = False return success ############################################################################### def setNetworkServiceOrder(self): ''' ''' ##### # Find the interface that needs to be at the top of the self.nso order cmd = ["/sbin/route", "get", "default"] self.ch.executeCommand(cmd) defaultInterface = None for line in self.ch.getOutput(): try: interface_match = re.match("\s+interface:\s+(\w+)", line) defaultInterface = interface_match.group(1) except (IndexError, KeyError, AttributeError) as err: self.logdispatch.log(LogPriority.DEBUG, str(line) + " : " + str(err)) else: self.logdispatch.log(LogPriority.DEBUG, "Found: " + str(line)) break ##### # Find the interface/service name via networksetup -listallhardwareports cmd = ["/usr/sbin/networksetup", "-listallhardwareports"] self.ch.executeCommand(cmd) hardwarePort = "" device = "" enet = "" for line in self.ch.getOutput(): try: hw_match = re.match("^Hardware Port:\s+(.*)\s*$", line) hardwarePort = hw_match.group(1) #print hardwarePort except AttributeError as err: pass try: #print line dev_match = re.match("^Device:\s+(.*)\s*$", line) device = dev_match.group(1) #print str(device) except AttributeError as err: pass try: enet_match = re.match("^Ethernet Address:\s+(\w+:\w+:\w+:\w+:\w+:\w+)\s*$", line) enet = enet_match.group(1) self.logger.log(LogPriority.DEBUG, "enet: " + str(enet)) except AttributeError as err: pass if re.match("^$", line) or re.match("^\s+$", line): if re.match("^%s$"%str(device), str(defaultInterface)): self.logdispatch.log(LogPriority.DEBUG, device) self.logdispatch.log(LogPriority.DEBUG, defaultInterface) break hardwarePort = "" device = "" enet = "" ##### # Reset NSO order if the defaultInterface is not at the top of the list newnso = {} i = 1 self.logdispatch.log(LogPriority.DEBUG, str(self.nso)) self.logdispatch.log(LogPriority.DEBUG, "hardware port: " + hardwarePort) for key, value in sorted(self.nso.items()): #print str(key) + " : " + str(value) if re.match("^%s$"%hardwarePort.strip(), value.strip()): key = re.sub("^\d\d\d\d$", "0000", key) newnso[key] = value else: orderkey = str(i).zfill(4) newnso[orderkey] = value i = i + 1 self.logdispatch.log(LogPriority.DEBUG, str(newnso)) #print str(newnso) self.nso = newnso self.logdispatch.log(LogPriority.DEBUG, str(self.nso)) for key, value in sorted(self.nso.items()): self.logdispatch.log(LogPriority.DEBUG, str(key) + " : " + str(value)) for item in self.ns: if re.match("^%s$"%hardwarePort.strip(), self.ns[item]["name"]) and self.ns[item]["type"] is "unknown" and re.match("^en", defaultInterface): self.ns[item]["type"] = "ethernet" ############################################################################### def startup(self): '''startup is designed to implement the startup portion of the stonix rule @author: ekkehard j. koch ''' disabled = True self.initialize() messagestring = "for location = " + str(self.location) for key in sorted(self.nso): network = self.nso[key] networkvalues = self.ns[network] networkname = networkvalues["name"] networktype = networkvalues["type"] networkenabled = networkvalues["enabled"] if networktype == "bluetooth" and networkenabled: fixedWorked = self.disableNetworkService(networkname) if fixedWorked: networkvalues["compliant"] = True messagestring = str(networkname) + " fixed " + \ ": " + str(networkvalues) else: disabled = False elif networktype == "wi-fi" and networkenabled: fixedWorked = self.disableNetworkService(networkname) if fixedWorked: networkvalues["compliant"] = True messagestring = str(networkname) + " fixed " + \ ": " + str(networkvalues) else: disabled = False else: networkvalues["compliant"] = True messagestring = "" if not messagestring == "": self.resultAppend(messagestring) return disabled ############################################################################### def updateCurrentNetworkConfigurationDictionary(self): '''update the network configuration dictianry @author: ekkehard j. koch :param self: essential if you override this definition :returns: boolean - true @note: None @change: Breen Malmberg - 3/23/2016 - added code to find and disable wi-fi on el capitan, via hardware ports instead of just service ''' self.logdispatch.log(LogPriority.DEBUG, "Entering updateCurrentNetworkConfigurationDictionary()...") try: success = True # issue networksetup -listallhardwareports to get all network services if success: command = [self.nsc, "-listallhardwareports"] self.ch.executeCommand(command) self.logdispatch.log(LogPriority.DEBUG, "Building ns dictionary from command: " + str(command)) success = self.networksetuplistallhardwareportsoutputprocessing(self.ch.getOutput()) # issue networksetup -listallnetworkservices to get all network services if success: command = [self.nsc, "-listnetworkserviceorder"] self.ch.executeCommand(command) self.logdispatch.log(LogPriority.DEBUG, "Building ns dictionary from command: " + str(command)) success = self.networksetupistnetworkserviceorderoutputprocessing(self.ch.getOutput()) # set ns init and nso init status self.nsInitialized = True self.nsoInitialized = True self.logdispatch.log(LogPriority.DEBUG, "Exiting updateCurrentNetworkConfigurationDictionary()...") except (KeyboardInterrupt, SystemExit): raise except Exception: success = False raise return success ############################################################################### def updateNetworkConfigurationDictionaryEntry(self, pKey): '''update a single network configuration dictionary entry @author: ekkehard j. koch :param self: essential if you override this definition :param pkey: key for the dictinary entry :param pKey: :returns: boolean - true @note: None @change: Breen Malmberg - 1/12/2017 - doc string edit; added debug logging; default var init success to True; added code to update the Wi-Fi entry; @change: Roy Nielsen - 3/6/2018 - Changed algo to look at 'Device' rather than 'name' when getting the airport power status ''' pKey = pKey.strip() self.logdispatch.log(LogPriority.DEBUG, "Entering networksetup.updateNetworkConfigurationDictionaryEntry() with pKey=" + str(pKey) + "...") success = True key = pKey try: success = True key = pKey entry = self.ns[key] if success: if entry == None: success = False self.logdispatch.log(LogPriority.DEBUG, "self.ns[" + str(key) + "] was not found! success set to False.") if success: command = [self.nsc, "-getmacaddress", key] self.ch.executeCommand(command) for line in self.ch.getOutput(): try: macaddress = re.search("(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)", line.strip()).group(1) except: macaddress = "" self.ns[key]["macaddress"] = macaddress if success: # added for disabling by device name 1/11/2017 if key == "Wi-Fi": self.logdispatch.log(LogPriority.DEBUG, "Updating Wi-Fi device entry for: " + str(self.ns[key]["name"])) command = [self.nsc, "-getairportpower", self.ns[key]["Device"]] self.ch.executeCommand(command) for line in self.ch.getOutput(): if re.search("Wi-Fi\s+Power.*On", line, re.IGNORECASE): self.ns[key]["enabled"] = True self.logdispatch.log(LogPriority.DEBUG, "airportpower for device " + str(self.ns[key]["name"]) + " is: On") else: self.ns[key]["enabled"] = False self.logdispatch.log(LogPriority.DEBUG, "airportpower for device " + str(self.ns[key]["name"]) + " is: Off") else: # original code (only for services) command = [self.nsc, "-getnetworkserviceenabled", key] self.ch.executeCommand(command) for line in self.ch.getOutput(): lineprocessed = line.strip() if lineprocessed == "Enabled": self.ns[key]["enabled"] = True else: self.ns[key]["enabled"] = False self.logdispatch.log(LogPriority.DEBUG, "Exiting networksetup.updateNetworkConfigurationDictionaryEntry() and returning success=" + str(success)) except KeyError: self.logdispatch.log(LogPriority.DEBUG, "Key error...") except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: success = False raise return success
class LaunchCtl(object): '''Service manager that provides an interface to the Mac OS launchctl command. @privatemethod: validateSubCommand - validate a command that is formatted as: { <subcommand> : [<arg1>, <arg1>, <arg3>]} where each argN is a string. @privatemethod: runSubCommand - runs a launchctl command, in the format described above, then collects standard out, standard error and the launchctl return code, returning them to the caller. # ------------------------------------------------------------------------- Legacy commands @publicmethod: load - (legacy) loads the passed in plist/service @publicmethod: unload - (legacy) unloads the passed in plist/service @publicmethod: start - (legacy) Start a service @publicmethod: stop - (legacy) Stop a service @publicmethod: list - (legacy) list a specific plist state, or returns a list of running launchd services. @publicmethod: bsexec - (legacy) execute a command in as close as possible context to the passed in PID. @publicmethod: asuser - (legacy) execute a command in as close as possible context to the passed in UID # ------------------------------------------------------------------------- Current commands @publicmethod: bootstrap @publicmethod: bootout @publicmethod: enable @publicmethod: disable @publicmethod: uncache @publicmethod: kickstart @publicmethod: kill - (2.0) Kills a service, with one of the passed in signals that are described in the Mac OS signal(3) manpage. @publicmethod: blame @publicmethod: printTarget @publicmethod: printCache @publicmethod: printDisabled @publicmethod: procinfo @publicmethod: hostinfo @publicmethod: resolveport @publicmethod: reboot @Note: Future subcommands may include 'plist', 'config', 'error'. @author: Roy Nielsen ''' def __init__(self, logger): """ Initialization Method @author: Roy Nielsen """ self.launchctl = "/bin/launchctl" self.logger = logger self.ch = CommandHelper(self.logger) # ---------------------------------------------------------------------- # helper methods # ---------------------------------------------------------------------- def isSaneFilePath(self, filepath): '''Check for a good file path in the passed in string. @author: Roy Nielsen :param filepath: ''' sane = False if isinstance(filepath, str): if re.match("^[A-Za-z/\.][A-Za-z0-9/\._-]*", filepath): sane = True else: self.logger.log( lp.DEBUG, "filepath: " + str(filepath) + " is not valid.") return sane # ---------------------------------------------------------------------- def validateSubCommand(self, command={}): '''Validate that we have a properly formatted command, and the subcommand is valid. :param command: (Default value = {}) :returns: s: success - whether the command was formatted correctly or not. @author: Roy Nielsen ''' success = False subcmd = [] if not isinstance(command, dict): self.logger.log(lp.ERROR, "Command must be a dictionary...") else: commands = 0 for subCommand, args in list(command.items()): commands += 1 ##### # Check to make sure only one command is in the dictionary if commands > 1: self.logger.log( lp.ERROR, "Damn it Jim! One command at " + "a time!!") success = False break ##### # Check if the subcommand is a valid subcommand... validSubcommands = [ "load", "unload", "start", "stop", "list", "bsexec", "asuser", "bootstrap", "bootout", "enable", "disable", "uncache", "kickstart", "kill", "blame", "print", "print-cache", "print-disabled", "procinfo", "hostinfo", "resolveport", "reboot" ] if subCommand not in validSubcommands: success = False break else: success = True ##### # Check to make sure the key or subCommand is a string, and # the value is alist and args are if not isinstance(subCommand, str) or \ not isinstance(args, list): self.logger.log( lp.ERROR, "subcommand needs to be a " + "string, and args needs to be a list " + "of strings") success = False else: ##### # Check the arguments to make sure they are all strings for arg in args: if not isinstance(arg, str): self.logger.log( lp.ERROR, "Arg '" + str(arg) + "'needs to be a string...") success = False if success: subcmd = [subCommand] + args self.logger.log(lp.DEBUG, 'subcmd: ' + str(subcmd)) return success, subcmd # ------------------------------------------------------------------------- def runSubCommand(self, commandDict={}): '''Use the passed in dictionary to create a MacOS 'security' command and execute it. :param commandDict: (Default value = {}) :returns: s: success - whether the command was successfull or not. @author: Roy Nielsen ''' success = False output = '' error = '' returncode = '' ##### # Make sure the command dictionary was properly formed, as well as # returning the formatted subcommand list validationSuccess, subCmd = self.validateSubCommand(commandDict) if validationSuccess: ##### # Command setup - note that the keychain deliberately has quotes # around it - there could be spaces in the path to the keychain, # so the quotes are required to fully resolve the file path. # Note: this is done in the build of the command, rather than # the build of the variable. cmd = [self.launchctl] + subCmd self.logger.log(lp.DEBUG, 'cmd: ' + str(cmd)) ##### # set up and run the command # self.ch.setCommand(cmd) success = self.ch.executeCommand(cmd) output = self.ch.getOutput() error = self.ch.getError() returncode = self.ch.getReturnCode() ##### # If the return code is 0, then we have success. if not returncode: success = True """ if "bootstrap" in subCmd: raise ValueError("cmd: " + str(cmd) + " output: " + str(output) + " error: " + str(error) + " retcode: " + str(returncode)) """ if error: self.logger.log(lp.INFO, "Output: " + str(output)) self.logger.log(lp.INFO, "Error: " + str(error)) self.logger.log(lp.INFO, "Return code: " + str(returncode)) success = False else: raise ValueError return success, str(output), str(error), str(returncode) # ---------------------------------------------------------------------- # Legacy Subcommands # ---------------------------------------------------------------------- def load(self, plist="", options="", sessionType="", domain=False): '''@note: From the launchctl man page: load | unload [-wF] [-S sessiontype] [-D domain] paths ... Load the specified configuration files or directories of con- figuration files. Jobs that are not on-demand will be started as soon as possible. All specified jobs will be loaded before any of them are allowed to start. Note that per-user configura- tion files (LaunchAgents) must be owned by root (if they are located in /Library/LaunchAgents) or the user loading them (if they are located in $HOME/Library/LaunchAgents). All system- wide daemons (LaunchDaemons) must be owned by root. Configura- tion files must disallow group and world writes. These restric- tions are in place for security reasons, as allowing writabil- ity to a launchd configuration file allows one to specify which executable will be launched. Note that allowing non-root write access to the /System/Library/LaunchDaemons directory WILL render your system unbootable. -w Overrides the Disabled key and sets it to false or true for the load and unload subcommands respectively. In previous versions, this option would modify the configuration file. Now the state of the Disabled key is stored elsewhere on- disk in a location that may not be directly manipulated by any process other than launchd. -F Force the loading or unloading of the plist. Ignore the Disabled key. -S sessiontype Some jobs only make sense in certain contexts. This flag instructs launchctl to look for jobs in a differ- ent location when using the -D flag, and allows launchctl to restrict which jobs are loaded into which session types. Sessions are only relevant for per-user launchd contexts. Relevant sessions are Aqua (the default), Background and LoginWindow. Background agents may be loaded independently of a GUI login. Aqua agents are loaded only when a user has logged in at the GUI. LoginWindow agents are loaded when the LoginWindow UI is displaying and currently run as root. -D domain Look for plist(5) files ending in *.plist in the domain given. This option may be thoughts of as expanding into many individual paths depending on the domain name given. Valid domains include "system," "local," "network" and "all." When providing a session type, an additional domain is available for use called "user." For example, without a session type given, "-D system" would load from or unload property list files from /System/Library/LaunchDaemons. With a session type passed, it would load from /System/Library/Laun- NOTE: Due to bugs in the previous implementation and long- standing client expectations around those bugs, the load and unload subcommands will only return a non-zero exit code due to improper usage. Otherwise, zero is always returned. @author: Roy Nielsen :param plist: (Default value = "") :param options: (Default value = "") :param sessionType: (Default value = "") :param domain: (Default value = False) ''' success = False ##### # Input validation. if self.isSaneFilePath(plist): args = [] if re.match("[-wF]+", str(options)) and \ isinstance(options, str): args.append(options) else: self.logger.log( lp.INFO, "Need a the options to be a single" + " string...") sessionTypes = ['Aqua', 'StandardIO', 'Background', 'LoginWindow'] if sessionType in sessionTypes: args += ['-S', sessionType] else: self.logger.log( lp.INFO, "Need a the sessionType in: " + str(sessionTypes)) if isinstance(domain, str): args += ['-D', domain] else: self.logger.log(lp.INFO, "Need a the domain in: " + str(sessionTypes)) args.append(plist) cmd = {"load": args} success, _, stderr, _ = self.runSubCommand(cmd) if not success and re.search("already loaded", stderr): success = True return success # ------------------------------------------------------------------------- def unLoad(self, plist="", options="", sessionType="", domain=False): '''@note: From the launchctl man page: load | unload [-wF] [-S sessiontype] [-D domain] paths ... Load the specified configuration files or directories of con- figuration files. Jobs that are not on-demand will be started as soon as possible. All specified jobs will be loaded before any of them are allowed to start. Note that per-user configura- tion files (LaunchAgents) must be owned by root (if they are located in /Library/LaunchAgents) or the user loading them (if they are located in $HOME/Library/LaunchAgents). All system- wide daemons (LaunchDaemons) must be owned by root. Configura- tion files must disallow group and world writes. These restric- tions are in place for security reasons, as allowing writabil- ity to a launchd configuration file allows one to specify which executable will be launched. Note that allowing non-root write access to the /System/Library/LaunchDaemons directory WILL render your system unbootable. -w Overrides the Disabled key and sets it to false or true for the load and unload subcommands respectively. In previous versions, this option would modify the configuration file. Now the state of the Disabled key is stored elsewhere on- disk in a location that may not be directly manipulated by any process other than launchd. -F Force the loading or unloading of the plist. Ignore the Disabled key. -S sessiontype Some jobs only make sense in certain contexts. This flag instructs launchctl to look for jobs in a differ- ent location when using the -D flag, and allows launchctl to restrict which jobs are loaded into which session types. Sessions are only relevant for per-user launchd contexts. Relevant sessions are Aqua (the default), Background and LoginWindow. Background agents may be loaded independently of a GUI login. Aqua agents are loaded only when a user has logged in at the GUI. LoginWindow agents are loaded when the LoginWindow UI is displaying and currently run as root. -D domain Look for plist(5) files ending in *.plist in the domain given. This option may be thoughts of as expanding into many individual paths depending on the domain name given. Valid domains include "system," "local," "network" and "all." When providing a session type, an additional domain is available for use called "user." For example, without a session type given, "-D system" would load from or unload property list files from /System/Library/LaunchDaemons. With a session type passed, it would load from /System/Library/Laun- NOTE: Due to bugs in the previous implementation and long- standing client expectations around those bugs, the load and unload subcommands will only return a non-zero exit code due to improper usage. Otherwise, zero is always returned. @author: Roy Nielsen :param plist: (Default value = "") :param options: (Default value = "") :param sessionType: (Default value = "") :param domain: (Default value = False) ''' success = False ##### # Input validation. if self.isSaneFilePath(plist): args = [] if re.match("[-wF]+", str(options)) and \ isinstance(options, str): args.append(options) else: self.logger.log( lp.INFO, "Need a the options to be a single" + " string...") sessionTypes = ['Aqua', 'StandardIO', 'Background', 'LoginWindow'] if sessionType in sessionTypes: args += ['-S', sessionType] else: self.logger.log( lp.INFO, "Need a the sessionType in: " + str(sessionTypes)) if isinstance(domain, str): args += ['-D', domain] else: self.logger.log(lp.INFO, "Need a the domain in: " + str(sessionTypes)) args.append(plist) cmd = {"unload": args} success, _, stderr, _ = self.runSubCommand(cmd) if not success and re.search('Could not find specified', stderr): success = True return success # ------------------------------------------------------------------------- def start(self, label=""): '''@note: From the launchctl man page: start label Start the specified job by label. The expected use of this sub- command is for debugging and testing so that one can manually kick-start an on-demand server. @author: Roy Nielsen :param label: (Default value = "") ''' success = False ##### # Input validation. if not label or not isinstance(label, str): return success cmd = {"start": label} success, _, _, _ = self.runSubCommand(cmd) return success # ------------------------------------------------------------------------- def stop(self, label=""): '''@note: From the launchctl man page: stop label Stop the specified job by label. If a job is on-demand, launchd may immediately restart the job if launchd finds any criteria that is satisfied. @author: Roy Nielsen :param label: (Default value = "") ''' success = False ##### # Input validation. if not label or not isinstance(label, str): return success cmd = {"stop": label} success, _, _, _ = self.runSubCommand(cmd) return success # ------------------------------------------------------------------------- def list(self, label=""): '''@note: From the launchctl man page: list [-x] [label] With no arguments, list all of the jobs loaded into launchd in three columns. The first column displays the PID of the job if it is running. The second column displays the last exit status of the job. If the number in this column is negative, it repre- sents the negative of the signal which stopped the job. Thus, "-15" would indicate that the job was terminated with SIGTERM. The third column is the job's label. If [label] is specified, prints information about the requested job. -x This flag is no longer supported. @author: Roy Nielsen :param label: (Default value = "") ''' success = False ##### # Input validation. if label and isinstance(label, str): cmd = [self.launchctl, 'list', label] elif not label: cmd = [self.launchctl, 'list'] else: return success ##### # set up and run the command # self.ch.setCommand(cmd) success = self.ch.executeCommand(cmd) output = self.ch.getOutput() error = self.ch.getError() returncode = self.ch.getReturnCode() if not error: self.logger.log(lp.DEBUG, "Output: " + str(output)) self.logger.log(lp.DEBUG, "Error: " + str(error)) self.logger.log(lp.DEBUG, "Return code: " + str(returncode)) success = True else: self.logger.log(lp.DEBUG, "Output: " + str(output)) self.logger.log(lp.DEBUG, "Error: " + str(error)) self.logger.log(lp.DEBUG, "Return code: " + str(returncode)) success = False return success, output, error, returncode # ------------------------------------------------------------------------- def bsExec(self, pid, command, args=[]): '''@note: From the launchctl man page: bsexec PID command [args] This executes the given command in as similar an execution con- text as possible to the target PID. Adopted attributes include the Mach bootstrap namespace, exception server and security audit session. It does not modify the process' credentials (UID, GID, etc.) or adopt any environment variables from the target process. It affects only the Mach bootstrap context and directly-related attributes. @author: Roy Nielsen :param pid: :param command: :param args: (Default value = []) ''' success = False ##### # Input validation. if not isinstance(pid, int) or \ not isinstance(command, str) or \ not isinstance(args, list): return success cmd = {"bsexec": [pid, command] + args} success, _, _, _ = self.runSubCommand(cmd) return success # ------------------------------------------------------------------------- def asUser(self, uid, command, args=[]): '''@note: From the launchctl man page: asuser UID command [args] This executes the given command in as similar an execution con- text as possible to that of the target user's bootstrap. Adopted attributes include the Mach bootstrap namespace, excep- tion server and security audit session. It does not modify the process' credentials (UID, GID, etc.) or adopt any user-spe- cific environment variables. It affects only the Mach bootstrap context and directly- related attributes. @author: Roy Nielsen :param uid: :param command: :param args: (Default value = []) ''' success = False ##### # Input validation. if not isinstance(uid, int) or \ not isinstance(command, str) or \ not isinstance(args, list): return success cmd = {"asuser": [uid, command] + args} success, stdout, stderr, retcode = self.runSubCommand(cmd) if retcode != '0': raise ValueError(reportStack() + "- success: " + str(success) + " stdout: " + str(stdout) + " stderr: " + str(stderr) + " retcode: " + str(retcode)) return success # ---------------------------------------------------------------------- # Supported Second generation subcommands # ---------------------------------------------------------------------- def bootStrap(self, domainTarget="", servicePath=''): '''@note: From the launchctl man page: bootstrap | bootout domain-target [service-path service-path2 ...] | service-target Bootstraps or removes domains and services. Services may be specified as a series of paths or a service identifier. Paths may point to XPC service bundles, launchd.plist(5) s, or a directories containing a collection of either. If there were one or more errors while bootstrapping or removing a collection of services, the problematic paths will be printed with the errors that occurred. If no paths or service target are specified, these commands can either bootstrap or remove a domain specified as a domain tar- get. Some domains will implicitly bootstrap pre-defined paths as part of their creation. @author: Roy Nielsen :param domainTarget: (Default value = "") :param servicePath: (Default value = '') ''' success = False cmd = '' ##### # Input validation. if not isinstance(domainTarget, str) or \ not isinstance(servicePath, str): return success if servicePath and domainTarget: cmd = {"bootstrap": [domainTarget, servicePath]} elif domainTarget: cmd = {"bootstrap": [domainTarget]} else: return success success, stdout, stderr, retcode = self.runSubCommand(cmd) if retcode != '0': self.logger.log( lp.DEBUG, reportStack() + "- success: " + str(success) + " stdout: " + str(stdout) + " stderr: " + str(stderr) + " retcode: " + str(retcode)) return success # ---------------------------------------------------------------------- def bootOut(self, domainTarget="", servicePath=''): '''@note: From the launchctl man page: bootstrap | bootout domain-target [service-path service-path2 ...] | service-target Bootstraps or removes domains and services. Services may be specified as a series of paths or a service identifier. Paths may point to XPC service bundles, launchd.plist(5) s, or a directories containing a collection of either. If there were one or more errors while bootstrapping or removing a collection of services, the problematic paths will be printed with the errors that occurred. If no paths or service target are specified, these commands can either bootstrap or remove a domain specified as a domain tar- get. Some domains will implicitly bootstrap pre-defined paths as part of their creation. @author: Roy Nielsen :param domainTarget: (Default value = "") :param servicePath: (Default value = '') ''' success = False ##### # Input validation. if not isinstance(domainTarget, str) or \ not isinstance(servicePath, str): return success if servicePath and domainTarget: cmd = {"bootout": [domainTarget, servicePath]} elif domainTarget: cmd = {"bootout": [domainTarget]} else: return success success, stdout, stderr, retcode = self.runSubCommand(cmd) ##### # errors that indicate the process is complete or in # progress if re.search("No such process", stderr) or \ re.search("Operation now in progress", stderr): success = True if retcode != '0' and not success: self.logger.log( lp.DEBUG, reportStack() + "- success: " + str(success) + " stdout: " + str(stdout) + " stderr: " + str(stderr) + " retcode: " + str(retcode)) for item in stderr: if item and re.search("Could not find specified service", item): success = True break return success # ---------------------------------------------------------------------- def enable(self, serviceTarget, servicePath=''): '''From the launchctl man page: enable | disable service-target Enables or disables the service in the requested domain. Once a service is disabled, it cannot be loaded in the specified domain until it is once again enabled. This state persists across boots of the device. This subcommand may only target services within the system domain or user and user-login domains. :param serviceTarget: :param servicePath: (Default value = '') ''' success = False ##### # Input validation. if not isinstance(serviceTarget, str): return success if servicePath and isinstance(servicePath, str): cmd = {"enable": [serviceTarget, servicePath]} else: cmd = {"enable": [serviceTarget]} success, stdout, stderr, retcode = self.runSubCommand(cmd) if str(retcode) != '0': success = False self.logger.log( lp.DEBUG, reportStack() + "- success: " + str(success) + " stdout: " + str(stdout) + " stderr: " + str(stderr) + " retcode: " + str(retcode)) else: self.logger.log( lp.DEBUG, reportStack() + "- success: " + str(success) + " stdout: " + str(stdout) + " stderr: " + str(stderr) + " retcode: " + str(retcode)) success = True return success # ------------------------------------------------------------------------- def disable(self, serviceTarget): '''From the launchctl man page: enable | disable service-target Enables or disables the service in the requested domain. Once a service is disabled, it cannot be loaded in the specified domain until it is once again enabled. This state persists across boots of the device. This subcommand may only target services within the system domain or user and user-login domains. :param serviceTarget: ''' success = False ##### # Input validation. if not isinstance(serviceTarget, str): return success cmd = {"disable": [serviceTarget]} success, stdout, stderr, retcode = self.runSubCommand(cmd) if str(retcode) != '0': success = False self.logger.log( lp.DEBUG, reportStack() + "- success: " + str(success) + " stdout: " + str(stdout) + " stderr: " + str(stderr) + " retcode: " + str(retcode)) else: self.logger.log( lp.DEBUG, reportStack() + "- success: " + str(success) + " stdout: " + str(stdout) + " stderr: " + str(stderr) + " retcode: " + str(retcode)) success = True return success #------------------------------------------------------------------------- def unCache(self, serviceName): '''Bypass the cache and read the service configuration from disk :param serviceName: ''' success = False ##### # Input validation. if not isinstance(serviceName, str): return success cmd = {"uncache": [serviceName]} success, stdout, stderr, retcode = self.runSubCommand(cmd) if str(retcode) != '0': success = False self.logger.log( lp.DEBUG, reportStack() + "- success: " + str(success) + " stdout: " + str(stdout) + " stderr: " + str(stderr) + " retcode: " + str(retcode)) else: self.logger.log( lp.DEBUG, reportStack() + "- success: " + str(success) + " stdout: " + str(stdout) + " stderr: " + str(stderr) + " retcode: " + str(retcode)) success = True return success # ------------------------------------------------------------------------- def kickStart(self, serviceTarget="", options='-k'): '''From the launchctl man page: kickstart [-kp] service-target Instructs launchd to kickstart the specified service. Options can be one of: -k If the service is already running, kill the running instance before restarting the service. -p Upon success, print the PID of the new process or the already-running process to stdout. High sierra options: -s Force the service to start. -x Attach to xpcproxy(3) before it execs and becomes the service process. This flag is generally not useful for anyone but the launchd maintainer. (-p) No longer available in High Sierra :param serviceTarget: (Default value = "") :param options: (Default value = '-k') ''' ##### # Input validation. args = [] if re.match("[-kp]+", str(options)) and \ isinstance(options, str): args.append(options) else: self.logger.log(lp.INFO, "Need a the options to be a single " + "string...") args.append(serviceTarget) self.logger.log(lp.DEBUG, "args: " + str(args)) cmd = {"kickstart": args} self.logger.log(lp.DEBUG, "cmd: " + str(cmd)) success, stdout, stderr, retcode = self.runSubCommand(cmd) ##### # If a '0' is returned if retcode == '0' and success: success = True else: raise ValueError("kickstart - success: " + str(success) + " stdout: " + str(stdout) + " stderr: " + str(stderr) + " retcode: " + str(retcode)) return success # ------------------------------------------------------------------------- def kill(self, signal="", serviceTarget=""): '''From the launchctl man page: kill signal-name | signal-number service-target Sends the specified signal to the specified service if it is running. The signal number or name (SIGTERM, SIGKILL, etc.) may be specified. :param signal: (Default value = "") :param serviceTarget: (Default value = "") ''' success = False args = [] ##### # Validate signal - from the signal(3) manpage on OS X. signals = [ 'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT', 'SIGEMT', 'SIGFPE', 'SIGKILL', 'SIGBUS', 'SIGSEGV', 'SIGSYS', 'SIGPIPE', 'SIGALRM', 'SIGTERM', 'SIGURG', 'SIGSTOP', 'SIGTSTP', 'SIGCONT', 'SIGCHLD', 'SIGTTIN', 'SIGTTOU', 'SIGIO', 'SIGXCPU', 'SIGXFSZ', 'SIGVTALRM', 'SIGPROF', 'SIGWINCH', 'SIGINFO', 'SIGUSR1', 'SIGUSR2' ] if isinstance(signal, str) and signal in signals: args.append(signal) elif isinstance(signal, int) and signal < 32: args.append(signal) else: return success ##### # Service target, just check for string... if isinstance(serviceTarget, str): args.append(serviceTarget) else: return success args.append(serviceTarget) cmd = {"kill": args} success, _, _, _ = self.runSubCommand(cmd) return success # ------------------------------------------------------------------------- def blame(self, serviceTarget): '''From the launchctl man page: blame service-target If the service is running, prints a human-readable string describing why launchd launched the service. Note that services may run for many reasons; this subcommand will only show the most proximate reason. So if a service was run due to a timer firing, this subcommand will print that reason, irrespective of whether there were messages waiting on the service's various endpoints. This subcommand is only intended for debugging and profiling use and its output should not be relied upon in pro- duction scenarios. :param serviceTarget: ''' success = False ##### # Input validation. if not isinstance(serviceTarget, str): return success cmd = {"blame": [serviceTarget]} success, stdout, _, _ = self.runSubCommand(cmd) return success, stdout # ------------------------------------------------------------------------- def printTarget(self, target): '''@note: From the launchctl man page: print domain-target | service-target Prints information about the specified service or domain. Domain output includes various properties about the domain as well as a list of services and endpoints in the domain with state pertaining to each. Service output includes various prop- erties of the service, including information about its origin on-disk, its current state, execution context, and last exit status. IMPORTANT: This output is NOT API in any sense at all. Do NOT rely on the structure or information emitted for ANY reason. It may change from release to release without warning. @author: Roy Nielsen :param target: ''' success = False ##### # Input validation. if not isinstance(target, str): return success # prepended system/ to service-target in order to hot fix multiple # issues with service detection in servicehelper two implementation # all rules calling new servicehelper must specify the service target # context and they all currently do not. system/ is where all # system services run. currently servicehelper two cannot look for # user context services when being run in admin mode anyway, so this # is just a best-effort workaround until servicehelper two can # be redesigned or all the rules changed to prepend system/ in # their servicehelper calls cmd = {"print": ["system/" + target]} success, stdout, stderr, _ = self.runSubCommand(cmd) if re.search("Could not find service", stderr) and \ re.search("in domain for system", stderr): success = False return success, stdout # ------------------------------------------------------------------------- def printCache(self): '''@note: From the launchctl man page: print-cache Prints the contents of the launchd service cache. @author: Roy Nielsen ''' cmd = {"print-cache": []} success, stdout, _, _ = self.runSubCommand(cmd) if success: self.logger.log(lp.DEBUG, str(success)) self.logger.log(lp.DEBUG, str(stdout)) return success, stdout # ------------------------------------------------------------------------- def printDisabled(self, target=''): '''@note: From the launchctl man page: print-disabled Prints the list of disabled services. @author: Roy Nielsen :param target: (Default value = '') ''' success = False stdout = '' if target and isinstance(target, str): cmd = {"print-disabled": [target]} success, stdout, _, _ = self.runSubCommand(cmd) return success, stdout # ------------------------------------------------------------------------- def procInfo(self, pid): '''@note: From the launchctl man page: procinfo pid Prints information about the execution context of the specified PID. This information includes Mach task-special ports and :param pid: :raises what: names the ports are advertised as in the Mach bootstrap :raises namespace: if they are known to launchd :raises text.: This subcommand is intended for diagnostic purposes only :raises and: its output should not be relied upon in production scenar :raises ios.: This command requires root privileges :raises author: Roy Nielsen ''' success = False ##### # Input validation. if not isinstance(pid, int): return success cmd = {"procinfo": [pid]} success, stdout, _, _ = self.runSubCommand(cmd) return success, stdout # ------------------------------------------------------------------------- def hostinfo(self): '''@note: From the launchctl man page: hostinfo Prints information about the system's host-special ports, including the host-exception port. This subcommand requires root privileges. @author: Roy Nielsen ''' cmd = {"hostinfo": []} _, stdout, _, _ = self.runSubCommand(cmd) return stdout # ------------------------------------------------------------------------- def resolvePort(self, ownerPid, portName): '''@note: From the launchctl man page: resolveport owner-pid port-name Given a PID and the name of a Mach port right in that process' port namespace, resolves that port to an endpoint name known to launchd. This subcommand requires root privileges. @author: Roy Nielsen :param ownerPid: :param portName: ''' success = False ##### # Input validation. if not isinstance(ownerPid, int) or not isinstance(portName, str): return success cmd = {"rsolveport": [ownerPid, portName]} _, stdout, _, _ = self.runSubCommand(cmd) return stdout # ------------------------------------------------------------------------- def reboot(self, context, mountPoint): '''@note: From the launchctl man page: reboot [system|userspace|halt|logout|apps|reroot <mount-point>] Instructs launchd to begin tearing down userspace. With no argument given or with the system argument given, launchd will make the reboot(2) system call when userspace has been com- pletely torn down. With the halt argument given, launchd will make the reboot(2) system call when userspace has been com- pletely torn down and pass the RB_HALT flag, halting the system and not initiating a reboot. With the userspace argument given, launchd will re-exec itself when userspace has been torn down and bring userspace back up. This is useful for rebooting the system quickly under condi- tions where kernel data structures or hardware do not need to be re-initialized. With the reroot argument given, launchd will perform a userspace shutdown as with the userspace argument, but it will exec a copy of launchd from the specified mount-point. This mechanism is a light-weight way of changing boot partitions. As part of this process, launchd will make mount-point the new root partition and bring userspace up as if the kernel had des- ignated mount-point as the root partition. IMPORTANT: This type of reboot will, in no way, affect the already-running kernel on the host. Therefore, when using this option to switch to another volume, you should only target vol- umes whose userspace stacks are compatible with the already- running kernel. NOTE: As of the date of this writing, this option does not com- pletely work. With the logout argument given, launchd will tear down the caller's GUI login session in a manner similar to a logout ini- tiated from the Apple menu. The key difference is that a logout initiated through this subcommand will be much faster since it will not give apps a chance to display modal dialogs to block logout indefinitely; therefore there is data corruption risk to using this option. Only use it when you know you have no unsaved data in your running apps. With the apps argument given, launchd will terminate all apps running in the caller's GUI login session that did not come from a launchd.plist(5) on-disk. Apps like Finder, Dock and SystemUIServer will be unaffected. Apps are terminated in the same manner as the logout argument, and all the same caveats apply. -s When rebooting the machine (either a full reboot or userspace reboot), brings the subsequent boot session up in single-user mode. @author: Roy Nielsen :param context: :param mountPoint: ''' success = False validContexts = ['System', 'users', 'halt', 'logout', 'apps', 'reroot'] if not isinstance(context, str) or \ not context in validContexts: return success if mountPoint and isinstance(mountPoint, str): cmd = {"reboot": [context, mountPoint]} elif not mountPoint: cmd = {"reboot": [context]} else: return success success, _, _, _ = self.runSubCommand(cmd) return success