class SHchkconfig(ServiceHelperTemplate): """SHchkconfig is the Service Helper for systems using the chkconfig command to configure services. (RHEL up to 6, SUSE, Centos up to 6, etc) @author: David Kennel """ def __init__(self, environment, logdispatcher): """ Constructor """ super(SHchkconfig, self).__init__(environment, logdispatcher) self.environ = environment self.logger = logdispatcher self.initobjs() self.localize() def initobjs(self): """initialize class objects""" self.ch = CommandHelper(self.logger) def localize(self): """set base command paths (chkconfig and service) based on OS""" self.svc = "" self.chk = "" chk_paths = ["/sbin/chkconfig", "/usr/sbin/chkconfig"] for cp in chk_paths: if os.path.exists(cp): self.chk = cp break service_paths = ["/sbin/service", "/usr/sbin/service"] for sp in service_paths: if os.path.exists(sp): self.svc = sp break if not self.svc: raise IOError( "Could not locate the service utility on this system") if not self.chk: raise IOError( "Could not locate the chkconfig utility on this system") def startService(self, service, **kwargs): """start a given service :param service: string; name of service :param kwargs: return: success :param **kwargs: :returns: success :rtype: bool @author: Breen Malmberg """ success = True self.ch.executeCommand(self.svc + " " + service + " start") retcode = self.ch.getReturnCode() if retcode != 0: success = False if not self.isRunning(service): success = False return success def stopService(self, service, **kwargs): """stop a given service :param service: param kwargs: :param **kwargs: :returns: success :rtype: bool @author: Breen Malmberg """ success = True self.ch.executeCommand(self.svc + " " + service + " stop") retcode = self.ch.getReturnCode() if retcode != 0: success = False if self.isRunning(service): success = False return success def disableService(self, service, **kwargs): """Disables the specified service and stops it if it is running :param service: string; Name of the service to be disabled :param **kwargs: :returns: bool @author: David Kennel @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ disabled = True self.ch.executeCommand(self.chk + " " + service + " off") retcode = self.ch.getReturnCode() if retcode != 0: disabled = False if self.auditService(service): disabled = False if not self.stopService(service): disabled = False return disabled def enableService(self, service, **kwargs): """Enables a service and starts it if it is not running as long as we are not in install mode :param service: string; Name of the service to be enabled :param **kwargs: :returns: enabled :rtype: bool @author: David Kennel @change: Breen Malmberg - 04/10/2019 - """ enabled = True self.ch.executeCommand(self.chk + " " + service + " on") retcode = self.ch.getReturnCode() if retcode != 0: enabled = False if not self.auditService(service): enabled = False if not self.startService(service): enabled = False return enabled def auditService(self, service, **kwargs): """Checks the status of a service and returns a bool indicating whether or not the service is enabled :param service: string; Name of the service to audit :param **kwargs: :returns: enabled :rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ enabled = True if not self.audit_chkconfig_service(service): enabled = False return enabled def audit_chkconfig_service(self, service): """uses the chkconfig command to check if a given service is enabled or not :param service: :returns: enabled :rtype: bool @author: Breen Malmberg """ enabled = True self.ch.executeCommand(self.chk + " --list " + service) retcode = self.ch.getReturnCode() if retcode != 0: enabled = False self.logger.log(LogPriority.DEBUG, "Failed to get status of service: " + service) return enabled output = self.ch.getOutputString() if not re.search(":on", output): enabled = False return enabled def isRunning(self, service, **kwargs): """Check to see if a service is currently running. :param service: string; Name of the service to check :param **kwargs: :returns: running :rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ running = True # see: http://refspecs.linuxbase.org/LSB_3.1.0/LSB-generic/LSB-generic/iniscrptact.html success_codes = [0] self.ch.executeCommand(self.svc + " " + service + " status") retcode = self.ch.getReturnCode() if retcode not in success_codes: running = False self.logger.log( LogPriority.DEBUG, "Command error while getting run status of service: " + service) return running outputlines = self.ch.getOutput() # need to parse for either sysv or systemd output if not self.parse_running(outputlines): running = False return running def parse_running(self, outputlines): """check whether given service is running, with the service command this is the older (classic) systemV case :param outputlines: list; list of strings to search :returns: running :rtype: bool @author: Breen Malmberg """ running = True systemctl_locations = ["/usr/bin/systemctl", "/bin/systemctl"] if any(os.path.exists(sl) for sl in systemctl_locations): searchterms = ["Active:\s+inactive", "Active:\s+unknown"] else: searchterms = [ "is stopped", "hook is not installed", "is not running" ] for line in outputlines: if any(re.search(st, line) for st in searchterms): running = False break return running def reloadService(self, service, **kwargs): """Reload (HUP) a service so that it re-reads it's config files. Called by rules that are configuring a service to make the new configuration active. :param service: string; Name of service to be reloaded :param **kwargs: :returns: reloaded :rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ reloaded = True # force-reload: cause the configuration to be reloaded if the service supports this, # otherwise restart the service if it is running self.ch.executeCommand(self.svc + " " + service + " force-reload") retcode = self.ch.getReturnCode() if retcode != 0: reloaded = False self.logger.log(LogPriority.DEBUG, "Failed to reload service: " + service) return reloaded def listServices(self, **kwargs): """Return a list containing strings that are service names. :param **kwargs: :returns: service_list :rtype: list @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ service_list = [] self.ch.executeCommand(self.chk + " --list") outputlines = self.ch.getOutput() for line in outputlines: try: service_list.append(line.split()[0]) except IndexError: pass return service_list def getStartCommand(self, service): """retrieve the start command. Mostly used by event recording :param service: :returns: string - start command @author: dwalker """ return self.svc + " " + service + " start" def getStopCommand(self, service): """retrieve the stop command. Mostly used by event recording :param service: :returns: string - stop command @author: dwalker """ return self.svc + " " + service + " stop" def getEnableCommand(self, service): """retrieve the enable command. Mostly used by event recording :param service: :returns: string - enable command @author: dwalker """ return self.chk + " " + service + " on" def getDisableCommand(self, service): """retrieve the start command. Mostly used by event recording :param service: :returns: string - disable command @author: dwalker """ return self.chk + " " + service + " off"
class MacPkgr(object): def __init__(self, environ, logger): ''' Mac package manager based on other stonix package managers. Uses the stonix IHmac and InstallingHelper Libraries. They can install .zip, .tar, .tar.gz, .pkg and .mpkg files via an http or https URL :param environ: environment object :param logger: logdispatcher object # Methods specific to Mac. @note: Uses the stonix IHmac and InstallingHelper Libraries. They can install .zip, .tar, .tar.gz, .pkg and .mpkg files via an http or https URL @note: WARNING: To use checkInstall or removepackage, this package manager converts all of the plists in the /var/db/receipts directory to text, then converts they all back to binary when it is done performing a reverse lookup to find if a specific package is installed. I would love to use Greg Neagle's FoundationPlist.py, but licenses are not compatible. @change: Breen Malmberg - 2/28/2017 - Made the missing macreporoot message more clear and helpful; added logic to also check if it is 'None' ''' self.environ = environ self.osfamily = self.environ.getosfamily() if not re.match("^darwin$", self.osfamily.strip()): return ##### # setting up to call ctypes to do a filesystem sync if self.environ.getosfamily() == "darwin": self.libc = C.CDLL("/usr/lib/libc.dylib") else: self.libc = None self.logger = logger self.detailedresults = "" self.pkgUrl = "" self.reporoot = "" if not MACREPOROOT: raise NoRepoException("Please ensure that the constant, MACREPOROOT, is properly defined in localize.py and is not set to 'None'") elif MACREPOROOT == None: raise NoRepoException("Please ensure that the constant, MACREPOROOT, is properly defined in localize.py and is not set to 'None'") else: self.reporoot = MACREPOROOT self.dotmd5 = True self.logger.log(LogPriority.DEBUG, "Done initializing MacPkgr class...") self.ch = CommandHelper(self.logger) self.connection = Connectivity(self.logger) def installPackage(self, package): ''' Install a package. Return a bool indicating success or failure. :param package: Path to the package past the REPOROOT IE. package would be: QuickAdd.Stonix.pkg rather than: https://jss.lanl.gov/CasperShare/QuickAdd.Stonix.pkg \_____________________________/ \________________/ | | REPOROOT package Where REPOROOT is initialized in the class __init__, and package is passed in to this method This assumes that all packages installed via an instance of this class will be retrieved from the same REPOROOT. If you need to use another REPOROOT, please use another instance of this class. :returns: success :rtype: bool @author: dwalker, Roy Nielsen @change: Breen Malmberg - 2/28/2017 - added logic to handle case where self.reporoot is undefined; added logging; minor doc string edit ''' success = False if not self.reporoot: self.logger.log(LogPriority.WARNING, "No MacRepoRoot defined! Unable to determine package URL!") return success try: self.package = package ##### # Create a class variable that houses the whole URL if self.reporoot.endswith("/"): self.pkgUrl = self.reporoot + self.package else: self.pkgUrl = self.reporoot + "/" + self.package message = "self.pkgUrl: " + str(self.pkgUrl) self.logger.log(LogPriority.DEBUG, message) if re.search("://", self.pkgUrl): if self.connection.isPageAvailable(self.pkgUrl): ##### # Download into a temporary directory success = self.downloadPackage() if success: ##### # Apple operating systems have a lazy attitude towards # writing to disk - the package doesn't get fully # written to disk until the following method is called. # Otherwise when the downloaded package is further # manipulated, (uncompressed or installed) the # downloaded file is not there. There may be other # ways to get python to do the filesystem sync... try: self.libc.sync() except: pass ##### # Make sure the md5 of the file matches that of the # server if self.checkMd5(): ##### # unarchive if necessary compressed = [".tar", ".tar.gz", ".tgz", ".tar.bz", ".tbz", ".zip"] for extension in compressed: if self.tmpLocalPkg.endswith(extension): self.unArchive() try: self.libc.sync() except: pass ##### # The unArchive renames self.tmpLocalPkg to the # actual software to install that is inside the # package. # # install - if extension is .app, copy to the # /Applications folder, otherwise if it # is a .pkg or .mpkg use the installer # command if self.tmpLocalPkg.endswith(".app"): success = self.copyInstall() elif self.tmpLocalPkg.endswith(".pkg") or \ self.tmpLocalPkg.endswith(".mpkg"): success = self.installPkg() else: ##### # Otherwise the repository is on a mounted filesystem self.logger.log(LogPriority.DEBUG, "Looking for a local " + "filesystem repo...") self.tmpLocalPkg = self.reporoot + self.package except (KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) raise err if success: try: self.libc.sync() except: pass return success ########################################################################### def getPkgUrl(self): '''Setter for the class varialbe pkgUrl @author: Roy Nielsen ''' return self.pkgUrl ########################################################################### def setPkgUrl(self, pkgUrl=""): '''Setter for the class varialbe pkgUrl @author: Roy Nielsen :param pkgUrl: (Default value = "") ''' self.pkgUrl = pkgUrl ########################################################################### def removePackage(self, package="", install_root="/"): '''Remove a package domain. Return a bool indicating success or failure. Not yet implemented... Will use pkgutil to determine domain, then delete files in receipt.. :param string: package : Name of the package to be removed, must be recognizable to the underlying package manager. :param package: (Default value = "") :param install_root: (Default value = "/") :returns: bool : @author: rsn ''' success = False self.package = package try: self.logger.log(LogPriority.DEBUG, "Package: " + str(package)) domain = None domain = self.findDomain(package) self.logger.log(LogPriority.DEBUG, "removePackage - Domain: " + domain) if domain: cmd_one = ["/usr/sbin/pkgutil", "--only-files", "--files", domain] cmd_two = ["/usr/sbin/pkgutil", "--only-dirs", "--files", domain] ##### # Use the pkgutil command to get a list of files in the package # receipt count = 0 self.ch.executeCommand(cmd_one) files2remove = self.ch.getOutputString().split("\n") print(("Files to remove: " + str(files2remove))) self.logger.log(LogPriority.DEBUG, files2remove) if str(self.ch.getReturnCode()) == str(0): for file in files2remove: if file: try: ##### # Make sure "/" is prepended to the file as # pkgutil does not report the first "/" in the # file path os.remove(install_root + file) count = count + 1 except OSError as err: self.logger.log(LogPriority.DEBUG, "Error trying to remove: " + str(file)) self.logger.log(LogPriority.DEBUG, "With Exception: " + str(err)) else: ##### # Potentially empty filename in the list, need to # bump the count to match. count = count + 1 ##### # Directory list will include directories such as /usr # and /usr/local... Sucess is obtained only if all of # the files (not directories) are deleted. if count == len(files2remove): success = True else: self.logger.log(LogPriority.WARNING, "Count: " + str(count)) self.logger.log(LogPriority.WARNING, "Files removed: " + str(len(files2remove))) ##### # Use the pkgutil command to get a list of directories # in the package receipt self.ch.executeCommand(cmd_two) dirs2remove = self.ch.getOutputString().split("\n") ##### # Reverse list as list is generated with parents first # rather than children first. dirs2remove.reverse() self.logger.log(LogPriority.DEBUG, dirs2remove) if str(self.ch.getReturnCode()) == str(0): for dir in dirs2remove: if dir: try: ##### # Make sure "/" is prepended to the directory # tree as pkgutil does not report the first "/" # in the file path os.rmdir(install_root + dir) ##### # We don't care if any of the child directories # still have files, as directories such as # /usr/bin, /usr/local/bin are reported by # pkgutil in the directory listing, which is # why we use os.rmdir rather than shutil.rmtree # and we don't report on the success or failure # of removing directories. except OSError as err: self.logger.log(LogPriority.DEBUG, "Error trying to remove: " + str(dir)) self.logger.log(LogPriority.DEBUG, "With Exception: " + str(err)) pass ##### # Make the system package database "forget" the package # was installed. cmd_three = ["/usr/sbin/pkgutil", "--forget", domain] self.ch.executeCommand(cmd_three) if re.match("^%s$" % str(self.ch.getReturnCode()).strip(), str(0)): success = True else: self.logger.log(LogPriority.DEBUG, "Page: \"" + str(package) + "\" Not found") except(KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) if success: try: self.libc.sync() except: pass print(("Remove Package success: " + str(success))) return success ########################################################################### def checkInstall(self, package): '''Check the installation status of a package. Return a bool; True if the package is installed. Use pkgutil to determine if package has been installed or not. :param string: package : Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. :param package: :returns: bool : @author: rsn ''' success = False self.package = package try: ##### # Perform a reverse lookup to get the domain... domain = self.findDomain(package) self.logger.log(LogPriority.DEBUG, "Domain: " + str(domain)) if domain: success = True self.logger.log(LogPriority.DEBUG, "Domain: " + str(domain) + " found") except(KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) return success ########################################################################### def checkAvailable(self, package): '''Check if a package is available at the "reporoot" :param package: :returns: success :rtype: bool @author: Roy Nielsen @change: Breen Malmberg - 2/28/2017 - added logic to handle case where self.reporoot is undefined; added logging; minor doc string edit ''' success = False self.package = package if not self.reporoot: self.logger.log(LogPriority.WARNING, "No MacRepoRoot defined! Unable to determine package URL!") return success try: self.logger.log(LogPriority.DEBUG, "Checking if: " + str(package)) + " is available on the server..." self.logger.log(LogPriority.DEBUG, "From repo: " + str(self.reporoot)) self.pkgUrl = self.reporoot + "/" + package # If there network, install, else no network, log if self.connection.isPageAvailable(self.pkgUrl): self.logger.log(LogPriority.DEBUG, "There is a connection to" + " the server...") ##### # Download the file self.downloadPackage() ##### # Perform a md5 checksum - if there is a match, return # success = True if self.checkMd5(): success = True except(KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) return success ########################################################################### def getInstall(self, package): return self.installPackage(package) ########################################################################### def getRemove(self, package): return self.removePackage(package) ########################################################################### def findDomain(self, pkg=""): '''Go through the package receipts database to find a package, and return a domain. Apple stores package information in "domain" format, rather than a package name format. Accessing the package name means we need to look through all the ".plist" files in /var/db/receipts to find the package name, then we can return the domain so that can be used for package management. Install package receipts can be found in /var/db/receipts. A domain is the filename in the receipts database without the ".plist" or ".bom". An example is org.macports.MacPorts :param eters: pkg - the name of the install package that we need the domain for. :param pkg: (Default value = "") :returns: s: domains - the first domain in a possible list of domains. @author: Roy Nielsen ''' try: self.logger.log(LogPriority.DEBUG, "Looking for: " + str(pkg)) path = "/var/db/receipts/" files = [] domain = "" for name in os.listdir(path): if os.path.isfile(os.path.join(path, name)) and \ os.path.isfile(os.path.join(path, name)) and \ name.endswith(".plist"): files.append(name) unwrap = "/usr/bin/plutil -convert xml1 /var/db/receipts/*.plist" wrap = "/usr/bin/plutil -convert binary1 /var/db/receipts/*.plist" self.ch.executeCommand(unwrap) if not re.match("^%s$" % str(self.ch.getReturnCode()), str(0)): ##### # Unwrap command didn't work... return None domain = None else: try: self.libc.sync() except: pass ##### # Unwrap command worked, process the receipt plists for afile in files: if re.match("^\..+.plist", afile): continue self.logger.log(LogPriority.DEBUG, "afile: " + str(afile)) ##### # Get the path without the plist file extension. afile_path = os.path.join(path, afile) ##### # Make sure we have a valid file on the filesystem if os.path.isfile(afile_path): try: plist = plistlib.readPlist(afile_path) except Exception as err: self.logger.log(LogPriority.DEBUG, "Exception " + "trying to use" + " plistlib: " + str(err)) raise err else: if re.match("^%s$" % plist['PackageFileName'], pkg): ##### # Find the first instance of the # PackageFileName without the .plist. domain = ".".join(afile.split(".")[:-1]) break ##### # Make the plists binary again... self.ch.executeCommand(wrap) ##### # Log the domain... self.logger.log(LogPriority.DEBUG, "Domain: " + str(domain)) print(("findDomain: " + str(domain))) except(KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) return domain ########################################################################### def downloadPackage(self): '''Download the package in the self.package URL to a temporary directory created by tempfile.mkdtemp. Does not checked for a cached file. @Note: path to downloaded file will be in the self.tmpLocalPkg variable for use by other class methods. @author: Roy Nielsen ''' success = False urlfile = None try: self.tmpDir = "" if self.pkgUrl: ##### # Make a temporary directory to download the package try: self.tmpDir = tempfile.mkdtemp() self.logger.log(LogPriority.DEBUG, "tmpDir: " + str(self.tmpDir)) except Exception as err: message = "Problem creating temporary directory: " + \ str(err) self.logger.log(LogPriority.WARNING, message) raise err else: ##### # First try to open the URL if self.connection.isPageAvailable(self.pkgUrl): urlfile = urllib.request.urlopen(self.pkgUrl, timeout=10) ##### # Get just the package name out of the "package" url urlList = self.pkgUrl.split("/") self.pkgName = urlList[-1] self.tmpLocalPkg = os.path.join(self.tmpDir, self.pkgName) try: ##### # Next try to open a file for writing the local file f = open(str(self.tmpLocalPkg).strip(), "w") except IOError as err: message = "Error opening file - err: " + str(err) self.logger.log(LogPriority.INFO, message) raise err except Exception as err: message = "Generic exception opening file - err: " + \ str(err) self.logger.log(LogPriority.INFO, message) raise err else: self.logger.log(LogPriority.DEBUG, "..................") ##### # take data out of the url stream and put it in the # file a chunk at a time - more sane for large files chunk = 16 * 1024 * 1024 counter = 0 while 1: try: if urlfile: databytes = urlfile.read(chunk) if not databytes: message = "Done reading file: " + \ self.pkgUrl self.logger.log(LogPriority.DEBUG, message) break data = databytes f.write(data.decode('utf-8', 'replace')) message = "Read " + str(len(databytes)) + \ " bytes" self.logger.log(LogPriority.DEBUG, message) else: raise IOError("What file???") except (OSError or IOError) as err: ##### # Catch in case we run out of space on the # local system during the download process message = "IO error: " + str(err) self.logger.log(LogPriority.INFO, message) raise err except SSLError: # This will catch timeouts. Due to a bug in # the urlfile object, it will sometimes not # read, and will need to be recreated. The # counter ensures that if the error is not # a simple timeout, it will not get stuck # in an infinite loop. counter += 1 if counter > 3: raise self.logger.log(LogPriority.DEBUG, "Connection timed out. " + "Creating new urlfile " + "object.") urlfile = urllib.request.urlopen(self.pkgUrl, timeout=10) else: success = True f.close() urlfile.close() else: message = "Error opening the URL: " + str(self.pkgUrl) self.logger.log(LogPriority.DEBUG, message) else: message = "Need a valid package url. Can't download nothing..." self.logger.log(LogPriority.DEBUG, message) except(KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) return success ########################################################################### def checkMd5(self): '''Validate that the MD5 of the file is the same as the one on the server, for consistency's sake, ie we got a good download. Takes an MD5 of the local file, then appends it to the filename with a ".", then does a request and getreqpose and checks for a "200 in the response.status field. .<filename>.<UPPER-md5sum> Specific to the Casper server for Macs. If you want to override this method for another OS, subclass this class, or rewrite the function. @author: Roy Nielsen ''' success = False try: hashSuccess, myhash = self.getDownloadedFileMd5sum() if hashSuccess and myhash: ##### # Generate the name of the hash using the generated md5 hashname = "." + self.package + \ "." + str(myhash).upper().strip() message = "Hashname: " + str(hashname).strip() self.logger.log(LogPriority.DEBUG, message) ##### # Generate a web link to the hash file hashUrlList = self.pkgUrl.split("/") del hashUrlList[-1] hashUrlList.append(str(hashname)) self.hashUrl = "/".join(hashUrlList) self.logger.log(LogPriority.DEBUG, "Page: " + self.hashUrl) if self.connection.isPageAvailable(self.hashUrl): success = True message = "Found url: " + str(self.hashUrl) else: message = "Did NOT find url: " + str(self.hashUrl) self.logger.log(LogPriority, message) except(KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) return success ########################################################################### def getDownloadedFileMd5sum(self): '''Get the md5sum of a file on the local filesystem. Calculate the data in chunks in case of large files. :returns: s: retval - the md5sum of the file, or -1 if unsuccessful in getting the md5sum @author: Roy Nielsen ''' success = False try: retval = None try: if os.path.exists(self.tmpLocalPkg): fh = open(self.tmpLocalPkg, 'r') else: raise Exception("Cannot open a non-existing file...") except Exception as err: message = "Cannot open file: " + self.tmpLocalPkg + \ " for reading." self.logger.log(LogPriority.WARNING, message) self.logger.log(LogPriority.WARNING, "Exception : " + str(err)) success = False retval = '0' else: chunk = 16 * 1024 * 1024 m = hashlib.md5() while True: data = fh.read(chunk).encode('utf-8') if not data: break m.update(data) retval = m.hexdigest() except(KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) else: success = True return success, str(retval).strip() ########################################################################### def unArchive(self): '''Unarchive tar, tar.gz, tgz, tar.bz, and zip files. Using tarfile and zipfile libraries @Note: Will look for the first "*.app" or "*.pkg", and use that as the self.tmpLocalPkg that the rest of the class will use to try to install. This way the compressed file can be named something different than the actual package to install. This means that the compressed package can be named generically and a specified version is in the compressed file. Caution - This means when removing the package means you need to know the name of the package inside the compressed file to remove the installed file. @author: Roy Nielsen ''' try: retval = 0 ############################################## # Check if the file is a tar file of some sort inTarList = False tars = ["tar", "tgz", "tar.gz", "tbz", "tar.bz"] for extension in tars: if filename.endswith(extension): inTarList = True if re.match("^\s*$", filename): self.logger.log(LogPriority.DEBUG, "Emtpy archive name, cannot uncompress.") retval = -1 elif inTarList: import tarfile try: ##### # Open the tar file to prepare for extraction tar = tarfile.open(filename) try: message = "file: " + filename + ", dest: " + destination self.logger.log(LogPriority.DEBUG, message) ##### # Extract the tarball at the "destination" location tar.extractall(destination) except (OSError | IOError) as err: message = "Error extracting archive: " + str(err) self.logger.log(LogPriority.WARNING, message) raise err except Exception as err: message = "Error opening archive: " + str(err) self.logger.log(LogPriority.DEBUG, message) raise err else: tar.close() retval = 0 elif filename.endswith("zip"): ############################################### # Process if it a zip file. # # partial reference at: # http://stackoverflow.com/questions/279945/set-permissions-on-a-compressed-file-in-python import zipfile ##### # create a zipfile object, method for python 2.5: # http://docs.python.org/release/2.5.2/lib/module-zipfile.html # Use this type of method as without it the unzipped # permissions aren't what they are supposed to be. uzfile = zipfile.ZipFile(filename, "r") if zipfile.is_zipfile(filename): ##### # if the file is a zipfile # # list the files inside the zipfile # for internal_filename in uzfile.namelist(): for ifilename in uzfile.filelist: perm = ((ifilename.external_attr >> 16) & 0o777) internal_filename = ifilename.filename message = "extracting file: " + str(internal_filename) self.logger.log(LogPriority.DEBUG, message) ##### # Process directories first if internal_filename.endswith("/"): ##### # if a directory is found, create it (zipfile # doesn't) try: os.makedirs(os.path.join(destination, internal_filename.strip("/")), perm) except OSError as err: message = "Error making directory: " + str(err) self.logger.log(LogPriority.DEBUG, message) raise err else: continue ##### # Now process if it is not a directoy try: contents = uzfile.read(internal_filename) except RuntimeError as err: ##### # Calling read() on a closed ZipFile will raise a # RuntimeError self.logger.log(LogPriority.DEBUG, ["InstallingHelper.un_archive", "Called read on a closed ZipFile: " + str(err)]) raise err else: try: ##### # using os.open as regular open doesn't give # the ability to set permissions on a file. unzip_file = os.path.join(destination, internal_filename) target = os.open(unzip_file, os.O_CREAT | os.O_WRONLY, perm) try: ##### # when an exception is thrown in the try block, the # execution immediately passes to the finally block # After all the statements in the finally block are # executed, the exception is raised again and is # handled in the except statements if present in # the next higher layer of the try-except statement os.write(target, contents) finally: os.close(target) except OSError as err: message = "Error opening file for writing: " + \ str(err) self.logger.log(LogPriority.WARNING, message) raise err uzfile.close() if not retval < -1: # if there was not an error creating a directory as # part of the unarchive process retval = 0 else: # Not a zipfile based on the file's "magic number" self.logger.log(LogPriority.DEBUG, ["InstallingHelper.un_archive", "file: " + filename + " is not a zipfile."]) retval = -1 ##### # Find the first item in the directory that is a .app | .pkg | .mpkg # Use that as the self.tmpLocalPkg that the rest of the class will # use to try to install. This way the compressed file can be # named something different than the actual package to install. # This means that the compressed package can be named generically # and a specified version is in the compressed file. # Caution - This means when removing the package means you need # to know the name of the package inside the compressed file to # remove the installed file. dirlist = os.listdir(self.tmpDir) for myfile in dirlist: if re.search(".app", myfile) or \ re.search(".pkg", myfile) or \ re.search(".mpkg", myfile): self.tmpLocalPkg = self.tmpDir + "/" + str(myfile) break except(KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) return retval ########################################################################### def copyInstall(self, dest="/Applications", isdir=True, mode=0o775, owner="root", group="admin"): '''Copies a directory tree, or contents of a directory to "dest" destination. :param dest: (Default value = "/Applications") :param isdir: (Default value = True) :param mode: (Default value = 0775) :param owner: (Default value = "root") :param group: (Default value = "admin") ''' try: if re.match("^\s*$", self.tmpDir): self.tmpDir = "." src_path = self.tmpDir + "/" + self.pkgName self.logger.log(LogPriority.DEBUG, "New src_path: " + src_path) if isdir: try: shutil.copytree(src_path, dest) except Exception as err: message = "Unable to recursively copy dir: " + src_path + \ " error: " + str(err) self.logger.log(LogPriority.ERROR, message) raise Exception(message) else: success = True ##### # try to chmod the directory (not recursively) try: os.chmod(dest, mode) except OSError as err: success = False message = "Unable to change mode: " + str(err) self.logger.log(LogPriority.ERROR, message) raise Exception(message) ##### # UID & GID needed to chown the directory uid = 0 gid = 80 try: uid = pwd.getpwnam(owner)[2] except KeyError as err: success = False message = "Error: " + str(err) self.logger.log(LogPriority.DEBUG, message) raise Exception(message) try: gid = grp.getgrnam(group)[2] except KeyError as err: success = False message = "Error: " + str(err) self.logger.log(LogPriority.DEBUG, message) raise Exception(message) ##### # Can only chown if uid & gid are int's if ((type(uid) == type(int())) and (type(gid) == type(int()))): try: os.chown(dest, uid, gid) except OSError as err: success = False message = "Error: " + str(err) self.logger.log(LogPriority.DEBUG, message) raise Exception(message) else: try: dir_list = os.listdir(src_path) except OSError as err: message = "Error listing files from: " + src_path + \ " error: " + str(err) self.logger.log(LogPriority.ERROR, message) raise Exception(message) else: for myfile in dir_list: try: shutil.copy2(myfile, dest) except Exception as err: message = "Error copying file: " + myfile + \ " error: " + str(err) self.logger.log(LogPriority.DEBUG, message) raise else: success = True except(KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) return success ########################################################################## def installPkg(self): '''This function references generic methods from the InstallingHelper class to download an archived .pkg or .mpkg file, check md5sum of the file against the md5 on the server, unarchive the file and install the package. It will remove the temporary directory that the file was downloaded and unarchived to when the install is complete. local vars: tmp_dir = the temporary directory that is created for the downloaded file, and to unarchive it into. self.sig_match = A variable that tells if the signature of the downloaded file and the server string match. Set in the download_and_prepare method of the parent class pkg_extension = for verifying that the package extension is either ".pkg" or ".mpkg" self.package_name = the name of the package to be downloaded, without the archive extension. Set in the InstallingHelper class. @author: Roy Nielsen ''' success = False try: ##### # Check if it's a pkg or mpkg if self.tmpLocalPkg.endswith(".pkg") or \ self.tmpLocalPkg.endswith(".mpkg"): ##### # set up the install package command string cmd = ["/usr/bin/sudo", "/usr/sbin/installer", "-verboseR", "-pkg", self.tmpLocalPkg, "-target", "/"] self.ch.executeCommand(cmd) self.logger.log(LogPriority.DEBUG, self.ch.getOutputString()) if self.ch.getReturnCode() == 0: success = True ##### # remove the temporary directory where the archive was # downloaded and unarchived. try: shutil.rmtree(self.tmpDir) except Exception as err: self.logger.log(LogPriority.ERROR, "Exception: " + str(err)) raise err else: success = True else: self.logger.log(LogPriority.ERROR, "Valid package " + "extension not found, cannot install:" + " " + self.tmpLocalPkg) except(KeyboardInterrupt, SystemExit): raise except Exception as err: print(err) self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) return success
class Freebsd(object): '''The template class that provides a framework that must be implemented by all platform specific pkgmgr classes. :version: @author: Derek T Walker 08-06-2012 ''' def __init__(self, logger): self.logger = logger self.ch = CommandHelper(self.logger) self.install = "/usr/sbin/pkg_add -r -f " self.remove = "/usr/sbin/pkg_delete " self.info = "/usr/sbin/pkg_info " self.versioncheck = "/usr/sbin/pkg_version -l < " ############################################################################### def installpackage(self, package): '''Install a package. Return a bool indicating success or failure. :param string: package : Name of the package to be installed, must be recognizable to the underlying package manager. :param package: :returns: installed :rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/18/2017 - doc string fixes; refactor of method; parameter validation ''' installed = False try: # parameter validation if not package: self.logger.log(LogPriority.DEBUG, "Parameter: package was blank!") return installed if not isinstance(package, str): self.logger.log(LogPriority.DEBUG, "Parameter: package needs to be of type string. Got: " + str(type(package))) return installed self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " installed successfully") installed = True else: self.logger.log(LogPriority.DEBUG, "Failed to install package " + str(package)) except Exception: raise return installed ############################################################################### def removepackage(self, package): '''Remove a package. Return a bool indicating success or failure. :param package: string; Name of the package to be removed, must be recognizable to the underlying package manager. :returns: removed :rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/18/2017 - refactor of method; doc string fixes; parameter validation ''' removed = True try: # parameter validation if not package: self.logger.log(LogPriority.DEBUG, "Parameter: package was blank!") return removed if not isinstance(package, str): self.logger.log(LogPriority.DEBUG, "Parameter: package needs to be of type string. Got: " + str(type(package))) return removed self.ch.executeCommand(self.remove + package) retcode = self.ch.getReturnCode() if retcode != 0: self.logger.log(LogPriority.DEBUG, "Failed to remove package " + str(package)) removed = False else: self.logger.log(LogPriority.DEBUG, "Successfully removed package " + str(package)) except Exception: raise return removed ############################################################################### def checkInstall(self, package): '''Check the installation status of a package. Return a bool; True if the package is installed. :param package: string; Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. :returns: installed :rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/18/2017 - refactor of method; doc string fixes; parameter validation ''' installed = False try: # parameter validation if not package: self.logger.log(LogPriority.DEBUG, "Parameter: package was blank!") return installed if not isinstance(package, str): self.logger.log(LogPriority.DEBUG, "Parameter: package needs to be of type string. Got: " + str(type(package))) return installed self.ch.executeCommand(self.info + package) retcode = self.ch.getReturnCode() if retcode != 0: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is NOT installed") else: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is installed") installed = True except Exception: raise return installed def checkUpdate(self, package=""): '''STUB METHOD check for updates for the specified package if package is not specified, check for all package updates currently unfinished as I have no earthly idea how to reliably manage packages on bsd (ports? portsmanager? portsmaster? pkg_zzz? pkg?) also versioning heavily affects what package manager binary(ies) is/are available. :param package: string; name of package to check (Default value = "") :returns: updatesavail :rtype: bool @author: Breen Malmberg ''' updatesavail = False try: pass # stub except Exception: raise return updatesavail def Update(self, package=""): '''STUB METHOD update the specified package if no package is specified, then update all packages on the system currently unfinished as I have no earthly idea how to reliably manage packages on bsd (ports? portsmanager? portsmaster? pkg_zzz? pkg?) also versioning heavily affects what package manager binary(ies) is/are available. :param package: string; name of package to update (Default value = "") :returns: updated :rtype: bool @author: Breen Malmberg ''' updated = False try: pass # stub except Exception: raise return updated def getPackageFromFile(self, filename): '''return a string containing the name of the package which provides the specified filename :param filename: :returns: packagename :rtype: string @author: Breen Malmberg ''' packagename = "" try: # parameter validation if not filename: self.logger.log(LogPriority.DEBUG, "Parameter: filename was blank!") return packagename if not isinstance(filename, str): self.logger.log(LogPriority.DEBUG, "Parameter: filename needs to be of type string. Got: " + str(type(filename))) return packagename self.ch.executeCommand(self.info + " -W " + filename) if self.ch.getReturnCode() == 0: packagename = self.ch.getOutputString() else: self.logger.log(LogPriority.DEBUG, "Failed to get the package for the given filename") except Exception: raise return packagename ############################################################################### def getInstall(self): return self.install ############################################################################### def getRemove(self): return self.remove def getInfo(self): return self.info
class KVAProfiles(object): def __init__(self, logger, path): self.logger = logger self.path = path self.undocmd = "" self.installcmd = "" self.data = "" self.badvalues = "" def validate(self, output, data): '''@summary: Method checks if either profile is installed and/or contents of profile is up to par with our security standards @author: dwalker :param output: The output from system_profiler SPConfigurationProfileDataType command :param data: The data dictionary from the calling rule with the profile identifiers and key value pairs to look for. View KVEditor class for details on data dict format :returns: bool - True or False ''' '''Go throught output and see if we can find the profile identifier (key)''' self.badvalues = "" self.data = data for key, val in self.data.items(): '''In some cases val will be blank in which case we're just looking for the presence of the profile or more specifically the identifier (key)''' if not val: for line in output: if re.search("^" + key, line.strip()): return True '''We never found the profile, return False''' debug = "The profile sub-identifier:" + key + " was not found\n" self.logger.log(LogPriority.DEBUG, debug) return False iterator1 = 0 for line in output: keyoutput = [] '''We found the profile identifier''' if re.search("^" + key + ":$", line.strip()): ''''Put all output after the identifier line into a new list called temp''' temp = output[iterator1 + 1:] iterator2 = 0 '''Go through this new list and look for where the payload section starts. The rest of the output gets stored in the keyoutput list''' for line in temp: if re.search("^Payload Data:", line.strip()): temp = temp[iterator2 + 1:] keyoutput = temp break else: iterator2 += 1 if keyoutput: payloadblocktemp = [] '''This next loop is getting everything inside the payload section stopping before the next identifier''' for line in keyoutput: if not re.search(".*:", line): line = re.sub("\s+", "", line) payloadblocktemp.append(line) else: break payloadblock = [] i = 0 dontadd = False '''This loop is to clean up the output and squash together any consecutive lines that are blank values inside {} or ()''' while i < len(payloadblocktemp): if dontadd: i += 1 dontadd = False continue '''The next two if statements check to see if two consecutive lines represent an empty dictionary or tuple. If so we want to combine these into one line without the ; at the end''' if re.search("\{$", payloadblocktemp[i]): try: if re.search("\};$", payloadblocktemp[i + 1]): dontadd = True line = re.sub(";$", "", payloadblocktemp[i + 1]) payloadblock.append( payloadblocktemp[i] + line) else: payloadblock.append( payloadblocktemp[i]) except IndexError as err: debug = "File in bad format, fix will install profile\n" self.logger.log(LogPriority.DEBUG, [debug, err]) return False elif re.search("\($", payloadblocktemp[i]): try: if re.search("\);$", payloadblocktemp[i + 1]): dontadd = True line = re.sub(";$", "", payloadblocktemp[i + 1]) payloadblock.append( payloadblocktemp[i] + line) else: payloadblock.append( payloadblocktemp[i]) except IndexError as err: debug = "File in bad format, fix will install profile\n" self.logger.log(LogPriority.DEBUG, [debug, err]) return False else: payloadblock.append(payloadblocktemp[i]) i += 1 '''k is the key inside val variable (e.g. allowsimple) and v is the value, in this example, a dict (e.g. {"val": "1", "type": "bool", "accept": "", "result": False"})''' for k, v in val.items(): retval = False if isinstance(v["val"], list): retval = self.checkTuple(k, v, payloadblock) elif isinstance(v["val"], dict): retval = self.checkDict(k, v, payloadblock) else: retval = self.checkSimple(k, v, payloadblock) if retval == True: v["result"] = True else: iterator1 += 1 self.setbadvalues() return self.data def checkSimple(self, k, v, payloadblock): '''@summary: Method that checks payloadblock contents for k (key) and associated v (value) @author: dwalker :param k: Not to be confused with the key in the calling method which was our identifier before the payload. This key is now the key in the inner dictionary passed through as val from the calling method :param v: Not to be confused with the value in the calling method which was our inner dictionary passed through as val. This val is now the inner dictionary: {"val":..., "type": ..., "accept": ..., "result": False} :param payloadblock: A list of lines from our payload portion of the output from the system_profiler command :returns: bool - True or False ''' founditem = False retval = True unsecure = False debug = "" for line in payloadblock: if re.search("^\"{0,1}" + k + "\"{0,1}=", line.strip()): founditem = True temp = line.strip().split("=") try: if temp[1]: '''Remove any arbitrary whitespace''' temp[1] = re.sub("\s", "", temp[1]) '''Remove semicolon at end if exists''' temp[1] = re.sub(";$", "", temp[1]) '''We handle strings and bools the same, checking the value and if accept key has an alternately accpeted value, we check for that, if neither match, return False''' if v["type"] == "bool" or v["type"] == "string": if str(temp[1].strip()) != v["val"]: if v["accept"]: if temp[1].strip() != v["accept"]: debug += "Key: " + k + " doesn't " + \ "contain the correct boolean " + \ "value\n" unsecure = True break else: debug += "Key: " + k + " doesn't " + \ "contain the correct boolean " + \ "value\n" unsecure = True break '''If the second value inside the list v is the word int, then we want to make sure that our value found after the = in our output matches what we're expecting in v["val"] which could be any numerical integer. It it's not correct, then we check the accept key for the word "more" or "less". More indicates that if the values don't match but the value in our output is a greater integer value than what we're expecting, then it's still ok and vice versa with the less keyword.''' elif v["type"] == "int": if temp[1].strip() != v["val"]: if v["accept"] == "more": if int(temp[1].strip()) < int(v["val"]): debug += "Key: " + k + " doesn't " + \ "contain the correct integer " + \ "value\n" unsecure = True break elif v["accept"] == "less": if int(temp[1].strip()) > int(v["val"]): debug += "Key: " + k + " doesn't " + \ "contain the correct integer " + \ "value\n" unsecure = True break elif v["accept"] == "": debug = "Key: " + k + " doesn't " + \ "contain the correct integer " + \ "value\n" unsecure = True else: error = "Invalid value for accept parameter in data " + \ "dictionary. Needs to be either more or less keyword\n" self.logger.log(LogPriority.ERROR, error) raise Exception(error) except IndexError: debug += "Profile in bad format\n" break if not founditem: debug = "Key: " + k + " not found\n" retval = False if unsecure: debug = "Key: " + k + " found but had an incorrect value\n" retval = False if debug: self.logger.log(LogPriority.DEBUG, debug) return retval def checkTuple(self, k, v, payloadblock): '''@summary: Method that checks payloadblock contents for k (key) and associated v (value) @author: dwalker :param k: Not to be confused with the key in the calling method which was our identifier before the payload. This key is now the key in the inner dictionary passed through as val from the calling method :param v: Not to be confused with the value in the calling method which was our inner dictionary passed through as val. This val is now the inner dictionary: {"val":..., "type": ..., "accept": ..., "result": False} :param payloadblock: A list of lines from our payload portion of the output from the system_profiler command :returns: bool - True or False ''' retval = True iterator = 0 temp, temp2 = [], [] for line in payloadblock: if re.search("^\"{0,1}" + k + "\"{0,1}=", line.strip()): if re.search("\(\)$", line): if str(v["val"]) == "[]": return True else: return False elif re.search("\($", line): temp = payloadblock[iterator + 1:] break else: iterator += 1 iterator = 0 for line in temp: if re.search("\)\;", line): temp2 = temp[:iterator] else: iterator += 1 if temp2: temp = temp2 if temp: replaceables = [] for line in temp: if re.search("\,$", line): line = re.sub("\,$", "", line) replaceables.append(line) temp = replaceables removeables = [] for line in temp: if line in v["val"]: removeables.append(line) if removeables: v = v["val"] for item in removeables: v.remove(item) temp.remove(item) v = tuple(v) if v: '''There are still items left so we didn't find them all''' debug = "The following tuple items weren't found for the key " + k + "\n" debug += str(v) + "\n" self.logger.log(LogPriority.DEBUG, debug) retval = False if temp: debug = "The following items were in the output that shouldn't have been\n" for item in temp: if not re.search("\)\;|\($", item): debug += str(temp) + "\n" self.logger.log(LogPriority.DEBUG, debug) retval = False else: debug = "key " + k + " wasn't found\n" self.logger.log(LogPriority.DEBUG, debug) retval = False return retval def checkDict(self, k, v, payloadblock): '''@summary: Method that checks payloadblock contents for k (key) and associated v (value) @author: dwalker :param k: Not to be confused with the key in the calling method which was our identifier before the payload. This key is now the key in the inner dictionary passed through as val from the calling method :param v: Not to be confused with the value in the calling method which was our inner dictionary passed through as val. This val is now the inner dictionary: {"val":..., "type": ..., "accept": ..., "result": False} :param payloadblock: A list of lines from our payload portion of the output from the system_profiler command :returns: bool - True or False ''' retval = True iterator = 0 for line in payloadblock: if re.search("^\"{0,1}" + k + "\"{0,1}=", line): if re.search("\{\}$", line): if str(v["val"]) == "{}": return True else: return False elif re.search("\{$", line): temp = payloadblock[iterator + 1:] break else: iterator += 1 for k2, v2 in v.items(): if isinstance(v2, list): retval = self.checkSimple(k2, v2, temp) elif isinstance(v2, tuple): retval = self.checkTuple(k2, v2, temp) elif isinstance(v2, dict): retval = self.checkDict(k2, v2, temp) if not retval: return False return retval def setUndoCmd(self, undocmd): '''@summary: Mutator method to set self.undocmd to the passed in undo command. @author: dwalker :param undocmd: undo command passed through from update method ''' self.undocmd = undocmd def setInstallCmd(self, installcmd): '''@summary: Mutator method to set self.installcmd to thev passed in install command. @author: dwalker :param installcmd: install command passed through from update method ''' self.installcmd = installcmd def getUndoCmd(self): '''@summary: Accessor method to retrieve self.undocmd @author: dwalker :returns: self.undocmd ''' return self.undocmd def getInstallCmd(self): '''@summary: Accessor method to retrieve self.installcmd @author: dwalker :returns: self.installcmd ''' return self.installcmd def update(self): '''@summary: Method to set the install command for installing the profile for the fix method and set the remove command for removing the profile for the undo method in upper implementing classes @author: dwalker :returns: bool - True ''' cmd = ["/usr/bin/profiles", "-I", "-F", self.path] self.setInstallCmd(cmd) cmd = ["/usr/bin/profiles", "-R", "-F", self.path] self.setUndoCmd(cmd) return True def commit(self): '''@summary: Method that performs the install command to install the appropriate profile for the calling rule. @author: dwalker :returns: bool - True or False ''' self.ch = CommandHelper(self.logger) if self.installcmd: if not self.ch.executeCommand(self.installcmd): return False elif self.ch.getReturnCode() != 0: return False else: return True else: return False return True def setbadvalues(self): if self.data: for k, v in self.data.items(): for k2, v2, in v.items(): if v2["result"] == False: self.badvalues += k2 + " key does not have value of " + str( v2["val"]) + " or doesn't exist\n"
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 Zypper(object): """The template class that provides a framework that must be implemented by all platform specific pkgmgr classes. @author: Derek T Walker @change: 2012/08/08 Derek Walker - Original Implementation @change: 2014/09/10 dkennel - Added -n option to search command string @change: 2014/12/24 Breen Malmberg - fixed a typo in the old search string; fixed multiple pep8 violations; changed search strings to be match exact and search for installed or available separately @change: 2015/08/20 eball - Added getPackageFromFile and self.rpm var @change: 2016/08/02 eball - Moved checkInstall return out of else block @change: 2017/04/19 Breen Malmberg - refactored multiple methods; cleaned up doc strings; added logging; added two methods: Update and checkUpdate; removed detailedresults reset in __init__ (this should always be handled in the calling rule); replaced detailedresults instances with logging; added the flag "--quiet" to the install variable """ def __init__(self, logger): self.logger = logger self.ch = CommandHelper(self.logger) self.zyploc = "/usr/bin/zypper" self.install = self.zyploc + " --non-interactive --quiet install " self.remove = self.zyploc + " --non-interactive remove " self.searchi = self.zyploc + " --non-interactive search --match-exact -i " self.searchu = self.zyploc + " --non-interactive search --match-exact -u " self.updates = self.zyploc + " lu " self.upzypp = self.zyploc + " up " self.rpm = "/usr/bin/rpm -q " self.pkgtype = "zypper" self.pkgerrs = [1, 2, 3, 4, 5, 6] self.pkgnotfound = [104] def installpackage(self, package): """Install a package. Return a bool indicating success or failure. :param package: string; Name of the package to be installed, must be recognizable to the underlying package manager. :returns: installed :rtype: bool @author: Derek Walker @change: Breen Malmberg - 12/24/2014 - fixed method doc string formatting @change: Breen Malmberg - 10/1/2018 - added check for package manager lock and retry loop """ installed = True maxtries = 12 trynum = 0 while psRunning("zypper"): trynum += 1 if trynum == maxtries: self.logger.log( LogPriority.DEBUG, "Timed out while attempting to install package due to zypper package manager being in-use by another process." ) installed = False return installed else: self.logger.log( LogPriority.DEBUG, "zypper package manager is in-use by another process. Waiting for it to be freed..." ) time.sleep(5) try: self.ch.executeCommand(self.install + package) retcode = self.ch.getReturnCode() if retcode in self.pkgerrs: errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, "Package installation because:\n" + errstr) installed = False elif retcode in self.pkgnotfound: self.logger.log( LogPriority.DEBUG, "Package installation failed because zypper could not find a package named: " + str(package)) installed = False if installed: self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " installed successfully") else: self.logger.log(LogPriority.DEBUG, "Failed to install package " + str(package)) except Exception: raise return installed def removepackage(self, package): """Remove a package. Return a bool indicating success or failure. :param package: string; Name of the package to be removed, must be recognizable to the underlying package manager. :returns: removed :rtype: bool @author: Derek Walker @change: 12/24/2014 - Breen Malmberg - fixed method doc string formatting; fixed an issue with var 'removed' not being initialized before it was called """ removed = True maxtries = 12 trynum = 0 while psRunning("zypper"): trynum += 1 if trynum == maxtries: self.logger.log( LogPriority.DEBUG, "Timed out while attempting to remove package due to zypper package manager being in-use by another process." ) removed = False return removed else: self.logger.log( LogPriority.DEBUG, "zypper package manager is in-use by another process. Waiting for it to be freed..." ) time.sleep(5) try: self.ch.executeCommand(self.remove + package) retcode = self.ch.getReturnCode() if retcode in self.pkgerrs: errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, "Package removal failed because:\n" + errstr) removed = False elif retcode in self.pkgnotfound: self.logger.log( LogPriority.DEBUG, "No package found matching: " + str(package) + ". Nothing to remove") if removed: self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " was removed successfully") else: self.logger.log(LogPriority.DEBUG, "Failed to remove package " + str(package)) except Exception: raise return removed def checkInstall(self, package): """Check the installation status of a package. Return a bool; True if the package is installed. :param string package: Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. :param package: :returns: bool @author: Derek Walker @change: 12/24/2014 - Breen Malmberg - fixed method doc string formatting @change: 12/24/2014 - Breen Malmberg - changed var name 'found' to 'installed' @change: 12/24/2014 - Breen Malmberg - now uses correct search syntax @change: 12/24/2014 - Breen Malmberg - removed detailedresults update on 'found but not installed' as this no longer applies to this method """ installed = True maxtries = 12 trynum = 0 while psRunning("zypper"): trynum += 1 if trynum == maxtries: self.logger.log( LogPriority.DEBUG, "Timed out while attempting to check status of package due to zypper package manager being in-use by another process." ) installed = False return installed else: self.logger.log( LogPriority.DEBUG, "zypper package manager is in-use by another process. Waiting for it to be freed..." ) time.sleep(5) try: self.ch.executeCommand(self.searchi + package) retcode = self.ch.getReturnCode() if retcode in self.pkgerrs: errstr = self.ch.getErrorString() self.logger.log( LogPriority.DEBUG, "Failed to check for package: " + str(package) + " because:\n" + errstr) installed = False elif retcode in self.pkgnotfound: installed = False if installed: self.logger.log(LogPriority.DEBUG, " Package " + str(package) + " is installed") else: self.logger.log( LogPriority.DEBUG, " Package " + str(package) + " is NOT installed") except Exception: raise return installed def checkAvailable(self, package): """check if given package is available to install on the current system :param package: :returns: bool @author: Derek Walker @change: 12/24/2014 - Breen Malmberg - added method documentation @change: 12/24/2014 - Breen Malmberg - changed var name 'found' to 'available' @change: 12/24/2014 - Breen Malmberg - fixed search syntax and updated search variable name @change: Breen Malmberg - 5/1/2017 - replaced detailedresults with logging; added parameter validation """ available = True found = False maxtries = 12 trynum = 0 while psRunning("zypper"): trynum += 1 if trynum == maxtries: self.logger.log( LogPriority.DEBUG, "Timed out while attempting to check availability of package, due to zypper package manager being in-use by another process." ) available = False return available else: self.logger.log( LogPriority.DEBUG, "zypper package manager is in-use by another process. Waiting for it to be freed..." ) time.sleep(5) try: self.ch.executeCommand(self.searchu + package) retcode = self.ch.getReturnCode() if retcode in self.pkgerrs: errstr = self.ch.getErrorString() self.logger.log( LogPriority.DEBUG, "Failed to check if package: " + str(package) + " is available, because:\n" + errstr) available = False elif retcode in self.pkgnotfound: available = False else: output = self.ch.getOutput() for line in output: if re.search(package, line): found = True if not found: available = False if available: self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " is available to install") else: self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " is NOT available to install") except Exception: raise return available def checkUpdate(self, package=""): """check for available updates for specified package. if no package is specified, then check for updates to the entire system. :param package: string; name of package to check (Default value = "") :returns: updatesavail :rtype: bool @author: Breen Malmberg """ # zypper does not have a package-specific list updates mechanism # you have to list all updates or nothing updatesavail = True try: if package: self.ch.executeCommand(self.updates + " | grep " + package) else: self.ch.executeCommand(self.updates) retcode = self.ch.getReturnCode() if retcode in [2, 3, 4, 5, 6]: errstr = self.ch.getErrorString() self.logger.log( LogPriority.DEBUG, "Failed to check for updates because:\n" + errstr) updatesavail = False elif retcode in self.pkgnotfound: updatesavail = False if not updatesavail: self.logger.log(LogPriority.DEBUG, "No updates available") else: self.logger.log(LogPriority.DEBUG, "Updates available") except Exception: raise return updatesavail def Update(self, package=""): """update a specified package if no package name is specified, then update all packages on the system :param package: string; name of package to update (Default value = "") :returns: updated :rtype: bool @author: Breen Malmberg """ updated = True try: self.ch.executeCommand(self.upzypp + package) retcode = self.ch.getReturnCode() if retcode in self.pkgerrs: errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, "Failed to update because:\n" + errstr) updated = False elif retcode in self.pkgnotfound: self.logger.log( LogPriority.DEBUG, "Unable to find package named: " + str(package)) updated = False if updated: self.logger.log(LogPriority.DEBUG, "Updates applied successfully") else: self.logger.log(LogPriority.DEBUG, "Failed to apply updates") except Exception: raise return updated def getPackageFromFile(self, filename): """Returns the name of the package that provides the given filename/path. :param filename: :returns: string name of package if found, None otherwise @author: Eric Ball """ packagename = "" try: self.ch.executeCommand(self.rpm + "-f " + filename) retcode = self.ch.getReturnCode() if retcode in self.pkgerrs: errstr = self.ch.getErrorString() self.logger.log( LogPriority.DEBUG, "Failed to get package name because:\n" + errstr) else: outputstr = self.ch.getOutputString() packagename = outputstr except Exception: raise return packagename def getInstall(self): """return the install command string for the zypper pkg manager :returns: string @author: Derek Walker @change: 12/24/2014 - Breen Malmberg - added method documentation """ return self.install def getRemove(self): """return the uninstall/remove command string for the zypper pkg manager :returns: string @author: Derek Walker @change: 12/24/2014 - Breen Malmberg - added method documentation """ return self.remove
class Portage(object): '''The template class that provides a framework that must be implemented by all platform specific pkgmgr classes. :version: @author: Derek T Walker 08-06-2012 @change: Breen Malmberg - 4/18/2017 - minor method edits; added doc strings; added logging; added parameter validation; removed detailedresults reset in __init__ of this class (should only be done in rule template and the individual rules); fixed some pep8 grammar; fixed the location of emerge (changed 'emerge' to '/usr/bin/emerge') ''' def __init__(self, logger): self.logger = logger self.ch = CommandHelper(self.logger) self.install = "/usr/bin/emerge " self.remove = self.install + " --unmerge " self.search = self.install + " --search " ############################################################################### def installpackage(self, package): '''Install a package. Return a bool indicating success or failure. :param package: string; Name of the package to be installed, must be recognizable to the underlying package manager. :returns: installed :rtype: bool @author: Derek T Walker 08-06-2012 @change: Breen Malmberg - 4/18/2017 - fixed return var logic; fixed doc string; added parameter validation; changed detailedresults to logging ''' installed = False try: # parameter validation if not package: self.logger.log(LogPriority.DEBUG, "Parameter: package was blank!") return installed if not isinstance(package, str): self.logger.log( LogPriority.DEBUG, "Parameter: package must be of type string. Got: " + str(type(package))) return installed self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: installed = True self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " was successfully installed") else: self.logger.log(LogPriority.DEBUG, "Failed to install package " + str(package)) except Exception: raise return installed ############################################################################### def removepackage(self, package): '''Remove a package. Return a bool indicating success or failure. :param package: string; Name of the package to be removed, must be recognizable to the underlying package manager. :returns: removed :rtype: bool @author: Derek T Walker 08-6-2012 @change: Breen Malmberg - 4/18/2017 - minor edit; replaced detailedresults with logging; fixed return var logic; added parameter validation; fixed doc string ''' removed = False try: # parameter validation if not package: self.logger.log(LogPriority.DEBUG, "Parameter: package was blank!") return removed if not isinstance(package, str): self.logger.log( LogPriority.DEBUG, "Parameter package must be of type string. Got: " + str(type(package))) return removed self.ch.executeCommand(self.remove + package) if self.ch.getReturnCode() == 0: removed = True self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " was successfully removed") else: self.logger.log(LogPriority.DEBUG, "Failed to remove package " + str(package)) except Exception: raise return removed ############################################################################### def checkInstall(self, package): '''Check the installation status of a package. Return a bool; True if the package is installed. :param package: string; Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager :returns: installed :rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/18/2017 - fixed doc string; minor edit; replaced detailedresults with logging (detailedresults should be handled in the calling rule); changed name of return var; added parameter validation ''' installed = False try: # parameter validation if not package: self.logger.log(LogPriority.DEBUG, "Parameter: package was blank!") return installed if not isinstance(package, str): self.logger.log( LogPriority.DEBUG, "Parameter: package must be of type string. Got: " + str(type(package))) return installed stringToinstalled = "(.*)" + package + "(.*)" f = glob.glob('/var/db/pkg') for item in f: if re.search(stringToinstalled, item): installed = True break if installed: self.logger.log(LogPriority.DEBUG, "\nPackage " + str(package) + " is installed") else: self.logger.log( LogPriority.DEBUG, "\nPackage " + str(package) + " is NOT installed") except Exception: raise return installed ############################################################################### def checkAvailable(self, package): '''check if the specified package is available return True if found return False if not found :param package: string; name of package to check for :returns: found :rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/18/2017 - added doc string; minor edit; parameter validation added; added code comments; added logging; replaced detailedresults with logging ''' available = False try: # parameter validation if not package: self.logger.log(LogPriority.DEBUG, "Parameter: package was blank!") return available if not isinstance(package, str): self.logger.log( LogPriority.DEBUG, "Parameter: package must be of type string. Got: " + str(type(package))) return available self.ch.executeCommand(self.search + package) if self.ch.getReturnCode() == 0: available = True self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " is available to install") else: self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " is NOT available to install") except Exception: raise return available def checkUpdate(self, package=""): '''check for updates for the specified package or if not package is specified, check for all updates on the system :param package: string; name of package, for which, to check updates (Default value = "") :returns: updatesavail :rtype: bool @author: Breen Malmberg ''' # defaults updatesavail = False searchpkg = self.search + package + " &> /dev/null" try: # I have no idea how to check for ALL updates, with portage/emerge.. if package: # parameter validation if not isinstance(package, str): self.logger.log( LogPriority.DEBUG, "Parameter package must be of type string. Got: " + str(type(package))) return updatesavail # check for updates for specified package self.ch.executeCommand(searchpkg) if self.ch.getReturnCode() == 100: updatesavail = True else: self.logger.log( LogPriority.DEBUG, "No package specified. Did NOT search for all updates.") except Exception: raise return updatesavail def Update(self, package=""): '''update either the specified package or all packages on the system, if no package is specified :param pacakge: string; name of package to update :param package: (Default value = "") :returns: updated :rtype: bool @author: Breen Malmberg ''' # defaults updated = True updatepkg = self.install + " -avDuN " + package updateall = self.install + " -uDU --with-bdeps=y @world" try: if package: self.logger.log( LogPriority.DEBUG, "Attempting to update package " + str(package) + " ...") self.ch.executeCommand(updatepkg) retcode = self.ch.getReturnCode() if retcode != 0: updated = False self.logger.log(LogPriority.DEBUG, "Failed to update package " + str(package)) self.logger.log(LogPriority.DEBUG, "Error code: " + str(retcode)) else: self.logger.log(LogPriority.DEBUG, "Attempting to update all packages...") self.ch.executeCommand(updateall) retcode = self.ch.getReturnCode() if retcode != 0: updated = False self.logger.log(LogPriority.DEBUG, "Failed to update packages") self.logger.log(LogPriority.DEBUG, "Error code: " + str(retcode)) except Exception: raise return updated ############################################################################### def getInstall(self): return self.install ############################################################################### def getRemove(self): return self.remove def getSearch(self): return self.search
class Solaris(object): def __init__(self, logger): '''Remember, for solaris sparc systems, the package names will typically begin with SUNW ''' self.logger = logger self.detailedresults = "" self.sparc = "(.)*sparc(.)*" self.ch = CommandHelper(self.logger) self.install = "/usr/sbin/pkgadd -n -i " self.remove = "/usr/sbin/pkgrm -n " self.info = "/usr/bin/pkginfo " ############################################################################### def installpackage(self, package): '''Install a package. Return a bool indicating success or failure. :param string: package : Name of the package to be installed, must be recognizable to the underlying package manager. :param package: :returns: bool : @author ''' try: # retval = call(self.install + package,stdout=None,shell=True) # if retval == 0: self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: self.detailedresults = package + " pkg installed successfully" self.logger.log(LogPriority.INFO, ["Solaris.install", self.detailedresults]) return True else: self.detailedresults = package + " pkg not able to install." self.detailedresults += "This package may not be available, \ may be mispelled, or may depend on other packages in which non\ interactive mode can't be used" self.logger.log(LogPriority.INFO, ["Solaris.install", self.detailedresults]) return False except (KeyboardInterrupt, SystemExit): raise except Exception as err: #print err self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.INFO, ['Solaris.install', self.detailedresults]) ############################################################################### def removepackage(self, package): '''Remove a package. Return a bool indicating success or failure. :param string: package : Name of the package to be removed, must be recognizable to the underlying package manager. :param package: :returns: bool : @author ''' try: # retval = call(self.remove + package,stdout=None,shell=True) # if retval == 0: self.ch.executeCommand(self.remove + package) if self.ch.getReturnCode() == 0: self.detailedresults = package + " pkg removed successfully" self.logger.log(LogPriority.INFO, ["Solaris.remove", self.detailedresults]) return True else: self.detailedresults = package + " pkg not able to be removed." self.detailedresults += "This package may not be installed \ may be mispelled or may depend on other packages in which non \ interactive mode can't be used" self.logger.log(LogPriority.INFO, ["Solaris.remove", self.detailedresults]) return False except (KeyboardInterrupt, SystemExit): raise except Exception as err: #print err self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.INFO, ["Solaris.remove", self.detailedresults]) ############################################################################### def checkInstall(self, package): '''Check the installation status of a package. Return a bool; True if the packageis installed. :param string: package : Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. :param package: :returns: bool : @author ''' try: if search("^SUNW", package): retval = call(["/usr/bin/pkginfo", package], stdout=None, shell=False) else: retval = call("/usr/bin/pkginfo | grep -i " + package, stdout=None, shell=True) if retval == 0: self.detailedresults = package + " pkg found" self.logger.log(LogPriority.INFO, ["Solaris.check", self.detailedresults]) return True else: self.detailedresults = package + " pkg not found" self.logger.log(LogPriority.INFO, ["Solaris.check", self.detailedresults]) return False except (KeyboardInterrupt, SystemExit): raise except Exception as err: #print err self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.INFO, ["Solaris.check", self.detailedresults]) ############################################################################### def checkAvailable(self, package): pass ############################################################################### def getInstall(self): return self.install ############################################################################### def getRemove(self): return self.remove
class Yum(object): """The template class that provides a framework that must be implemented by all platform specific pkgmgr classes. """ def __init__(self, logger): self.environ = Environment() self.logger = logger self.ch = CommandHelper(self.logger) self.yumloc = "/usr/bin/yum" self.install = self.yumloc + " install -y " self.remove = self.yumloc + " remove -y " self.search = self.yumloc + " list " self.checkupdates = self.search + "updates " self.listavail = self.search + "available " self.listinstalled = self.search + "installed " self.updatepkg = self.yumloc + " update -y --obsoletes " myos = self.environ.getostype().lower() if re.search("red hat.*?release 6", myos) or \ re.search("^centos$", myos.strip()): self.rpmloc = "/bin/rpm" else: self.rpmloc = "/usr/bin/rpm" self.provides = self.rpmloc + " -qf " self.query = self.rpmloc + " -qa " def installpackage(self, package): """Install a package. Return a bool indicating success or failure. :param package: string; Name of the package to be installed, must be recognizable to the underlying package manager. :return: installed :rtype: bool """ installed = True maxtries = 12 trynum = 0 while psRunning("yum"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to install package due to yum package manager being in-use by another process.") installed = False return installed else: self.logger.log(LogPriority.DEBUG, "Yum package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: self.ch.executeCommand(self.install + package) retcode = self.ch.getReturnCode() if retcode != 0: errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, "Yum command failed with: " + str(errstr)) installed = False if installed: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " was installed successfully") else: self.logger.log(LogPriority.DEBUG, "Failed to install package " + str(package)) except Exception: raise return installed def removepackage(self, package): """Remove a package. Return a bool indicating success or failure. :param package: string; Name of the package to be removed, must be recognizable to the underlying package manager. :return: removed :rtype: bool """ removed = True maxtries = 12 trynum = 0 while psRunning("yum"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to remove package, due to yum package manager being in-use by another process.") removed = False return removed else: self.logger.log(LogPriority.DEBUG, "Yum package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: self.ch.executeCommand(self.remove + package) retcode = self.ch.getReturnCode() if retcode != 0: errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, "Yum command failed with: " + str(errstr)) removed = False if removed: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " was successfully removed") else: self.logger.log(LogPriority.DEBUG, "Failed to remove package " + str(package)) except Exception: raise return removed def checkInstall(self, package): """Check the installation status of a package. Return a bool; True if the package is installed. :param package: string; Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. :return: found :rtype: bool """ installed = True maxtries = 12 trynum = 0 acceptablecodes = [1,0] while psRunning("yum"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to check status of package, due to yum package manager being in-use by another process.") installed = False return installed else: self.logger.log(LogPriority.DEBUG, "Yum package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: self.ch.executeCommand(self.listinstalled + package) retcode = self.ch.getReturnCode() if retcode not in acceptablecodes: installed = False errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, "Yum command failed with: " + str(errmsg)) else: outputlines = self.ch.getAllList() for line in outputlines: if re.search("No matching Packages", line, re.I): installed = False if installed: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is installed") else: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is NOT installed") except Exception: raise return installed def Update(self, package=""): """update specified package if any updates are available for it if no package is specified, update all packages which can be updated on the system :param package: string; name of package to update (Default value = "") :return: updated :rtype: bool """ updated = True try: try: self.ch.executeCommand(self.updatepkg + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: updated = False raise repoError('yum', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) updated = False if package: if updated: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " was successfully updated") else: self.logger.log(LogPriority.DEBUG, "No updates were found for package " + str(package)) else: if updated: self.logger.log(LogPriority.DEBUG, "All packages were successfully updated") else: self.logger.log(LogPriority.DEBUG, "No updates were found for this system") except Exception: raise return updated def checkUpdate(self, package=""): """check if there are any updates available for specified package if no package is specified, check if any updates are available for the current system :param package: string; name of package to check (Default value = "") :return: updatesavail :rtype: bool """ updatesavail = False try: try: self.ch.executeCommand(self.checkupdates + package) retcode = self.ch.getReturnCode() output = self.ch.getOutputString() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('yum', retcode, str(errstr)) else: if re.search("Updated packages", output, re.IGNORECASE): updatesavail = True except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) else: if re.search("Updated packages", output, re.IGNORECASE): updatesavail = True if package: if updatesavail: self.logger.log(LogPriority.DEBUG, "Updates are available for package " + str(package)) else: self.logger.log(LogPriority.DEBUG, "No updates are available for package " + str(package)) else: if updatesavail: self.logger.log(LogPriority.DEBUG, "Updates are available for this system") else: self.logger.log(LogPriority.DEBUG, "No updates are available for this system") except Exception: raise return updatesavail def checkAvailable(self, package): """check if specified package is available to install return True if it is return False if not :param package: string; name of package to check :return: available :rtype: bool """ available = True maxtries = 12 trynum = 0 while psRunning("yum"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to check availability of package, due to yum package manager being in-use by another process.") available = False return available else: self.logger.log(LogPriority.DEBUG, "Yum package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: self.ch.executeCommand(self.listavail + package) retcode = self.ch.getReturnCode() if retcode in [0,1]: output = self.ch.getAllList() for line in output: if re.search("No matching Packages", line, re.I): available = False else: errstr = self.ch.getErrorString() available = False self.logger.log(LogPriority.DEBUG, errstr) if available: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is available to install") else: self.logger.log(LogPriority.DEBUG, "No package " + str(package) + " was found to install") except Exception: raise return available def getPackageFromFile(self, filename): """Returns the name of the package that provides the given filename/path. :param filename: string; The name or path of the file to resolve :return: packagename :rtype: string """ packagename = "" try: try: self.ch.executeCommand(self.provides + filename) retcode = self.ch.getReturnCode() outputstr = self.ch.getOutputString() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('yum', retcode, str(errstr)) else: packagename = outputstr except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) except Exception: raise return packagename def getInstall(self): return self.install def getRemove(self): return self.remove
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
class AptGet(object): """Linux specific package manager for distributions that use the apt-get command to install packages. @author: Derek T Walker @change: 2012/08/06 Derek Walker - Original Implementation @change: 2015/08/20 eball - Added getPackageFromFile @change: 2017/04/27 Breen Malmberg - added two methods checkUpdate and Update; fixed doc string formatting; removed detailedresults reset in init; replaced with --force-yes flag with --assume-yes (from the man page for apt-get: Force yes. This is a dangerous option that will cause apt-get to continue without prompting if it is doing something potentially harmful. It should not be used except in very special situations. Using --force-yes can potentially destroy your system!) @change: 2017/08/16 bgonz12 - Added DEBIAN_FRONTEND=noninteractive env var to remove function @change: 2017/10/18 Breen Malmberg - changed class var names to be more self-explanatory; changed command to check whether there are available packages to use the canonical debian/ubuntu method; added calls to repoError exception to determine exact nature and cause of any errors with querying or calling repositories on the system (this adds logging of the nature and cause(s) as well); changed log messaging to be more consistent in style/format; removed calls to validateParam due to concerns about the stability and reliability of that method """ def __init__(self, logger): self.logger = logger self.ch = CommandHelper(self.logger) self.aptgetloc = "/usr/bin/apt-get" self.aptcacheloc = "/usr/bin/apt-cache" self.dpkgloc = "/usr/bin/dpkg" self.aptinstall = "DEBIAN_FRONTEND=noninteractive " + self.aptgetloc + " -y --assume-yes install " self.aptremove = "DEBIAN_FRONTEND=noninteractive " + self.aptgetloc + " -y remove " self.aptchkupdates = self.aptgetloc + " list --upgradeable " self.aptupgrade = self.aptgetloc + " -u upgrade --assume-yes " self.checkinstalled = "/usr/bin/apt list --installed " self.checkavailable = "/usr/bin/apt-cache search --names-only " self.findpkgforfilename = "/usr/bin/dpkg -S " self.pkgerrors = [1,100] def installpackage(self, package): """Install a package. Return a bool indicating success or failure. :param package: string; Name of the package to be installed, must be recognizable to the underlying package manager. :returns: installed :rtype: bool @author: Derek Walker @change: Breen Malmberg - 4/27/2017 - fixed doc string formatting; method now returns a variable; parameter validation added detailedresults replaced with logging @change: Breen Malmberg - 10/1/2018 - added check for package manager lock and retry loop """ installed = True maxtries = 12 trynum = 0 pslist = ["apt", "apt-get", "dpkg"] if type(package) is bytes: package = package.decode('utf-8') for ps in pslist: while psRunning(ps): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to install package, due to Apt package manager being in-use by another process.") installed = False return installed else: self.logger.log(LogPriority.DEBUG, "Apt package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: self.ch.executeCommand(self.aptinstall + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() # recursive call to this method if package manager is still locked if re.search("Could not get lock", errstr, re.I): self.logger.log(LogPriority.DEBUG, "Apt package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) return self.installpackage(package) elif retcode in self.pkgerrors: installed = False self.logger.log(LogPriority.DEBUG, str(errstr)) if installed: self.logger.log(LogPriority.DEBUG, "Successfully installed package " + str(package)) else: self.logger.log(LogPriority.DEBUG, "Failed to install package " + str(package)) except Exception: raise return installed def removepackage(self, package): """Remove a package. Return a bool indicating success or failure. :param package: string; Name of the package to be removed, must be recognizable to the underlying package manager. :returns: removed :rtype: bool @author: Derek T. Walker """ removed = True maxtries = 12 trynum = 0 pslist = ["apt", "apt-get", "dpkg"] if type(package) is bytes: package = package.decode('utf-8') for ps in pslist: while psRunning(ps): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to remove package, due to Apt package manager being in-use by another process.") removed = False return removed else: self.logger.log(LogPriority.DEBUG, "Apt package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: self.ch.executeCommand(self.aptremove + package) retcode = self.ch.getReturnCode() if retcode in self.pkgerrors: errstr = self.ch.getErrorString() removed = False self.logger.log(LogPriority.DEBUG, str(errstr)) if removed: self.logger.log(LogPriority.DEBUG, "Successfully removed package " + str(package)) else: self.logger.log(LogPriority.DEBUG, "Failed to remove package " + str(package)) except Exception: raise return removed def checkInstall(self, package): """Check the installation status of a package. Return a bool; True if the package is installed. :param package: :returns: installed :rtype: bool @author: Derek Walker @change: Breen Malmberg - 4/27/2017 - fixed doc string formatting; method now returns a variable; replaced detailedresults with logging """ installed = False maxtries = 12 trynum = 0 pslist = ["apt", "apt-get", "dpkg"] if type(package) is bytes: package = package.decode('utf-8') for ps in pslist: while psRunning(ps): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to check status of package, due to Apt package manager being in-use by another process.") installed = False return installed else: self.logger.log(LogPriority.DEBUG, "Apt package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: self.ch.executeCommand(self.checkinstalled + package) retcode = self.ch.getReturnCode() outputstr = self.ch.getOutputString() if retcode in self.pkgerrors: errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, str(errstr)) if re.search(package + ".*installed", outputstr, re.I): installed = True if not installed: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is NOT installed") else: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is installed") except Exception: raise return installed def checkAvailable(self, package): """check if a given package is available :param package: string; Name of package to check :returns: found :rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/27/2017 - created doc string; pulled result logging out of conditional """ found = False outputstr = "" maxtries = 12 trynum = 0 pslist = ["apt", "apt-get", "dpkg"] if type(package) is bytes: package = package.decode('utf-8') for ps in pslist: while psRunning(ps): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to check availability of package, due to apt package manager being in-use by another process.") available = False return available else: self.logger.log(LogPriority.DEBUG, "apt package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: self.ch.executeCommand(self.checkavailable + "^" + package + "$") retcode = self.ch.getReturnCode() if retcode in self.pkgerrors: errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) else: outputstr = self.ch.getOutputString() if re.search("^" + package, outputstr, re.I): found = True if found: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is available to install") else: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is NOT available to install") except Exception: raise return found def Update(self, package=""): """update the specified package if any updates are available for it if no package is specified, apply all available updates for the system :param package: string; (OPTIONAL) name of package to update (Default value = "") :returns: updated :rtype: bool @author: Breen Malmberg """ updated = True try: if type(package) is bytes: package = package.decode('utf-8') self.ch.executeCommand(self.aptupgrade + package) retcode = self.ch.getReturnCode() if retcode in self.pkgerrors: errstr = self.ch.getErrorString() updated = False self.logger.log(LogPriority.DEBUG, errstr) if package: if updated: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " was updated successfully") else: self.logger.log(LogPriority.DEBUG, "Failed to apply updates to package " + str(package)) else: if updated: self.logger.log(LogPriority.DEBUG, "All updates were installed successfully") else: self.logger.log(LogPriority.DEBUG, "Failed to apply updates") except Exception: raise return updated def checkUpdate(self, package=""): """check for updates for specified package if no package is specified, then check for updates for the entire system :param package: string; (OPTIONAL) Name of package to check (Default value = "") :returns: updatesavail :rtype: bool @author: Breen Malmberg """ updatesavail = False try: self.ch.executeCommand(self.aptchkupdates + package) retcode = self.ch.getReturnCode() if retcode in self.pkgerrors: errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) else: outputstr = self.ch.getOutputString() if re.search("upgradable", outputstr, re.I): updatesavail = True if package: if updatesavail: self.logger.log(LogPriority.DEBUG, "Updates are available for package " + str(package)) else: self.logger.log(LogPriority.DEBUG, "No updates are available for package " + str(package)) else: if updatesavail: self.logger.log(LogPriority.DEBUG, "Updates are available for this system") else: self.logger.log(LogPriority.DEBUG, "No updates are available for this system") except Exception: raise return updatesavail def getPackageFromFile(self, filename): """Returns the name of the package that provides the given filename/path. :param filename: :returns: packagename :rtype: string @author: Eric Ball @change: Breen Malmberg - 4/17/2017 - fixed doc string formatting; method now returns a variable; added param validation """ packagename = "" try: try: self.ch.executeCommand(self.findpkgforfilename + filename) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('apt', retcode, str(errstr)) if self.ch.getReturnCode() == 0: output = self.ch.getOutputString() packagename = output.split(":")[0] except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) pass except Exception: raise return packagename def getInstall(self): return self.aptinstall def getRemove(self): return self.aptremove
class SHupdaterc(ServiceHelperTemplate): """SHupdaterc is the Service Helper for systems using the rcupdate command to configure services. (Debian, Ubuntu and variants) """ def __init__(self, environment, logdispatcher): """ Constructor """ super(SHupdaterc, self).__init__(environment, logdispatcher) self.environment = environment self.logdispatcher = logdispatcher self.ch = CommandHelper(self.logdispatcher) self.updaterc = "/usr/sbin/update-rc.d " self.svc = "/usr/sbin/service " def disableService(self, service, **kwargs): """Disables the service and terminates it if it is running. :param service: string; name of service :param kwargs: dict; dictionary of key-value arguments :param **kwargs: :returns: enabled :rtype: bool @author: ??? """ disabled = True self.logdispatcher.log(LogPriority.DEBUG, "Disabling service: " + service) self.ch.executeCommand(self.updaterc + service + " disable") retcode = self.ch.getReturnCode() if retcode != 0: errmsg = self.ch.getErrorString() self.logdispatcher.log(LogPriority.DEBUG, errmsg) disabled = False else: if self.auditService(service): disabled = False if disabled: self.logdispatcher.log(LogPriority.DEBUG, "Successfully disabled service: " + service) return disabled def enableService(self, service, **kwargs): """Enables a service and starts it if it is not running as long as we are not in install mode :param service: string; name of service :param kwargs: dict; dictionary of key-value arguments :param **kwargs: :returns: enabled :rtype: bool @author: ??? """ enabled = True self.logdispatcher.log(LogPriority.DEBUG, "Enabling service: " + service) self.ch.executeCommand(self.updaterc + service + " enable") retcode = self.ch.getReturnCode() if retcode != 0: errmsg = self.ch.getErrorString() self.logdispatcher.log(LogPriority.DEBUG, errmsg) enabled = False else: if not self.auditService(service): enabled = False if enabled: self.logdispatcher.log(LogPriority.DEBUG, "Successfully enabled service: " + service) return enabled def auditService(self, service, **kwargs): """Checks all /etc/rc*.d/ directories for the "S" (start) service entry in updaterc, if an "S" entry with the service name exists in any of the rc*.d/ directories, it means that the service is scheduled to start at boot :param service: string; name of service :param kwargs: dict; dictionary of key-value arguments :param **kwargs: :returns: enabled :rtype: bool @author: ??? """ enabled = False self.logdispatcher.log( LogPriority.DEBUG, "Checking if service: " + service + " is enabled") self.ch.executeCommand("ls -l /etc/rc*.d/") if self.ch.findInOutput("S[0-9]+" + service): enabled = True if enabled: self.logdispatcher.log(LogPriority.DEBUG, "Service: " + service + " is enabled") else: self.logdispatcher.log(LogPriority.DEBUG, "Service: " + service + " is disabled") return enabled def isRunning(self, service, **kwargs): """Check to see if a service is currently running. :param service: string; name of service :param kwargs: dict; dictionary of key-value arguments :param **kwargs: :returns: enabled :rtype: bool @author: ??? """ running = False self.logdispatcher.log( LogPriority.DEBUG, "Checking if service: " + service + " is running") self.ch.executeCommand(self.svc + "--status-all") if self.ch.findInOutput("\[\s+\+\s+\]\s+" + service): running = True if running: self.logdispatcher.log(LogPriority.DEBUG, "Service: " + service + " IS running") else: self.logdispatcher.log(LogPriority.DEBUG, "Service: " + service + " is NOT running") return running def reloadService(self, service, **kwargs): """Reload (HUP) a service so that it re-reads it's config files. Called by rules that are configuring a service to make the new configuration active. :param service: string; name of service :param kwargs: dict; dictionary of key-value arguments :param **kwargs: :returns: enabled :rtype: bool @author: ??? """ reloaded = True self.logdispatcher.log(LogPriority.DEBUG, "Reloading service: " + service) self.ch.executeCommand(self.svc + service + " stop") self.ch.executeCommand(self.svc + service + " start") retcode = self.ch.getReturnCode() if retcode != 0: reloaded = False errmsg = self.ch.getErrorString() self.logdispatcher.log(LogPriority.DEBUG, errmsg) else: if not self.isRunning(service): reloaded = False self.logdispatcher.log(LogPriority.DEBUG, "Failed to reload service: " + service) return reloaded def listServices(self, **kwargs): """Return a list containing strings that are service names. :param kwargs: dict; dictionary of key-value arguments :param **kwargs: :returns: enabled :rtype: bool @author: ??? """ services = [] self.logdispatcher.log(LogPriority.DEBUG, "Fetching list of services") self.ch.executeCommand(self.svc + "--status-all") output = self.ch.getOutput() for line in output: if re.search("^\[", line): try: services.append(line.split("]")[1].strip()) except (IndexError, AttributeError): continue return services def getStartCommand(self, service): """retrieve the start command. Mostly used by event recording :param service: :returns: string - start command @author: Derek Walker """ return self.svc + service + ' start' def getStopCommand(self, service): """retrieve the stop command. Mostly used by event recording :param service: :returns: string - stop command @author: Derek Walker """ return self.svc + service + ' stop' def getEnableCommand(self, service): """retrieve the enable command. Mostly used by event recording :param service: :returns: string - enable command @author: Derek Walker """ return self.updaterc + service + ' enable' def getDisableCommand(self, service): """retrieve the start command. Mostly used by event recording :param service: :returns: string - disable command @author: Derek Walker """ return self.updaterc + service + ' disable'
class Dnf(object): '''The template class that provides a framework that must be implemented by all platform specific pkgmgr classes. Specifically for Fedora :version: @author: Derek T Walker 08-13-2015 @change: Breen Malmberg - 4/18/2017 - refactor of multiple methods; removed detailedresults reset in __init__; added the -q (non-interactive) flag to install and remove command var's; added a dnf info command var; added parameter validation to each method ''' def __init__(self, logger): self.logger = logger self.ch = CommandHelper(self.logger) self.dnfloc = "/usr/bin/dnf" self.install = self.dnfloc + " install -yq " self.remove = self.dnfloc + " remove -yq " self.search = self.dnfloc + " search " self.checkinstalled = self.dnfloc + " list --installed " self.chavailable = self.dnfloc + " list --available " self.checkupdate = self.dnfloc + " check-update " self.rpm = "/bin/rpm -qf " self.updatepackage = self.dnfloc + " -yq upgrade " self.lockfiles = [ "/var/run/dnf.lock", "/var/run/dnf.pid", "/run/dnf.lock", "/run/dnf.pid" ] def installpackage(self, package): '''Install a package. Return a bool indicating success or failure. :param package: string; Name of the package to be installed, must be recognizable to the underlying package manager. :returns: installed :rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/18/2017 - refactored method; added logging; replaced detailedresults with logging @change: Breen Malmberg - 10/1/2018 - added check for package manager lock and retry loop ''' installed = True maxtries = 12 trynum = 0 while psRunning("dnf"): trynum += 1 if trynum == maxtries: self.logger.log( LogPriority.DEBUG, "Timed out while attempting to install package, due to dnf package manager being in-use by another process." ) installed = False return installed else: self.logger.log( LogPriority.DEBUG, "dnf package manager is in-use by another process. Waiting for it to be freed..." ) time.sleep(5) try: try: self.ch.executeCommand(self.install + package) retcode = self.ch.getReturnCode() if retcode != 0: raise repoError('dnf', retcode) except repoError as repoerr: if not repoerr.success: installed = False if installed: self.logger.log( LogPriority.DEBUG, "Successfully installed package " + str(package)) else: self.logger.log(LogPriority.DEBUG, "Failed to install package " + str(package)) except Exception: raise return installed def removepackage(self, package): '''Remove a package. Return a bool indicating success or failure. :param package: string; Name of the package to be removed, must be recognizable to the underlying package manager. :returns: removed :rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/18/2017 ''' removed = True maxtries = 12 trynum = 0 while psRunning("dnf"): trynum += 1 if trynum == maxtries: self.logger.log( LogPriority.DEBUG, "Timed out while attempting to remove package, due to dnf package manager being in-use by another process." ) installed = False return installed else: self.logger.log( LogPriority.DEBUG, "dnf package manager is in-use by another process. Waiting for it to be freed..." ) time.sleep(5) try: try: self.ch.executeCommand(self.remove + package) retcode = self.ch.getReturnCode() if retcode != 0: raise repoError('dnf', retcode) except repoError as repoerr: if not repoerr.success: removed = False if removed: self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " was successfully removed") else: self.logger.log(LogPriority.DEBUG, "Failed to remove package " + str(package)) except Exception: raise return removed def checkInstall(self, package): '''Check the installation status of a package. Return a bool; True if the package is installed. :param package: :returns: installed :rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/18/2017 ''' installed = False errstr = "" outputstr = "" maxtries = 12 trynum = 0 while psRunning("dnf"): trynum += 1 if trynum == maxtries: self.logger.log( LogPriority.DEBUG, "Timed out while attempting to check status of package, due to dnf package manager being in-use by another process." ) installed = False return installed else: self.logger.log( LogPriority.DEBUG, "dnf package manager is in-use by another process. Waiting for it to be freed..." ) time.sleep(5) try: try: # There is no dnf search command which will only return an # "installed" result set. Therefore we must parse the output # to determine if the package is installed or just available. # The below command string will produce stdout with only the # installed result set of packages self.ch.executeCommand(self.checkinstalled + package + " | grep -iA 1 installed") retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() outputstr = self.ch.getOutputString() # With this command specifically, in this package manager, we # can't count exit code 1 as being an error because the check installed # command (with dnf) will return an error (1) exit code if no results are # returned, even if there is no error. We also can't use error or output strings # to parse because it is possible for this command to also return no output of any # kind, in addition to returning a 1 exit code... Therefore we must exempt exit # code 1 for this command specifically... if retcode != 0 | 1: raise repoError('dnf', retcode, str(errstr)) else: if re.search(package, outputstr, re.IGNORECASE): installed = True except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) installed = False else: if re.search(package, outputstr, re.IGNORECASE): installed = True if installed: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is installed") else: self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " is NOT installed") except Exception: raise return installed def checkAvailable(self, package): '''check if the given package is available to install :param package: string; name of package to check for :returns: found :rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/18/2017 - added doc string; refactor of method ''' found = False maxtries = 12 trynum = 0 while psRunning("dnf"): trynum += 1 if trynum == maxtries: self.logger.log( LogPriority.DEBUG, "Timed out while attempting to check availability of package, due to dnf package manager being in-use by another process." ) found = False return found else: self.logger.log( LogPriority.DEBUG, "dnf package manager is in-use by another process. Waiting for it to be freed..." ) time.sleep(5) try: try: self.ch.executeCommand(self.chavailable + package) retcode = self.ch.getReturnCode() outputstr = self.ch.getOutputString() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('dnf', retcode, str(errstr)) else: if re.search(package, outputstr, re.IGNORECASE): found = True except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) else: if re.search(package, outputstr, re.IGNORECASE): found = True if found: self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " is available to install") else: self.logger.log( LogPriority.DEBUG, "No package " + str(package) + " available to install") except Exception: raise return found def checkUpdate(self, package=""): '''check the specified package for updates if no package is specified, check for all/any updates on the system return True if there are updates available return False if there are no updates available :param package: string; name of package to check (Default value = "") :returns: updatesavail :rtype: bool @author: Breen Malmberg ''' updatesavail = False try: try: self.ch.executeCommand(self.checkupdate + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('dnf', retcode) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) return False else: updatesavail = True if package: if updatesavail: self.logger.log(LogPriority.DEBUG, "Updates are available") else: self.logger.log(LogPriority.DEBUG, "No updates are available") else: if updatesavail: self.logger.log( LogPriority.DEBUG, "Updates are available for package " + str(package)) else: self.logger.log( LogPriority.DEBUG, "No updates are available for package " + str(package)) except Exception: raise return updatesavail def Update(self, package=""): '''update the specified package if no package is specified, update all packages on the system :param package: string; name of package to update (Default value = "") :returns: updated :rtype: bool @author: Breen Malmberg ''' updated = True try: try: self.ch.executeCommand(self.updatepackage + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('dnf', retcode) else: updated = True except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) return False else: updated = True if package: if updated: self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " successfully updated") else: self.logger.log( LogPriority.DEBUG, "Package " + str(package) + " was NOT updated") else: if updated: self.logger.log(LogPriority.DEBUG, "All packages updated successfully") else: self.logger.log( LogPriority.DEBUG, "One or more packages failed to update properly") except Exception: raise return updated def getPackageFromFile(self, filename): '''return a string with the name of the parent package in it :param filename: :returns: packagename :rtype: string @author: Eric Ball @change: Breen Malmberg - 4/18/2017 - fixed doc string; refactored method ''' packagename = "" try: try: self.ch.executeCommand(self.rpm + filename) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() outputstr = self.ch.getOutputString() if retcode != 0: raise repoError('dnf', retcode, str(errstr)) else: packagename = outputstr except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) else: packagename = outputstr except Exception: raise return packagename def getInstall(self): return self.install def getRemove(self): return self.remove def getSearch(self): return self.search def getInfo(self): return self.info def getCheck(self): return self.checkupdate