def add_to_keychain_list(keychain_path): '''Ensure the keychain is in the search path. Returns True if we added the keychain to the list.''' # we use *foo to expand a list of keychain paths # pylint: disable=W0142 added_keychain = False output = security('list-keychains', '-d', 'user') # Split the output and strip it of whitespace and leading/trailing # quotes, the result are absolute paths to keychains # Preserve the order in case we need to append to them search_keychains = [x.strip().strip('"') for x in output.split('\n') if x.strip()] if not keychain_path in search_keychains: # Keychain is not in the search paths munkicommon.display_debug2('Adding client keychain to search path...') search_keychains.append(keychain_path) try: output = security( 'list-keychains', '-d', 'user', '-s', *search_keychains) if output: munkicommon.display_debug2(output) added_keychain = True except SecurityError, err: munkicommon.display_error( 'Could not add keychain %s to keychain list: %s', keychain_path, err) added_keychain = False
def read_signed_profile(profile_path): '''Attempts to read a (presumably) signed profile.''' # filed for future reference: # openssl smime -inform DER -verify -in Signed.mobileconfig # -noverify -out Unsigned.mobileconfig # will strip the signing from a signed profile # this might be a better approach # from: http://apple.stackexchange.com/questions/105981/ # how-do-i-view-or-verify-signed-mobileconfig-files-using-terminal # but... we're going to use an Apple-provided tool instead. cmd = ['/usr/bin/security', 'cms', '-D', '-i', profile_path] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = proc.communicate() if proc.returncode: # security cms -D couldn't decode the file munkicommon.display_error( 'Error reading profile %s: %s' % (profile_path, stderr)) return {} try: return FoundationPlist.readPlistFromString(stdout) except FoundationPlist.NSPropertyListSerializationException, err: # not a valid plist munkicommon.display_error( 'Error reading profile %s: %s' % (profile_path, err)) return {}
def read_signed_profile(profile_path): '''Attempts to read a (presumably) signed profile.''' # filed for future reference: # openssl smime -inform DER -verify -in Signed.mobileconfig # -noverify -out Unsigned.mobileconfig # will strip the signing from a signed profile # this might be a better approach # from: https://apple.stackexchange.com/questions/105981/ # how-do-i-view-or-verify-signed-mobileconfig-files-using-terminal # but... we're going to use an Apple-provided tool instead. cmd = ['/usr/bin/security', 'cms', '-D', '-i', profile_path] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = proc.communicate() if proc.returncode: # security cms -D couldn't decode the file munkicommon.display_error('Error reading profile %s: %s' % (profile_path, stderr)) return {} try: return FoundationPlist.readPlistFromString(stdout) except FoundationPlist.NSPropertyListSerializationException, err: # not a valid plist munkicommon.display_error('Error reading profile %s: %s' % (profile_path, err)) return {}
def cacheAppleSUScatalog(): '''Caches a local copy of the current Apple SUS catalog.''' osvers = int(os.uname()[2].split('.')[0]) munkisuscatalog = munkicommon.pref('SoftwareUpdateServerURL') prefs_catalogURL = getSoftwareUpdatePref('CatalogURL') if munkisuscatalog: # defined in Munki's prefs? use that catalogURL = munkisuscatalog elif prefs_catalogURL: # defined via MCX or # in /Library/Preferences/com.apple.SoftwareUpdate.plist catalogURL = prefs_catalogURL elif osvers == 9: # default catalog for Leopard catalogURL = 'http://swscan.apple.com/content/catalogs/others/index-leopard.merged-1.sucatalog' elif osvers == 10: # default catalog for Snow Leopard catalogURL = 'http://swscan.apple.com/content/catalogs/others/index-leopard-snowleopard.merged-1.sucatalog' elif osvers == 11: # default catalog for Lion catalogURL = 'http://swscan.apple.com/content/catalogs/others/index-lion-snowleopard-leopard.merged-1.sucatalog.gz' else: munkicommon.display_error( 'Can\'t determine Software Update CatalogURL for Darwin ' 'version %s', osvers) return -1 if not os.path.exists(swupdCacheDir(temp=False)): try: os.makedirs(swupdCacheDir(temp=False)) except OSError, oserr: raise ReplicationError(oserr)
def remove_from_keychain_list(keychain_path): '''Remove keychain from the list of keychains''' # we use *foo to expand a list of keychain paths # pylint: disable=W0142 output = security('list-keychains', '-d', 'user') # Split the output and strip it of whitespace and leading/trailing # quotes, the result are absolute paths to keychains # Preserve the order in case we need to append to them search_keychains = [ x.strip().strip('"') for x in output.split('\n') if x.strip() ] if keychain_path in search_keychains: # Keychain is in the search path munkicommon.display_debug1('Removing %s from search path...', keychain_path) filtered_keychains = [ keychain for keychain in search_keychains if keychain != keychain_path ] try: output = security('list-keychains', '-d', 'user', '-s', *filtered_keychains) if output: munkicommon.display_debug2(output) except SecurityError, err: munkicommon.display_error('Could not set new keychain list: %s', err)
def doAdobeRemoval(item): '''Wrapper for all the Adobe removal methods''' uninstallmethod = item['uninstall_method'] itempath = "" if "uninstaller_item" in item: managedinstallbase = munkicommon.pref('ManagedInstallDir') itempath = os.path.join(managedinstallbase, 'Cache', item["uninstaller_item"]) if not os.path.exists(itempath): munkicommon.display_error("%s package for %s was " "missing from the cache." % (uninstallmethod, item['name'])) return -1 if uninstallmethod == "AdobeSetup": # CS3 uninstall retcode = runAdobeSetup(itempath, uninstalling=True) elif uninstallmethod == "AdobeUberUninstaller": # CS4 uninstall pkgname = item.get("adobe_package_name") or \ item.get("package_path","") retcode = runAdobeUberTool(itempath, pkgname, uninstalling=True) elif uninstallmethod == "AdobeCS5AAMEEPackage": # CS5 uninstall. Sheesh. Three releases, three methods. adobeInstallInfo = item.get('adobe_install_info') retcode = doAdobeCS5Uninstall(adobeInstallInfo) if retcode: munkicommon.display_error("Uninstall of %s failed." % item['name']) return retcode
def add_to_keychain_list(keychain_path): '''Ensure the keychain is in the search path. Returns True if we added the keychain to the list.''' # we use *foo to expand a list of keychain paths # pylint: disable=W0142 added_keychain = False output = security('list-keychains', '-d', 'user') # Split the output and strip it of whitespace and leading/trailing # quotes, the result are absolute paths to keychains # Preserve the order in case we need to append to them search_keychains = [ x.strip().strip('"') for x in output.split('\n') if x.strip() ] if not keychain_path in search_keychains: # Keychain is not in the search paths munkicommon.display_debug2('Adding client keychain to search path...') search_keychains.append(keychain_path) try: output = security('list-keychains', '-d', 'user', '-s', *search_keychains) if output: munkicommon.display_debug2(output) added_keychain = True except SecurityError, err: munkicommon.display_error( 'Could not add keychain %s to keychain list: %s', keychain_path, err) added_keychain = False
def remove_from_keychain_list(keychain_path): '''Remove keychain from the list of keychains''' # we use *foo to expand a list of keychain paths # pylint: disable=W0142 output = security('list-keychains', '-d', 'user') # Split the output and strip it of whitespace and leading/trailing # quotes, the result are absolute paths to keychains # Preserve the order in case we need to append to them search_keychains = [x.strip().strip('"') for x in output.split('\n') if x.strip()] if keychain_path in search_keychains: # Keychain is in the search path munkicommon.display_debug1( 'Removing %s from search path...', keychain_path) filtered_keychains = [keychain for keychain in search_keychains if keychain != keychain_path] try: output = security( 'list-keychains', '-d', 'user', '-s', *filtered_keychains) if output: munkicommon.display_debug2(output) except SecurityError, err: munkicommon.display_error( 'Could not set new keychain list: %s', err)
def downloadAvailableUpdates(): '''Downloads the available Apple updates using our local filtered sucatalog. Returns True if successful, False otherwise.''' msg = "Downloading available Apple Software Updates..." if munkicommon.munkistatusoutput: munkistatus.message(msg) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(msg) else: munkicommon.display_status(msg) # use our filtered local catalog catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/local_download.sucatalog') if not os.path.exists(catalogpath): munkicommon.display_error( 'Missing local Software Update catalog at %s', catalogpath) return False catalogURL = 'file://localhost' + urllib2.quote(catalogpath) # get the OS version osvers = int(os.uname()[2].split('.')[0]) if osvers == 9: retcode = leopardDownloadAvailableUpdates(catalogURL) else: retcode = run_softwareupdate(['--CatalogURL', catalogURL, '-d', '-a']) if retcode: # there was an error munkicommon.display_error("softwareupdate error: %s" % retcode) return False return True
def config_profile_info(ignore_cache=False): '''Returns a dictionary representing the output of `profiles -C -o`''' global CONFIG_PROFILE_INFO if not profiles_supported(): CONFIG_PROFILE_INFO = {} return CONFIG_PROFILE_INFO if not ignore_cache and CONFIG_PROFILE_INFO is not None: return CONFIG_PROFILE_INFO output_plist = os.path.join( tempfile.mkdtemp(dir=munkicommon.tmpdir()), 'profiles') cmd = ['/usr/bin/profiles', '-C', '-o', output_plist] proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.communicate() if proc.returncode != 0: munkicommon.display_error( 'Could not obtain configuration profile info: %s' % proc.stderr) CONFIG_PROFILE_INFO = {} else: try: CONFIG_PROFILE_INFO = FoundationPlist.readPlist( output_plist + '.plist') except BaseException, err: munkicommon.display_error( 'Could not read configuration profile info: %s' % err) CONFIG_PROFILE_INFO = {} finally:
def getpkgkeys(pkgnames): """ Given a list of receipt names, bom file names, or package ids, gets a list of pkg_keys from the pkgs table in our database. """ # open connection and cursor to our database conn = sqlite3.connect(packagedb) curs = conn.cursor() # check package names to make sure they're all in the database, # build our list of pkg_keys pkgerror = False pkgkeyslist = [] for pkg in pkgnames: values_t = (pkg,) munkicommon.display_debug1("select pkg_key from pkgs where pkgid = %s", pkg) pkg_keys = curs.execute("select pkg_key from pkgs where pkgid = ?", values_t).fetchall() if not pkg_keys: # try pkgname munkicommon.display_debug1("select pkg_key from pkgs where pkgname = %s", pkg) pkg_keys = curs.execute("select pkg_key from pkgs where pkgname = ?", values_t).fetchall() if not pkg_keys: munkicommon.display_error("%s not found in database.", pkg) pkgerror = True else: for row in pkg_keys: # only want first column pkgkeyslist.append(row[0]) if pkgerror: pkgkeyslist = [] curs.close() conn.close() munkicommon.display_debug1("pkgkeys: %s", pkgkeyslist) return pkgkeyslist
def config_profile_info(ignore_cache=False): '''Returns a dictionary representing the output of `profiles -C -o`''' global CONFIG_PROFILE_INFO if not profiles_supported(): CONFIG_PROFILE_INFO = {} return CONFIG_PROFILE_INFO if not ignore_cache and CONFIG_PROFILE_INFO is not None: return CONFIG_PROFILE_INFO output_plist = os.path.join(tempfile.mkdtemp(dir=munkicommon.tmpdir()), 'profiles') cmd = ['/usr/bin/profiles', '-C', '-o', output_plist] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.communicate() if proc.returncode != 0: munkicommon.display_error( 'Could not obtain configuration profile info: %s' % proc.stderr) CONFIG_PROFILE_INFO = {} else: try: CONFIG_PROFILE_INFO = FoundationPlist.readPlist(output_plist + '.plist') except BaseException, err: munkicommon.display_error( 'Could not read configuration profile info: %s' % err) CONFIG_PROFILE_INFO = {} finally:
def pem_cert_sha1_digest(cert_path): '''Return SHA1 digest for pem certificate at path''' try: raw_bytes = pem_cert_bytes(cert_path) return hashlib.sha1(raw_bytes).hexdigest().upper() except BaseException, err: munkicommon.display_error('Error reading %s: %s' % (cert_path, err)) return None
def read_file(pathname): '''Return the contents of pathname as a string''' try: fileobj = open(pathname, mode='r') data = fileobj.read() fileobj.close() return data except (OSError, IOError), err: munkicommon.display_error('Could not read %s: %s', pathname, err) return ''
def read_file(pathname): '''Return the contents of pathname as a string''' try: fileobj = open(pathname, mode='r') data = fileobj.read() fileobj.close() return data except (OSError, IOError), err: munkicommon.display_error( 'Could not read %s: %s', pathname, err) return ''
def read_profile(profile_path): '''Reads a profile.''' try: return FoundationPlist.readPlist(profile_path) except FoundationPlist.NSPropertyListSerializationException: # possibly a signed profile return read_signed_profile(profile_path) except BaseException, err: munkicommon.display_error( 'Error reading profile %s: %s' % (profile_path, err)) return {}
def read_profile(profile_path): '''Reads a profile.''' try: return FoundationPlist.readPlist(profile_path) except FoundationPlist.NSPropertyListSerializationException: # possibly a signed profile return read_signed_profile(profile_path) except BaseException, err: munkicommon.display_error('Error reading profile %s: %s' % (profile_path, err)) return {}
def writefile(stringdata, path): '''Writes string data to path. Returns the path on success, empty string on failure.''' try: fileobject = open(path, mode='w', buffering=1) print >> fileobject, stringdata.encode('UTF-8') fileobject.close() return path except (OSError, IOError): munkicommon.display_error("Couldn't write %s" % stringdata) return ""
def write_file(stringdata, pathname): '''Writes stringdata to pathname. Returns the pathname on success, empty string on failure.''' try: fileobject = open(pathname, mode='w') fileobject.write(stringdata) fileobject.close() return pathname except (OSError, IOError), err: munkicommon.display_error('Couldn\'t write %s to %s: %s', stringdata, pathname, err) return ''
def removepackages( pkgnames, forcedeletebundles=False, listfiles=False, rebuildpkgdb=False, noremovereceipts=False, noupdateapplepkgdb=False, ): """ Our main function, called by installer.py to remove items based on receipt info. """ if pkgnames == []: munkicommon.display_error("You must specify at least one package to remove!") return -2 if not initDatabase(forcerebuild=rebuildpkgdb): munkicommon.display_error("Could not initialize receipt database.") return -3 pkgkeyslist = getpkgkeys(pkgnames) if len(pkgkeyslist) == 0: return -4 if munkicommon.stopRequested(): return -128 removalpaths = getpathstoremove(pkgkeyslist) if munkicommon.stopRequested(): return -128 if removalpaths: if listfiles: removalpaths.sort() for item in removalpaths: print "/" + item.encode("UTF-8") else: if munkicommon.munkistatusoutput: munkistatus.disableStopButton() removeFilesystemItems(removalpaths, forcedeletebundles) else: munkicommon.display_status_minor("Nothing to remove.") if munkicommon.munkistatusoutput: time.sleep(2) if not listfiles: if not noremovereceipts: removeReceipts(pkgkeyslist, noupdateapplepkgdb) if munkicommon.munkistatusoutput: munkistatus.enableStopButton() munkicommon.display_status_minor("Package removal complete.") time.sleep(2) return 0
def make_client_keychain(cert_info=None): '''Builds a client cert keychain from existing client certs''' if not cert_info: # just grab data from Munki's preferences/defaults cert_info = get_munki_client_cert_info() client_cert_path = cert_info['client_cert_path'] client_key_path = cert_info['client_key_path'] site_urls = cert_info['site_urls'] if not client_cert_path: # no client, so nothing to do munkicommon.display_debug1( 'No client cert info provided, ' 'so no client keychain will be created.') return else: munkicommon.display_debug1('Client cert path: %s', client_cert_path) munkicommon.display_debug1('Client key path: %s', client_key_path) # to do some of the following options correctly, we need to be root # and have root's home. # check to see if we're root if os.geteuid() != 0: munkicommon.display_error( 'Can\'t make our client keychain unless we are root!') return # switch HOME if needed to root's home original_home = os.environ.get('HOME') if original_home: os.environ['HOME'] = os.path.expanduser('~root') keychain_pass = ( munkicommon.pref('KeychainPassword') or DEFAULT_KEYCHAIN_PASSWORD) abs_keychain_path = get_keychain_path() if os.path.exists(abs_keychain_path): os.unlink(abs_keychain_path) if not os.path.exists(os.path.dirname(abs_keychain_path)): os.makedirs(os.path.dirname(abs_keychain_path)) # create a new keychain munkicommon.display_debug1('Creating client keychain...') try: output = security('create-keychain', '-p', keychain_pass, abs_keychain_path) if output: munkicommon.display_debug2(output) except SecurityError, err: munkicommon.display_error( 'Could not create keychain %s: %s', abs_keychain_path, err) if original_home: # switch it back os.environ['HOME'] = original_home return
def removepackages(pkgnames, forcedeletebundles=False, listfiles=False, rebuildpkgdb=False, noremovereceipts=False, noupdateapplepkgdb=False): """ Our main function, called by installer.py to remove items based on receipt info. """ if pkgnames == []: munkicommon.display_error( "You must specify at least one package to remove!") return -2 if not initDatabase(forcerebuild=rebuildpkgdb): munkicommon.display_error("Could not initialize receipt database.") return -3 pkgkeyslist = getpkgkeys(pkgnames) if len(pkgkeyslist) == 0: return -4 if munkicommon.stopRequested(): return -128 removalpaths = getpathstoremove(pkgkeyslist) if munkicommon.stopRequested(): return -128 if removalpaths: if listfiles: removalpaths.sort() for item in removalpaths: print "/" + item.encode('UTF-8') else: if munkicommon.munkistatusoutput: munkistatus.disableStopButton() removeFilesystemItems(removalpaths, forcedeletebundles) else: munkicommon.display_status_minor('Nothing to remove.') if munkicommon.munkistatusoutput: time.sleep(2) if not listfiles: if not noremovereceipts: removeReceipts(pkgkeyslist, noupdateapplepkgdb) if munkicommon.munkistatusoutput: munkistatus.enableStopButton() munkicommon.display_status_minor('Package removal complete.') time.sleep(2) return 0
def install_profile(profile_path): '''Installs a profile. Returns True on success, False otherwise''' cmd = ['/usr/bin/profiles', '-IF', profile_path] proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.communicate() if proc.returncode != 0: munkicommon.display_error( 'Profile %s installation failed: %s' % (os.path.basename(profile_path), proc.stderr)) return False record_profile_receipt(profile_path) return True
def remove_profile(identifier): '''Removes a profile with the given identifier. Returns True on success, False otherwise''' cmd = ['/usr/bin/profiles', '-Rp', identifier] proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.communicate() if proc.returncode != 0: munkicommon.display_error( 'Profile %s removal failed: %s' % (identifier, proc.stderr)) return False remove_profile_receipt(identifier) return True
def main(): '''Used when calling removepackages.py directly from the command line.''' # command-line options p = optparse.OptionParser() p.set_usage('''Usage: %prog [options] package_id ...''') p.add_option('--forcedeletebundles', '-f', action='store_true', help='Delete bundles even if they aren\'t empty.') p.add_option('--listfiles', '-l', action='store_true', help='''List the filesystem objects to be removed, but do not actually remove them.''') p.add_option('--rebuildpkgdb', action='store_true', help='Force a rebuild of the internal package database.') p.add_option('--noremovereceipts', action='store_true', help='''Do not remove receipts and boms from /Library/Receipts and update internal package database.''') p.add_option('--noupdateapplepkgdb', action='store_true', help='''Do not update Apple\'s package database. If --noremovereceipts is also given, this is implied''') p.add_option('--munkistatusoutput', '-m', action='store_true', help='Output is formatted for use with MunkiStatus.') p.add_option('--verbose', '-v', action='count', default=1, help='''More verbose output. May be specified multiple times.''') # Get our options and our package names options, pkgnames = p.parse_args() # check to see if we're root if os.geteuid() != 0: munkicommon.display_error("You must run this as root!") exit(-1) # set the munkicommon globals munkicommon.munkistatusoutput = options.munkistatusoutput munkicommon.verbose = options.verbose if options.munkistatusoutput: pkgcount = len(pkgnames) munkistatus.message("Removing %s packages..." % pkgcount) munkistatus.detail("") retcode = removepackages(pkgnames, forcedeletebundles=options.forcedeletebundles, listfiles=options.listfiles, rebuildpkgdb=options.rebuildpkgdb, noremovereceipts=options.noremovereceipts, noupdateapplepkgdb=options.noupdateapplepkgdb) if options.munkistatusoutput: munkistatus.quit() exit(retcode)
def main(): '''Used when calling removepackages.py directly from the command line.''' # command-line options parser = optparse.OptionParser() parser.set_usage('''Usage: %prog [options] package_id ...''') parser.add_option('--forcedeletebundles', '-f', action='store_true', help='Delete bundles even if they aren\'t empty.') parser.add_option('--listfiles', '-l', action='store_true', help='List the filesystem objects to be removed, ' 'but do not actually remove them.') parser.add_option('--rebuildpkgdb', action='store_true', help='Force a rebuild of the internal package database.') parser.add_option('--noremovereceipts', action='store_true', help='''Do not remove receipts and boms from /Library/Receipts and update internal package database.''') parser.add_option('--noupdateapplepkgdb', action='store_true', help='Do not update Apple\'s package database. ' 'If --noremovereceipts is also given, this is implied') parser.add_option('--munkistatusoutput', '-m', action='store_true', help='Output is formatted for use with MunkiStatus.') parser.add_option('--verbose', '-v', action='count', default=1, help='More verbose output. May be specified multiple ' 'times.') # Get our options and our package names options, pkgnames = parser.parse_args() # check to see if we're root if os.geteuid() != 0: munkicommon.display_error("You must run this as root!") exit(-1) # set the munkicommon globals munkicommon.munkistatusoutput = options.munkistatusoutput munkicommon.verbose = options.verbose if options.munkistatusoutput: pkgcount = len(pkgnames) munkistatus.message("Removing %s packages..." % pkgcount) munkistatus.detail("") retcode = removepackages(pkgnames, forcedeletebundles=options.forcedeletebundles, listfiles=options.listfiles, rebuildpkgdb=options.rebuildpkgdb, noremovereceipts=options.noremovereceipts, noupdateapplepkgdb=options.noupdateapplepkgdb) if options.munkistatusoutput: munkistatus.quit() exit(retcode)
def debug_output(): '''Debugging output for keychain''' try: munkicommon.display_debug1('***Keychain list***') munkicommon.display_debug1(security('list-keychains', '-d', 'user')) munkicommon.display_debug1('***Default keychain info***') munkicommon.display_debug1(security('default-keychain', '-d', 'user')) keychainfile = get_keychain_path() if os.path.exists(keychainfile): munkicommon.display_debug1('***Info for %s***' % keychainfile) munkicommon.display_debug1( security('show-keychain-info', keychainfile)) except SecurityError, err: munkicommon.display_error(err)
def remove_profile(identifier): '''Removes a profile with the given identifier. Returns True on success, False otherwise''' cmd = ['/usr/bin/profiles', '-Rp', identifier] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.communicate() if proc.returncode != 0: munkicommon.display_error('Profile %s removal failed: %s' % (identifier, proc.stderr)) return False remove_profile_receipt(identifier) return True
def debug_output(): '''Debugging output for keychain''' try: munkicommon.display_debug1('***Keychain list***') munkicommon.display_debug1(security('list-keychains', '-d', 'user')) munkicommon.display_debug1('***Default keychain info***') munkicommon.display_debug1(security('default-keychain', '-d', 'user')) keychainfile = get_keychain_path() if os.path.exists(keychainfile): munkicommon.display_debug1('***Info for %s***' % keychainfile) munkicommon.display_debug1( security('show-keychain-info', keychainfile)) except SecurityError, err: munkicommon.display_error(str(err))
def getAvailableUpdates(): '''Returns a list of product IDs of available Apple updates''' msg = "Checking for available Apple Software Updates..." if munkicommon.munkistatusoutput: munkistatus.message(msg) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(msg) else: munkicommon.display_status(msg) applicable_updates = os.path.join(swupdCacheDir(), 'ApplicableUpdates.plist') if os.path.exists(applicable_updates): # remove any old item try: os.unlink(applicable_updates) except (OSError, IOError): pass # use our locally-cached Apple catalog catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/apple_index.sucatalog') catalogURL = 'file://localhost' + urllib2.quote(catalogpath) su_options = ['--CatalogURL', catalogURL, '-l', '-f', applicable_updates] retcode = run_softwareupdate(su_options) if retcode: # there was an error osvers = int(os.uname()[2].split('.')[0]) if osvers == 9: # always a non-zero retcode on Leopard pass else: munkicommon.display_error("softwareupdate error: %s" % retcode) return [] if os.path.exists(applicable_updates): try: updatelist = FoundationPlist.readPlist(applicable_updates) if updatelist: results_array = updatelist.get('phaseResultsArray', []) return [ item['productKey'] for item in results_array if 'productKey' in item ] except FoundationPlist.NSPropertyListSerializationException: return [] return []
def __init__(self): '''Adds CA certs as trusted to System keychain. Unlocks the munki.keychain if it exists. Makes sure the munki.keychain is in the search list. Creates a new client keychain if needed.''' add_ca_certs_to_system_keychain() self.keychain_path = get_keychain_path() if client_certs_exist() and os.path.exists(self.keychain_path): # we have client certs; we should build a keychain using them try: os.unlink(self.keychain_path) except (OSError, IOError), err: munkicommon.display_error( 'Could not remove pre-existing %s: %s' % (self.keychain_path, err))
def getAvailableUpdates(): '''Returns a list of product IDs of available Apple updates''' msg = "Checking for available Apple Software Updates..." if munkicommon.munkistatusoutput: munkistatus.message(msg) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(msg) else: munkicommon.display_status(msg) applicable_updates = os.path.join(swupdCacheDir(), 'ApplicableUpdates.plist') if os.path.exists(applicable_updates): # remove any old item try: os.unlink(applicable_updates) except (OSError, IOError): pass # use our locally-cached Apple catalog catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/apple_index.sucatalog') catalogURL = 'file://localhost' + urllib2.quote(catalogpath) su_options = ['--CatalogURL', catalogURL, '-l', '-f', applicable_updates] retcode = run_softwareupdate(su_options) if retcode: # there was an error osvers = int(os.uname()[2].split('.')[0]) if osvers == 9: # always a non-zero retcode on Leopard pass else: munkicommon.display_error("softwareupdate error: %s" % retcode) return [] if os.path.exists(applicable_updates): try: updatelist = FoundationPlist.readPlist(applicable_updates) if updatelist: results_array = updatelist.get('phaseResultsArray', []) return [item['productKey'] for item in results_array if 'productKey' in item] except FoundationPlist.NSPropertyListSerializationException: return [] return []
def install_profile(profile_path, profile_identifier): '''Installs a profile. Returns True on success, False otherwise''' cmd = ['/usr/bin/profiles', '-IF', profile_path] proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.communicate() if proc.returncode != 0: munkicommon.display_error( 'Profile %s installation failed: %s' % (os.path.basename(profile_path), proc.stderr)) return False if profile_identifier: record_profile_receipt(profile_path, profile_identifier) else: munkicommon.display_warning( 'No identifier for profile %s; cannot record an installation ' 'receipt.' % os.path.basename(profile_path)) return True
def runAdobeUberTool(dmgpath, pkgname='', uninstalling=False): '''Runs either AdobeUberInstaller or AdobeUberUninstaller from a disk image and provides progress feedback. pkgname is the name of a directory at the top level of the dmg containing the AdobeUber tools and their XML files.''' munkicommon.display_status_minor( 'Mounting disk image %s' % os.path.basename(dmgpath)) mountpoints = mountAdobeDmg(dmgpath) if mountpoints: installroot = mountpoints[0] if uninstalling: ubertool = os.path.join(installroot, pkgname, "AdobeUberUninstaller") else: ubertool = os.path.join(installroot, pkgname, "AdobeUberInstaller") if os.path.exists(ubertool): info = getAdobePackageInfo(installroot) packagename = info['display_name'] action = "Installing" if uninstalling: action = "Uninstalling" munkicommon.display_status_major('%s %s' % (action, packagename)) if munkicommon.munkistatusoutput: munkistatus.detail('Starting %s' % os.path.basename(ubertool)) # try to find and count the number of payloads # so we can give a rough progress indicator number_of_payloads = countPayloads(installroot) retcode = runAdobeInstallTool( [ubertool], number_of_payloads, killAdobeAIR=True) else: munkicommon.display_error("No %s found" % ubertool) retcode = -1 munkicommon.unmountdmg(installroot) return retcode else: munkicommon.display_error("No mountable filesystems on %s" % dmgpath) return -1
def unlock_and_set_nonlocking(keychain_path): '''Unlocks the keychain and sets it to non-locking''' keychain_pass = (munkicommon.pref('KeychainPassword') or DEFAULT_KEYCHAIN_PASSWORD) try: output = security('unlock-keychain', '-p', keychain_pass, keychain_path) if output: munkicommon.display_debug2(output) except SecurityError, err: # some problem unlocking the keychain. munkicommon.display_error('Could not unlock %s: %s.', keychain_path, err) # delete it try: os.unlink(keychain_path) except OSError, err: munkicommon.display_error('Could not remove %s: %s.', keychain_path, err)
def add_ca_certs_to_system_keychain(certdata=None): '''Adds any CA certs as trusted root certs to System.keychain''' if not certdata: certdata = get_munki_server_cert_data() ca_cert_path = certdata['ca_cert_path'] ca_dir_path = certdata['ca_dir_path'] SYSTEM_KEYCHAIN = "/Library/Keychains/System.keychain" if not os.path.exists(SYSTEM_KEYCHAIN): munkicommon.display_warning('%s not found.', SYSTEM_KEYCHAIN) return if not ca_cert_path and not ca_dir_path: # no CA certs, so nothing to do munkicommon.display_debug2( 'No CA cert info provided, so nothing to add to System keychain.') return else: munkicommon.display_debug2('CA cert path: %s', ca_cert_path) munkicommon.display_debug2('CA dir path: %s', ca_dir_path) # Add CA certs. security add-trusted-cert does the right thing even if # the cert is already added, so we just do it; checking to see if the # cert is already added is hard. certs_to_add = [] if ca_cert_path: certs_to_add.append(ca_cert_path) if ca_dir_path: # add any pem files in the ca_dir_path directory for item in os.listdir(ca_dir_path): if item.endswith('.pem'): certs_to_add.append(os.path.join(ca_dir_path, item)) for cert in certs_to_add: munkicommon.display_debug1('Adding CA cert %s...', cert) try: output = security('add-trusted-cert', '-d', '-k', SYSTEM_KEYCHAIN, cert) if output: munkicommon.display_debug2(output) except SecurityError, err: munkicommon.display_error( 'Could not add CA cert %s into System keychain: %s', cert, err)
def store_profile_receipt_data(identifier, hash_value): '''Stores info for profile identifier. If hash_value is None, item is removed from the datastore.''' profile_data = profile_receipt_data() if hash_value is not None: profile_dict = profile_info_for_installed_identifier(identifier, ignore_cache=True) install_date = profile_dict.get('ProfileInstallDate', 'UNKNOWN') profile_data[identifier] = { 'FileHash': hash_value, 'ProfileInstallDate': install_date } elif identifier in profile_data.keys(): del profile_data[identifier] try: FoundationPlist.writePlist(profile_data, profile_receipt_data_path()) except BaseException, err: munkicommon.display_error('Cannot update hash for %s: %s' % (identifier, err))
def store_profile_receipt_data(identifier, hash_value): '''Stores info for profile identifier. If hash_value is None, item is removed from the datastore.''' profile_data = profile_receipt_data() if hash_value is not None: profile_dict = profile_info_for_installed_identifier(identifier, ignore_cache=True) install_date = profile_dict.get('ProfileInstallDate', 'UNKNOWN') profile_data[identifier] = { 'FileHash': hash_value, 'ProfileInstallDate': install_date } elif identifier in profile_data.keys(): del profile_data[identifier] try: FoundationPlist.writePlist(profile_data, profile_receipt_data_path()) except BaseException, err: munkicommon.display_error( 'Cannot update hash for %s: %s' % (identifier, err))
def install_profile(profile_path, profile_identifier): '''Installs a profile. Returns True on success, False otherwise''' cmd = ['/usr/bin/profiles', '-IF', profile_path] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.communicate() if proc.returncode != 0: munkicommon.display_error( 'Profile %s installation failed: %s' % (os.path.basename(profile_path), proc.stderr)) return False if profile_identifier: record_profile_receipt(profile_path, profile_identifier) else: munkicommon.display_warning( 'No identifier for profile %s; cannot record an installation ' 'receipt.' % os.path.basename(profile_path)) return True
def unlock_and_set_nonlocking(keychain_path): '''Unlocks the keychain and sets it to non-locking''' keychain_pass = ( munkicommon.pref('KeychainPassword') or DEFAULT_KEYCHAIN_PASSWORD) try: output = security( 'unlock-keychain', '-p', keychain_pass, keychain_path) if output: munkicommon.display_debug2(output) except SecurityError, err: # some problem unlocking the keychain. munkicommon.display_error( 'Could not unlock %s: %s.', keychain_path, err) # delete it try: os.unlink(keychain_path) except OSError, err: munkicommon.display_error( 'Could not remove %s: %s.', keychain_path, err)
def add_ca_certs_to_system_keychain(cert_info=None): '''Adds any CA certs as trusted root certs to System.keychain''' if not cert_info: cert_info = get_munki_server_cert_info() ca_cert_path = cert_info['ca_cert_path'] ca_dir_path = cert_info['ca_dir_path'] if not ca_cert_path and not ca_dir_path: # no CA certs, so nothing to do munkicommon.display_debug2( 'No CA cert info provided, so nothing to add to System keychain.') return else: munkicommon.display_debug2('CA cert path: %s', ca_cert_path) munkicommon.display_debug2('CA dir path: %s', ca_dir_path) SYSTEM_KEYCHAIN = "/Library/Keychains/System.keychain" if not os.path.exists(SYSTEM_KEYCHAIN): munkicommon.display_warning('%s not found.', SYSTEM_KEYCHAIN) return # Add CA certs. security add-trusted-cert does the right thing even if # the cert is already added, so we just do it; checking to see if the # cert is already added is hard. certs_to_add = [] if ca_cert_path: certs_to_add.append(ca_cert_path) if ca_dir_path: # add any pem files in the ca_dir_path directory for item in os.listdir(ca_dir_path): if item.endswith('.pem'): certs_to_add.append(os.path.join(ca_dir_path, item)) for cert in certs_to_add: munkicommon.display_debug1('Adding CA cert %s...', cert) try: output = security('add-trusted-cert', '-d', '-k', SYSTEM_KEYCHAIN, cert) if output: munkicommon.display_debug2(output) except SecurityError, err: munkicommon.display_error( 'Could not add CA cert %s into System keychain: %s', cert, err)
def mountAdobeDmg(dmgpath): """ Attempts to mount the dmg at dmgpath and returns a list of mountpoints """ mountpoints = [] dmgname = os.path.basename(dmgpath) proc = subprocess.Popen(['/usr/bin/hdiutil', 'attach', dmgpath, '-nobrowse', '-noverify', '-plist'], bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (pliststr, err) = proc.communicate() if err: munkicommon.display_error("Error %s mounting %s." % (err, dmgname)) if pliststr: plist = FoundationPlist.readPlistFromString(pliststr) for entity in plist['system-entities']: if 'mount-point' in entity: mountpoints.append(entity['mount-point']) return mountpoints
def getpkgkeys(pkgnames): """ Given a list of receipt names, bom file names, or package ids, gets a list of pkg_keys from the pkgs table in our database. """ # open connection and cursor to our database conn = sqlite3.connect(packagedb) curs = conn.cursor() # check package names to make sure they're all in the database, # build our list of pkg_keys pkgerror = False pkgkeyslist = [] for pkg in pkgnames: values_t = (pkg, ) munkicommon.display_debug1("select pkg_key from pkgs where pkgid = %s", pkg) pkg_keys = curs.execute('select pkg_key from pkgs where pkgid = ?', values_t).fetchall() if not pkg_keys: # try pkgname munkicommon.display_debug1( "select pkg_key from pkgs where pkgname = %s", pkg) pkg_keys = curs.execute( 'select pkg_key from pkgs where pkgname = ?', values_t).fetchall() if not pkg_keys: munkicommon.display_error("%s not found in database.", pkg) pkgerror = True else: for row in pkg_keys: # only want first column pkgkeyslist.append(row[0]) if pkgerror: pkgkeyslist = [] curs.close() conn.close() munkicommon.display_debug1("pkgkeys: %s", pkgkeyslist) return pkgkeyslist
def doAdobeCS5Uninstall(adobeInstallInfo): '''Runs the locally-installed Adobe CS5 tools to remove CS5 products. We need the uninstallxml and the CS5 Setup.app.''' uninstallxml = adobeInstallInfo.get('uninstallxml') if not uninstallxml: munkicommon.display_error("No uninstall.xml in adobe_install_info") return -1 payloadcount = adobeInstallInfo.get('payload_count', 0) path = os.path.join(munkicommon.tmpdir, "uninstall.xml") deploymentFile = writefile(uninstallxml, path) if not deploymentFile: return -1 setupapp = "/Library/Application Support/Adobe/OOBE/PDApp/DWA/Setup.app" setup = os.path.join(setupapp, "Contents/MacOS/Setup") if not os.path.exists(setup): munkicommon.display_error("%s is not installed." % setupapp) return -1 uninstall_cmd = [ setup, '--mode=silent', '--action=uninstall', '--skipProcessCheck=1', '--deploymentFile=%s' % deploymentFile ] munkicommon.display_status_minor('Running Adobe Uninstall') return runAdobeInstallTool(uninstall_cmd, payloadcount)
def runAdobeSetup(dmgpath, uninstalling=False): '''Runs the Adobe setup tool in silent mode from an Adobe update DMG or an Adobe CS3 install DMG''' munkicommon.display_status_minor( 'Mounting disk image %s' % os.path.basename(dmgpath)) mountpoints = mountAdobeDmg(dmgpath) if mountpoints: setup_path = findSetupApp(mountpoints[0]) if setup_path: # look for install.xml or uninstall.xml at root deploymentfile = None installxml = os.path.join(mountpoints[0], "install.xml") uninstallxml = os.path.join(mountpoints[0], "uninstall.xml") if uninstalling: if os.path.exists(uninstallxml): deploymentfile = uninstallxml else: # we've been asked to uninstall, # but found no uninstall.xml # so we need to bail munkicommon.unmountdmg(mountpoints[0]) munkicommon.display_error( "%s doesn't appear to contain uninstall info." % os.path.basename(dmgpath)) return -1 else: if os.path.exists(installxml): deploymentfile = installxml # try to find and count the number of payloads # so we can give a rough progress indicator number_of_payloads = countPayloads(mountpoints[0]) munkicommon.display_status_minor('Running Adobe Setup') adobe_setup = [ setup_path, '--mode=silent', '--skipProcessCheck=1' ] if deploymentfile: adobe_setup.append('--deploymentFile=%s' % deploymentfile) retcode = runAdobeInstallTool(adobe_setup, number_of_payloads) else: munkicommon.display_error( '%s doesn\'t appear to contain Adobe Setup.' % os.path.basename(dmgpath)) retcode = -1 munkicommon.unmountdmg(mountpoints[0]) return retcode else: munkicommon.display_error('No mountable filesystems on %s' % dmgpath) return -1
def runAdobeCS5PatchInstaller(dmgpath, copylocal=False): '''Runs the AdobePatchInstaller for CS5. Optionally can copy the DMG contents to the local disk to work around issues with the patcher.''' munkicommon.display_status_minor( 'Mounting disk image %s' % os.path.basename(dmgpath)) mountpoints = mountAdobeDmg(dmgpath) if mountpoints: if copylocal: # copy the update to the local disk before installing updatedir = tempfile.mkdtemp() retcode = subprocess.call(["/bin/cp", "-r", mountpoints[0], updatedir]) # unmount diskimage munkicommon.unmountdmg(mountpoints[0]) if retcode: munkicommon.display_error( "Error copying items from %s" % dmgpath) return -1 # remove the dmg file to free up space, since we don't need it # any longer unused_result = subprocess.call(["/bin/rm", dmgpath]) else: updatedir = mountpoints[0] patchinstaller = findAdobePatchInstallerApp(updatedir) if patchinstaller: # try to find and count the number of payloads # so we can give a rough progress indicator number_of_payloads = countPayloads(updatedir) munkicommon.display_status_minor('Running Adobe Patch Installer') install_cmd = [ patchinstaller, '--mode=silent', '--skipProcessCheck=1' ] retcode = runAdobeInstallTool(install_cmd, number_of_payloads) else: munkicommon.display_error( "%s doesn't appear to contain AdobePatchInstaller.app." % os.path.basename(dmgpath)) retcode = -1 if copylocal: # clean up our mess unused_result = subprocess.call(["/bin/rm", "-rf", updatedir]) else: munkicommon.unmountdmg(mountpoints[0]) return retcode else: munkicommon.display_error("No mountable filesystems on %s" % dmgpath) return -1
def extractAppIconsFromFlatPkg(pkg_path): '''Extracts application icons from a flat package. Returns a list of paths to icns files.''' cmd = ['/usr/sbin/pkgutil', '--bom', pkg_path] proc = subprocess.Popen(cmd, shell=False, bufsize=-1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output = proc.communicate()[0] if proc.returncode: munkicommon.display_error(u'Could not get bom files from %s', pkg_path) return [] bomfilepaths = output.splitlines() pkg_dict = {} for bomfile in bomfilepaths: # bomfile path is of the form: # /tmp/FlashPlayer.pkg.boms.2Rxa1z/AdobeFlashPlayerComponent.pkg/Bom pkgname = os.path.basename(os.path.dirname(bomfile)) if not pkgname.endswith(u'.pkg'): # no subpackages; this is a component pkg pkgname = '' cmd = ['/usr/bin/lsbom', '-s', bomfile] proc = subprocess.Popen(cmd, shell=False, bufsize=-1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output = proc.communicate()[0] if proc.returncode: munkicommon.display_error(u'Could not lsbom %s', bomfile) # record paths to all app Info.plist files pkg_dict[pkgname] = [ os.path.normpath(line) for line in output.decode('utf-8').splitlines() if line.endswith(u'.app/Contents/Info.plist')] if not pkg_dict[pkgname]: # remove empty lists del pkg_dict[pkgname] if not pkg_dict: return [] icon_paths = [] pkgtmp = os.path.join(tempfile.mkdtemp(dir=u'/tmp'), u'pkg') exporttmp = tempfile.mkdtemp(dir='/tmp') cmd = ['/usr/sbin/pkgutil', '--expand', pkg_path, pkgtmp] result = subprocess.call(cmd) if result == 0: for pkg in pkg_dict: archive_path = os.path.join(pkgtmp, pkg, u'Payload') err = extractAppBitsFromPkgArchive(archive_path, exporttmp) if err == 0: for info_path in pkg_dict[pkg]: full_path = os.path.join(exporttmp, info_path) # convert path to Info.plist to path to app app_path = os.path.dirname(os.path.dirname(full_path)) icon_path = findIconForApp(app_path) if icon_path: icon_paths.append(icon_path) else: munkicommon.display_error( u'pax could not read files from %s', archive_path) return [] else: munkicommon.display_error(u'Could not expand %s', pkg_path) # clean up our expanded flat package; we no longer need it shutil.rmtree(pkgtmp) return icon_paths
def removeFilesystemItems(removalpaths, forcedeletebundles): """ Attempts to remove all the paths in the array removalpaths """ # we sort in reverse because we can delete from the bottom up, # clearing a directory before we try to remove the directory itself removalpaths.sort(reverse=True) removalerrors = "" removalcount = len(removalpaths) munkicommon.display_status_minor('Removing %s filesystem items' % removalcount) itemcount = len(removalpaths) itemindex = 0 munkicommon.display_percent_done(itemindex, itemcount) for item in removalpaths: itemindex += 1 pathtoremove = "/" + item # use os.path.lexists so broken links return true # so we can remove them if os.path.lexists(pathtoremove): munkicommon.display_detail("Removing: " + pathtoremove) if (os.path.isdir(pathtoremove) and not os.path.islink(pathtoremove)): diritems = munkicommon.listdir(pathtoremove) if diritems == ['.DS_Store']: # If there's only a .DS_Store file # we'll consider it empty ds_storepath = pathtoremove + "/.DS_Store" try: os.remove(ds_storepath) except (OSError, IOError): pass diritems = munkicommon.listdir(pathtoremove) if diritems == []: # directory is empty try: os.rmdir(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove directory %s - %s" % ( pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # the directory is marked for deletion but isn't empty. # if so directed, if it's a bundle (like .app), we should # remove it anyway - no use having a broken bundle hanging # around if forcedeletebundles and isBundle(pathtoremove): munkicommon.display_warning( "Removing non-empty bundle: %s", pathtoremove) retcode = subprocess.call( ['/bin/rm', '-r', pathtoremove]) if retcode: msg = "Couldn't remove bundle %s" % pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # if this path is inside a bundle, and we've been # directed to force remove bundles, # we don't need to warn because it's going to be # removed with the bundle. # Otherwise, we should warn about non-empty # directories. if not insideBundle(pathtoremove) or \ not forcedeletebundles: msg = \ "Did not remove %s because it is not empty." % \ pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # not a directory, just unlink it # I was using rm instead of Python because I don't trust # handling of resource forks with Python #retcode = subprocess.call(['/bin/rm', pathtoremove]) # but man that's slow. # I think there's a lot of overhead with the # subprocess call. I'm going to use os.remove. # I hope I don't regret it. retcode = '' try: os.remove(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove item %s: %s" % (pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg
def test_display_error_with_str_msg(self): munkicommon.display_error(MSG_STR)
def test_display_error_with_unicode_msg_unicode_arg(self): munkicommon.display_error(MSG_UNI, ARG_UNI)
def ImportPackage(packagepath, curs): """ Imports package data from the receipt at packagepath into our internal package database. """ bompath = os.path.join(packagepath, 'Contents/Archive.bom') infopath = os.path.join(packagepath, 'Contents/Info.plist') pkgname = os.path.basename(packagepath) if not os.path.exists(packagepath): munkicommon.display_error("%s not found.", packagepath) return if not os.path.isdir(packagepath): # Every machine I've seen has a bogus BSD.pkg, # so we won't print a warning for that specific one. if pkgname != "BSD.pkg": munkicommon.display_warning( "%s is not a valid receipt. Skipping.", packagepath) return if not os.path.exists(bompath): # look in receipt's Resources directory bomname = os.path.splitext(pkgname)[0] + '.bom' bompath = os.path.join(packagepath, "Contents/Resources", bomname) if not os.path.exists(bompath): munkicommon.display_warning( "%s has no BOM file. Skipping.", packagepath) return if not os.path.exists(infopath): munkicommon.display_warning( "%s has no Info.plist. Skipping.", packagepath) return timestamp = os.stat(packagepath).st_mtime owner = 0 plist = FoundationPlist.readPlist(infopath) if "CFBundleIdentifier" in plist: pkgid = plist["CFBundleIdentifier"] elif "Bundle identifier" in plist: # special case for JAMF Composer generated packages. WTF? pkgid = plist["Bundle identifier"] else: pkgid = pkgname if "CFBundleShortVersionString" in plist: vers = plist["CFBundleShortVersionString"] elif "Bundle versions string, short" in plist: # another special case for JAMF Composer-generated packages. Wow. vers = plist["Bundle versions string, short"] else: vers = "1.0" if "IFPkgRelocatedPath" in plist: ppath = plist["IFPkgRelocatedPath"] ppath = ppath.lstrip('./').rstrip('/') else: ppath = "" values_t = (timestamp, owner, pkgid, vers, ppath, pkgname) curs.execute( '''INSERT INTO pkgs (timestamp, owner, pkgid, vers, ppath, pkgname) values (?, ?, ?, ?, ?, ?)''', values_t) pkgkey = curs.lastrowid cmd = ["/usr/bin/lsbom", bompath] proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: line = proc.stdout.readline().decode('UTF-8') if not line and (proc.poll() != None): break try: item = line.rstrip("\n").split("\t") path = item[0] perms = item[1] uidgid = item[2].split("/") uid = uidgid[0] gid = uidgid[1] except IndexError: # we really only care about the path perms = "0000" uid = "0" gid = "0" try: if path != ".": # special case for MS Office 2008 installers if ppath == "tmp/com.microsoft.updater/office_location": ppath = "Applications" # prepend the ppath so the paths match the actual install # locations path = path.lstrip("./") if ppath: path = ppath + "/" + path values_t = (path, ) row = curs.execute( 'SELECT path_key from paths where path = ?', values_t).fetchone() if not row: curs.execute( 'INSERT INTO paths (path) values (?)', values_t) pathkey = curs.lastrowid else: pathkey = row[0] values_t = (pkgkey, pathkey, uid, gid, perms) curs.execute( '''INSERT INTO pkgs_paths (pkg_key, path_key, uid, gid, perms) values (?, ?, ?, ?, ?)''', values_t) except sqlite3.DatabaseError: pass
def initDatabase(forcerebuild=False): """ Builds or rebuilds our internal package database. """ if not shouldRebuildDB(packagedb) and not forcerebuild: return True munkicommon.display_status_minor( 'Gathering information on installed packages') if os.path.exists(packagedb): try: os.remove(packagedb) except (OSError, IOError): munkicommon.display_error( "Could not remove out-of-date receipt database.") return False os_version = munkicommon.getOsVersion(as_tuple=True) pkgcount = 0 receiptsdir = "/Library/Receipts" bomsdir = "/Library/Receipts/boms" if os.path.exists(receiptsdir): receiptlist = munkicommon.listdir(receiptsdir) for item in receiptlist: if item.endswith(".pkg"): pkgcount += 1 if os.path.exists(bomsdir): bomslist = munkicommon.listdir(bomsdir) for item in bomslist: if item.endswith(".bom"): pkgcount += 1 if os_version >= (10, 6): # Snow Leopard or later pkglist = [] cmd = ['/usr/sbin/pkgutil', '--pkgs'] proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) while True: line = proc.stdout.readline() if not line and (proc.poll() != None): break pkglist.append(line.rstrip('\n')) pkgcount += 1 conn = sqlite3.connect(packagedb) conn.text_factory = str curs = conn.cursor() CreateTables(curs) currentpkgindex = 0 munkicommon.display_percent_done(0, pkgcount) if os.path.exists(receiptsdir): receiptlist = munkicommon.listdir(receiptsdir) for item in receiptlist: if munkicommon.stopRequested(): curs.close() conn.close() #our package db isn't valid, so we should delete it os.remove(packagedb) return False if item.endswith(".pkg"): receiptpath = os.path.join(receiptsdir, item) munkicommon.display_detail("Importing %s...", receiptpath) ImportPackage(receiptpath, curs) currentpkgindex += 1 munkicommon.display_percent_done(currentpkgindex, pkgcount) if os.path.exists(bomsdir): bomslist = munkicommon.listdir(bomsdir) for item in bomslist: if munkicommon.stopRequested(): curs.close() conn.close() #our package db isn't valid, so we should delete it os.remove(packagedb) return False if item.endswith(".bom"): bompath = os.path.join(bomsdir, item) munkicommon.display_detail("Importing %s...", bompath) ImportBom(bompath, curs) currentpkgindex += 1 munkicommon.display_percent_done(currentpkgindex, pkgcount) if os_version >= (10, 6): # Snow Leopard or later for pkg in pkglist: if munkicommon.stopRequested(): curs.close() conn.close() #our package db isn't valid, so we should delete it os.remove(packagedb) return False munkicommon.display_detail("Importing %s...", pkg) ImportFromPkgutil(pkg, curs) currentpkgindex += 1 munkicommon.display_percent_done(currentpkgindex, pkgcount) # in case we didn't quite get to 100% for some reason if currentpkgindex < pkgcount: munkicommon.display_percent_done(pkgcount, pkgcount) # commit and close the db when we're done. conn.commit() curs.close() conn.close() return True
def removeFilesystemItems(removalpaths, forcedeletebundles): """ Attempts to remove all the paths in the array removalpaths """ # we sort in reverse because we can delete from the bottom up, # clearing a directory before we try to remove the directory itself removalpaths.sort(reverse=True) removalerrors = "" removalcount = len(removalpaths) munkicommon.display_status_minor( 'Removing %s filesystem items' % removalcount) itemcount = len(removalpaths) itemindex = 0 munkicommon.display_percent_done(itemindex, itemcount) for item in removalpaths: itemindex += 1 pathtoremove = "/" + item # use os.path.lexists so broken links return true # so we can remove them if os.path.lexists(pathtoremove): munkicommon.display_detail("Removing: " + pathtoremove) if (os.path.isdir(pathtoremove) and \ not os.path.islink(pathtoremove)): diritems = munkicommon.listdir(pathtoremove) if diritems == ['.DS_Store']: # If there's only a .DS_Store file # we'll consider it empty ds_storepath = pathtoremove + "/.DS_Store" try: os.remove(ds_storepath) except (OSError, IOError): pass diritems = munkicommon.listdir(pathtoremove) if diritems == []: # directory is empty try: os.rmdir(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove directory %s - %s" % ( pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # the directory is marked for deletion but isn't empty. # if so directed, if it's a bundle (like .app), we should # remove it anyway - no use having a broken bundle hanging # around if (forcedeletebundles and isBundle(pathtoremove)): munkicommon.display_warning( "Removing non-empty bundle: %s", pathtoremove) retcode = subprocess.call(['/bin/rm', '-r', pathtoremove]) if retcode: msg = "Couldn't remove bundle %s" % pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # if this path is inside a bundle, and we've been # directed to force remove bundles, # we don't need to warn because it's going to be # removed with the bundle. # Otherwise, we should warn about non-empty # directories. if not insideBundle(pathtoremove) or \ not forcedeletebundles: msg = \ "Did not remove %s because it is not empty." % \ pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # not a directory, just unlink it # I was using rm instead of Python because I don't trust # handling of resource forks with Python #retcode = subprocess.call(['/bin/rm', pathtoremove]) # but man that's slow. # I think there's a lot of overhead with the # subprocess call. I'm going to use os.remove. # I hope I don't regret it. retcode = '' try: os.remove(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove item %s: %s" % (pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg
added_keychain = add_to_keychain_list(abs_keychain_path) unlock_and_set_nonlocking(abs_keychain_path) # Add client cert (and optionally key) client_cert_file = None combined_pem = None if client_key_path: # combine client cert and private key before we import cert_data = read_file(client_cert_path) key_data = read_file(client_key_path) # write the combined data combined_pem = os.path.join(munkicommon.tmpdir(), 'combined.pem') if write_file(cert_data + key_data, combined_pem): client_cert_file = combined_pem else: munkicommon.display_error( 'Could not combine client cert and key for import!') else: client_cert_file = client_cert_path if client_cert_file: # client_cert_file is combined_pem or client_cert_file munkicommon.display_debug2('Importing client cert and key...') try: output = security( 'import', client_cert_file, '-A', '-k', abs_keychain_path) if output: munkicommon.display_debug2(output) except SecurityError, err: munkicommon.display_error( 'Could not import %s: %s', client_cert_file, err) if combined_pem: # we created this; we should clean it up