def get_munki_client_cert_info(): '''Attempt to get information we need from Munki's preferences or defaults. Returns a dictionary.''' ManagedInstallDir = munkicommon.pref('ManagedInstallDir') cert_info = {} cert_info['client_cert_path'] = None cert_info['client_key_path'] = None cert_info['site_urls'] = [] # get client cert if it exists if munkicommon.pref('UseClientCertificate'): cert_info['client_cert_path'] = ( munkicommon.pref('ClientCertificatePath') or None) cert_info['client_key_path'] = munkicommon.pref('ClientKeyPath') or None if not cert_info['client_cert_path']: for name in ['cert.pem', 'client.pem', 'munki.pem']: client_cert_path = os.path.join( ManagedInstallDir, 'certs', name) if os.path.exists(client_cert_path): cert_info['client_cert_path'] = client_cert_path break site_urls = [] for key in ['SoftwareRepoURL', 'PackageURL', 'CatalogURL', 'ManifestURL', 'IconURL', 'ClientResourceURL']: url = munkicommon.pref(key) if url: site_urls.append(url.rstrip('/') + '/') cert_info['site_urls'] = site_urls return cert_info
def get_munki_client_cert_data(): '''Attempt to get information we need from Munki's preferences or defaults. Returns a dictionary.''' ManagedInstallDir = munkicommon.pref('ManagedInstallDir') cert_data = {} cert_data['client_cert_path'] = None cert_data['client_key_path'] = None # get client cert if it exists if munkicommon.pref('UseClientCertificate'): cert_data['client_cert_path'] = ( munkicommon.pref('ClientCertificatePath') or None) cert_data['client_key_path'] = munkicommon.pref( 'ClientKeyPath') or None if not cert_data['client_cert_path']: for name in ['cert.pem', 'client.pem', 'munki.pem']: client_cert_path = os.path.join(ManagedInstallDir, 'certs', name) if os.path.exists(client_cert_path): cert_data['client_cert_path'] = client_cert_path break cert_data['site_url'] = (munkicommon.pref('SoftwareRepoURL').rstrip('/') + '/') return cert_data
def download_icons(item_list, icon_dir): """Download icons for items in the list. Based on updatecheck.py, modified. Copied from https://github.com/munki/munki/blob/master/code/client/munkilib/updatecheck.py#L2824 Attempts to download icons (actually png files) for items in item_list """ icon_list = [] icon_known_exts = [ '.bmp', '.gif', '.icns', '.jpg', '.jpeg', '.png', '.psd', '.tga', '.tif', '.tiff', '.yuv' ] icon_base_url = (pref('IconURL') or pref('SoftwareRepoURL') + '/icons/') icon_base_url = icon_base_url.rstrip('/') + '/' for item in item_list: icon_name = item.get('icon_name') or item['name'] pkginfo_icon_hash = item.get('icon_hash') if not os.path.splitext(icon_name)[1] in icon_known_exts: icon_name += '.png' icon_list.append(icon_name) icon_url = icon_base_url + urllib2.quote(icon_name.encode('UTF-8')) icon_path = os.path.join(icon_dir, icon_name) if os.path.isfile(icon_path): xattr_hash = getxattr(icon_path, XATTR_SHA) if not xattr_hash: xattr_hash = getsha256hash(icon_path) writeCachedChecksum(icon_path, xattr_hash) else: xattr_hash = 'nonexistent' icon_subdir = os.path.dirname(icon_path) if not os.path.exists(icon_subdir): try: os.makedirs(icon_subdir, 0755) except OSError, err: print 'Could not create %s' % icon_subdir continue custom_headers = [''] if BASIC_AUTH: # custom_headers = ['Authorization: Basic %s' % BASIC_AUTH] custom_headers = BASIC_AUTH if pkginfo_icon_hash != xattr_hash: item_name = item.get('display_name') or item['name'] message = 'Getting icon %s for %s...' % (icon_name, item_name) try: dummy_value = getResourceIfChangedAtomically( icon_url, icon_path, custom_headers=custom_headers, message=message) except MunkiDownloadError, err: print('Could not retrieve icon %s from the server: %s', icon_name, err) else: if os.path.isfile(icon_path): writeCachedChecksum(icon_path)
def download_icons(item_list, icon_dir): """Download icons for items in the list. Based on updatecheck.py, modified. Copied from https://github.com/munki/munki/blob/master/code/client/munkilib/updatecheck.py#L2824 Attempts to download icons (actually png files) for items in item_list """ icon_list = [] icon_known_exts = ['.bmp', '.gif', '.icns', '.jpg', '.jpeg', '.png', '.psd', '.tga', '.tif', '.tiff', '.yuv'] icon_base_url = (pref('IconURL') or pref('SoftwareRepoURL') + '/icons/') icon_base_url = icon_base_url.rstrip('/') + '/' for item in item_list: icon_name = item.get('icon_name') or item['name'] pkginfo_icon_hash = item.get('icon_hash') if not os.path.splitext(icon_name)[1] in icon_known_exts: icon_name += '.png' icon_list.append(icon_name) icon_url = icon_base_url + urllib2.quote(icon_name.encode('UTF-8')) icon_path = os.path.join(icon_dir, icon_name) if os.path.isfile(icon_path): xattr_hash = getxattr(icon_path, XATTR_SHA) if not xattr_hash: xattr_hash = getsha256hash(icon_path) writeCachedChecksum(icon_path, xattr_hash) else: xattr_hash = 'nonexistent' icon_subdir = os.path.dirname(icon_path) if not os.path.exists(icon_subdir): try: os.makedirs(icon_subdir, 0755) except OSError, err: print 'Could not create %s' % icon_subdir continue custom_headers = [''] if BASIC_AUTH: # custom_headers = ['Authorization: Basic %s' % BASIC_AUTH] custom_headers = BASIC_AUTH if pkginfo_icon_hash != xattr_hash: item_name = item.get('display_name') or item['name'] message = 'Getting icon %s for %s...' % (icon_name, item_name) try: dummy_value = getResourceIfChangedAtomically( icon_url, icon_path, custom_headers=custom_headers, message=message) except MunkiDownloadError, err: print ('Could not retrieve icon %s from the server: %s', icon_name, err) else: if os.path.isfile(icon_path): writeCachedChecksum(icon_path)
def handle_custom(custom_dir): """Download custom resources and build the package.""" print "Downloading Munki client resources." updatecheck.download_client_resources() # Client Resoures are stored in # /Library/Managed Installs/client_resources/custom.zip resource_dir = os.path.join( pref('ManagedInstallDir'), 'client_resources') resource_file = os.path.join(resource_dir, 'custom.zip') if os.path.isfile(resource_file): destination_path = custom_dir pkg_output_file = os.path.join(CACHE, 'munki_custom.pkg') success = build_pkg( resource_dir, 'munki_custom', 'com.facebook.cpe.munki_custom', destination_path, CACHE, 'Creating the Munki custom resources package.' ) if success: return pkg_output_file else: print >> sys.stderr, "Failed to build Munki custom resources package!" return None
def doAdobeInstall(item): '''Wrapper to handle all the Adobe installer methods. First get the path to the installer dmg. We know it exists because installer.py already checked.''' managedinstallbase = \ munkicommon.pref('ManagedInstallDir') itempath = os.path.join(managedinstallbase, 'Cache', item["installer_item"]) installer_type = item.get("installer_type","") if installer_type == "AdobeSetup": # Adobe CS3/CS4 updater or Adobe CS3 installer retcode = runAdobeSetup(itempath) elif installer_type == "AdobeUberInstaller": # Adobe CS4 installer pkgname = item.get("adobe_package_name") or \ item.get("package_path","") retcode = runAdobeUberTool(itempath, pkgname) elif installer_type == "AdobeAcrobatUpdater": # Acrobat Pro 9 updater retcode = updateAcrobatPro(itempath) elif installer_type == "AdobeCS5AAMEEPackage": # Adobe CS5 AAMEE package retcode = runAdobeCS5AAMEEInstall(itempath) elif installer_type == "AdobeCS5PatchInstaller": # Adobe CS5 updater retcode = runAdobeCS5PatchInstaller(itempath, copylocal=item.get("copy_local")) return retcode
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 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 appleSoftwareUpdatesAvailable(forcecheck=False, suppresscheck=False): '''Checks for available Apple Software Updates, trying not to hit the SUS more than needed''' if suppresscheck: # typically because we're doing a logout install; if # there are no waiting Apple Updates we shouldn't # trigger a check for them. pass elif forcecheck: # typically because user initiated the check from # Managed Software Update.app unused_retcode = checkForSoftwareUpdates(forcecheck=True) else: # have we checked recently? Don't want to check with # Apple Software Update server too frequently now = NSDate.new() nextSUcheck = now lastSUcheckString = munkicommon.pref('LastAppleSoftwareUpdateCheck') if lastSUcheckString: try: lastSUcheck = NSDate.dateWithString_(lastSUcheckString) interval = 24 * 60 * 60 nextSUcheck = lastSUcheck.dateByAddingTimeInterval_(interval) except (ValueError, TypeError): pass if now.timeIntervalSinceDate_(nextSUcheck) >= 0: unused_retcode = checkForSoftwareUpdates(forcecheck=True) else: unused_retcode = checkForSoftwareUpdates(forcecheck=False) if writeAppleUpdatesFile(): displayAppleUpdateInfo() return True else: return False
def swupdCacheDir(temp=True): '''Returns the local cache dir for our Software Update mini-cache. The temp cache directory is cleared upon install completion. The non-temp is kept.''' ManagedInstallDir = munkicommon.pref('ManagedInstallDir') if temp: return os.path.join(ManagedInstallDir, 'swupd', 'mirror') else: return os.path.join(ManagedInstallDir, 'swupd')
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 get_munki_server_cert_info(): '''Attempt to get information we need from Munki's preferences or defaults. Returns a dictionary.''' ManagedInstallDir = munkicommon.pref('ManagedInstallDir') cert_info = {} # get server CA cert if it exists so we can verify the Munki server cert_info['ca_cert_path'] = None cert_info['ca_dir_path'] = None if munkicommon.pref('SoftwareRepoCAPath'): CA_path = munkicommon.pref('SoftwareRepoCAPath') if os.path.isfile(CA_path): cert_info['ca_cert_path'] = CA_path elif os.path.isdir(CA_path): cert_info['ca_dir_path'] = CA_path if munkicommon.pref('SoftwareRepoCACertificate'): cert_info['ca_cert_path'] = munkicommon.pref( 'SoftwareRepoCACertificate') if cert_info['ca_cert_path'] == None: ca_cert_path = os.path.join(ManagedInstallDir, 'certs', 'ca.pem') if os.path.exists(ca_cert_path): cert_info['ca_cert_path'] = ca_cert_path return cert_info
def get_munki_server_cert_data(): '''Attempt to get information we need from Munki's preferences or defaults. Returns a dictionary.''' ManagedInstallDir = munkicommon.pref('ManagedInstallDir') cert_data = {} # get server CA cert if it exists so we can verify the Munki server cert_data['ca_cert_path'] = None cert_data['ca_dir_path'] = None if munkicommon.pref('SoftwareRepoCAPath'): CA_path = munkicommon.pref('SoftwareRepoCAPath') if os.path.isfile(CA_path): cert_data['ca_cert_path'] = CA_path elif os.path.isdir(CA_path): cert_data['ca_dir_path'] = CA_path if munkicommon.pref('SoftwareRepoCACertificate'): cert_data['ca_cert_path'] = munkicommon.pref( 'SoftwareRepoCACertificate') if cert_data['ca_cert_path'] == None: ca_cert_path = os.path.join(ManagedInstallDir, 'certs', 'ca.pem') if os.path.exists(ca_cert_path): cert_data['ca_cert_path'] = ca_cert_path return cert_data
def get_munki_client_cert_data(): '''Attempt to get information we need from Munki's preferences or defaults. Returns a dictionary.''' ManagedInstallDir = munkicommon.pref('ManagedInstallDir') cert_data = {} cert_data['client_cert_path'] = None cert_data['client_key_path'] = None # get client cert if it exists if munkicommon.pref('UseClientCertificate'): cert_data['client_cert_path'] = ( munkicommon.pref('ClientCertificatePath') or None) cert_data['client_key_path'] = munkicommon.pref('ClientKeyPath') or None if not cert_data['client_cert_path']: for name in ['cert.pem', 'client.pem', 'munki.pem']: client_cert_path = os.path.join( ManagedInstallDir, 'certs', name) if os.path.exists(client_cert_path): cert_data['client_cert_path'] = client_cert_path break cert_data['site_url'] = ( munkicommon.pref('SoftwareRepoURL').rstrip('/') + '/') return cert_data
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 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 handle_custom(custom_dir): """Download custom resources and build the package.""" print "Downloading Munki client resources." updatecheck.download_client_resources() # Client Resoures are stored in # /Library/Managed Installs/client_resources/custom.zip resource_dir = os.path.join(pref('ManagedInstallDir'), 'client_resources') resource_file = os.path.join(resource_dir, 'custom.zip') if os.path.isfile(resource_file): destination_path = custom_dir pkg_output_file = os.path.join(CACHE, 'munki_custom.pkg') success = build_pkg(resource_dir, 'munki_custom', 'com.facebook.cpe.munki_custom', destination_path, CACHE, 'Creating the Munki custom resources package.') if success: return pkg_output_file else: print >> sys.stderr, "Failed to build Munki custom resources package!" return None
def get_keychain_path(): '''Returns an absolute path for our keychain''' keychain_name = (munkicommon.pref('KeychainName') or DEFAULT_KEYCHAIN_NAME) # If we have an odd path that appears to be all directory and no # file name, revert to default filename if not os.path.basename(keychain_name): keychain_name = DEFAULT_KEYCHAIN_NAME # Check to make sure it's just a simple file name, no directory # information if os.path.dirname(keychain_name): # keychain name should be just the filename, # so we'll drop down to the base name keychain_name = os.path.basename( keychain_name).strip() or DEFAULT_KEYCHAIN_NAME # Correct the filename to include '.keychain' if not already present if not keychain_name.lower().endswith('.keychain'): keychain_name += '.keychain' keychain_path = os.path.realpath( os.path.join(KEYCHAIN_DIRECTORY, keychain_name)) return keychain_path
def installedApplePackagesChanged(): '''Generates a SHA-256 checksum of the info for all packages in the receipts database whose id matches com.apple.* and compares it to a stored version of this checksum. Returns False if the checksums match, True if they differ.''' cmd = ['/usr/sbin/pkgutil', '--regexp', '-pkg-info-plist', 'com\.apple\.*'] proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (output, unused_err) = proc.communicate() current_apple_packages_checksum = hashlib.sha256(output).hexdigest() old_apple_packages_checksum = munkicommon.pref( 'InstalledApplePackagesChecksum') if current_apple_packages_checksum == old_apple_packages_checksum: return False else: munkicommon.set_pref('InstalledApplePackagesChecksum', current_apple_packages_checksum) return True
def get_keychain_path(): '''Returns an absolute path for our keychain''' keychain_name = ( munkicommon.pref('KeychainName') or DEFAULT_KEYCHAIN_NAME) # If we have an odd path that appears to be all directory and no # file name, revert to default filename if not os.path.basename(keychain_name): keychain_name = DEFAULT_KEYCHAIN_NAME # Check to make sure it's just a simple file name, no directory # information if os.path.dirname(keychain_name): # keychain name should be just the filename, # so we'll drop down to the base name keychain_name = os.path.basename( keychain_name).strip() or DEFAULT_KEYCHAIN_NAME # Correct the filename to include '.keychain' if not already present if not keychain_name.lower().endswith('.keychain'): keychain_name += '.keychain' keychain_path = os.path.realpath( os.path.join(KEYCHAIN_DIRECTORY, keychain_name)) return keychain_path
def verifySoftwarePackageIntegrity(file_path, item_hash, always_hash=False): """Verifies the integrity of the given software package. The feature is controlled through the PackageVerificationMode key in the ManagedInstalls.plist. Following modes currently exist: none: No integrity check is performed. hash: Integrity check is performed by calcualting a SHA-256 hash of the given file and comparing it against the reference value in catalog. Only applies for package plists that contain the item_key; for packages without the item_key, verifcation always returns True. hash_strict: Same as hash, but returns False for package plists that do not contain the item_key. Args: file_path: The file to check integrity on. item_hash: the sha256 hash expected. always_hash: True/False always check (& return) the hash even if not necessary for this function. Returns: (True/False, sha256-hash) True if the package integrity could be validated. Otherwise, False. """ mode = munkicommon.pref('PackageVerificationMode') chash = None item_name = getURLitemBasename(file_path) if always_hash: chash = munkicommon.getsha256hash(file_path) if not mode: return (True, chash) elif mode.lower() == 'none': munkicommon.display_warning('Package integrity checking is disabled.') return (True, chash) elif mode.lower() == 'hash' or mode.lower() == 'hash_strict': if item_hash: munkicommon.display_status_minor('Verifying package integrity...') if not chash: chash = munkicommon.getsha256hash(file_path) if item_hash == chash: return (True, chash) else: munkicommon.display_error( 'Hash value integrity check for %s failed.' % item_name) return (False, chash) else: if mode.lower() == 'hash_strict': munkicommon.display_error( 'Reference hash value for %s is missing in catalog.' % item_name) return (False, chash) else: munkicommon.display_warning( 'Reference hash value missing for %s -- package ' 'integrity verification skipped.' % item_name) return (True, chash) else: munkicommon.display_error( 'The PackageVerificationMode in the ManagedInstalls.plist has an ' 'illegal value: %s' % munkicommon.pref('PackageVerificationMode')) return (False, chash)
def appleUpdatesFile(): '''Returns path to the AppleUpdates.plist''' return os.path.join(munkicommon.pref('ManagedInstallDir'), 'AppleUpdates.plist')
if minor_os_version > 8: # Try to find our ptyexec tool # first look in the parent directory of this file's directory # (../) parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ptyexec_path = os.path.join(parent_dir, 'ptyexec') if not os.path.exists(ptyexec_path): # try absolute path in munki's normal install dir ptyexec_path = '/usr/local/munki/ptyexec' if os.path.exists(ptyexec_path): cmd = [ptyexec_path] # Workaround for current issue in OS X 10.9's included curl # Allows for alternate curl binary path as Apple's included curl currently # broken for client-side certificate usage curl_path = munkicommon.pref('CurlPath') or '/usr/bin/curl' cmd.extend([curl_path, '-q', # don't read .curlrc file '--config', # use config file curldirectivepath]) proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) targetsize = 0 downloadedpercent = -1 donewithheaders = False maxheaders = 15 while True:
""" import base64 import hashlib import os import subprocess import munkicommon # we use lots of camelCase-style names. Deal with it. # pylint: disable=C0103 DEFAULT_KEYCHAIN_NAME = 'munki.keychain' DEFAULT_KEYCHAIN_PASSWORD = '******' KEYCHAIN_DIRECTORY = os.path.join( munkicommon.pref('ManagedInstallDir'), 'Keychains') 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 ''
https://gist.github.com/pudquick/7704254 """ import os import re import subprocess import munkicommon # we use lots of camelCase-style names. Deal with it. # pylint: disable=C0103 DEFAULT_KEYCHAIN_NAME = 'munki.keychain' DEFAULT_KEYCHAIN_PASSWORD = '******' KEYCHAIN_DIRECTORY = os.path.join(munkicommon.pref('ManagedInstallDir'), 'Keychains') 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 profile_receipt_data_path(): '''Returns the path to our installed profile data store''' ManagedInstallDir = munkicommon.pref('ManagedInstallDir') return os.path.join(ManagedInstallDir, 'ConfigProfileData.plist')
try: import FoundationPlist as plistlib except ImportError: print "Using plistlib" import plistlib try: from munkicommon import pref, getsha256hash import updatecheck from fetch import (getURLitemBasename, getResourceIfChangedAtomically, MunkiDownloadError, writeCachedChecksum, getxattr, XATTR_SHA) import keychain except ImportError as err: print "Something went wrong! %s" % err MUNKI_URL = pref('SoftwareRepoURL') PKGS_URL = MUNKI_URL + '/pkgs' ICONS_URL = MUNKI_URL + '/icons' BASIC_AUTH = pref('AdditionalHttpHeaders') CACHE = '/tmp' # download functions def download_url_to_cache(url, cache, force=False): """Take a URL and downloads it to a local cache.""" cache_path = os.path.join(cache, urllib2.unquote(getURLitemBasename(url))) custom_headers = [''] if BASIC_AUTH: # custom_headers = ['Authorization: Basic %s' % BASIC_AUTH] custom_headers = BASIC_AUTH if force:
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 # some globals packagedb = os.path.join(munkicommon.pref('ManagedInstallDir'), "b.receiptdb") 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, '
def getResourceIfChangedAtomically(url, destinationpath, custom_headers=None, expected_hash=None, message=None, resume=False, verify=False, follow_redirects=False): """Gets file from a URL. Checks first if there is already a file with the necessary checksum. Then checks if the file has changed on the server, resuming or re-downloading as necessary. If the file has changed verify the pkg hash if so configured. Supported schemes are http, https, file. Returns True if a new download was required; False if the item is already in the local cache. Raises a MunkiDownloadError derived class if there is an error.""" changed = False # If we already have a downloaded file & its (cached) hash matches what # we need, do nothing, return unchanged. if resume and expected_hash and os.path.isfile(destinationpath): xattr_hash = getxattr(destinationpath, XATTR_SHA) if not xattr_hash: xattr_hash = writeCachedChecksum(destinationpath) if xattr_hash == expected_hash: #File is already current, no change. return False elif munkicommon.pref('PackageVerificationMode').lower() in [ 'hash_strict', 'hash' ]: try: os.unlink(destinationpath) except OSError: pass munkicommon.log('Cached payload does not match hash in catalog, ' 'will check if changed and redownload: %s' % destinationpath) #continue with normal if-modified-since/etag update methods. if follow_redirects != True: # If we haven't explicitly said to follow redirect, the preference decides follow_redirects = munkicommon.pref('FollowHTTPRedirects') url_parse = urlparse.urlparse(url) if url_parse.scheme in ['http', 'https']: changed = getHTTPfileIfChangedAtomically( url, destinationpath, custom_headers=custom_headers, message=message, resume=resume, follow_redirects=follow_redirects) elif url_parse.scheme == 'file': changed = getFileIfChangedAtomically(url_parse.path, destinationpath) else: raise MunkiDownloadError('Unsupported scheme for %s: %s' % (url, url_parse.scheme)) if changed and verify: (verify_ok, fhash) = verifySoftwarePackageIntegrity(destinationpath, expected_hash, always_hash=True) if not verify_ok: try: os.unlink(destinationpath) except OSError: pass raise PackageVerificationError() if fhash: writeCachedChecksum(destinationpath, fhash=fhash) return changed
def getResourceIfChangedAtomically(url, destinationpath, cert_info=None, custom_headers=None, expected_hash=None, message=None, resume=False, verify=False, follow_redirects=False): """Gets file from a URL. Checks first if there is already a file with the necessary checksum. Then checks if the file has changed on the server, resuming or re-downloading as necessary. If the file has changed verify the pkg hash if so configured. Supported schemes are http, https, file. Returns True if a new download was required; False if the item is already in the local cache. Raises a MunkiDownloadError derived class if there is an error.""" changed = False # If we already have a downloaded file & its (cached) hash matches what # we need, do nothing, return unchanged. if resume and expected_hash and os.path.isfile(destinationpath): xattr_hash = getxattr(destinationpath, XATTR_SHA) if not xattr_hash: xattr_hash = writeCachedChecksum(destinationpath) if xattr_hash == expected_hash: #File is already current, no change. return False elif munkicommon.pref('PackageVerificationMode').lower() in \ ['hash_strict', 'hash']: try: os.unlink(destinationpath) except OSError: pass munkicommon.log('Cached payload does not match hash in catalog, ' 'will check if changed and redownload: %s' % destinationpath) #continue with normal if-modified-since/etag update methods. url_parse = urlparse.urlparse(url) if url_parse.scheme in ['http', 'https']: changed = getHTTPfileIfChangedAtomically( url, destinationpath, cert_info=cert_info, custom_headers=custom_headers, message=message, resume=resume, follow_redirects=follow_redirects) elif url_parse.scheme == 'file': changed = getFileIfChangedAtomically(url_parse.path, destinationpath) else: raise MunkiDownloadError( 'Unsupported scheme for %s: %s' % (url, url_parse.scheme)) if changed and verify: (verify_ok, fhash) = verifySoftwarePackageIntegrity(destinationpath, expected_hash, always_hash=True) if not verify_ok: try: os.unlink(destinationpath) except OSError: pass raise PackageVerificationError() if fhash: writeCachedChecksum(destinationpath, fhash=fhash) return changed
def get_url(url, destinationpath, custom_headers=None, message=None, onlyifnewer=False, resume=False, follow_redirects=False): """Gets an HTTP or HTTPS URL and stores it in destination path. Returns a dictionary of headers, which includes http_result_code and http_result_description. Will raise CurlError if Gurl returns an error. Will raise HTTPError if HTTP Result code is not 2xx or 304. If destinationpath already exists, you can set 'onlyifnewer' to true to indicate you only want to download the file only if it's newer on the server. If you set resume to True, Gurl will attempt to resume an interrupted download.""" tempdownloadpath = destinationpath + '.download' if os.path.exists(tempdownloadpath) and not resume: if resume and not os.path.exists(destinationpath): os.remove(tempdownloadpath) cache_data = None if onlyifnewer and os.path.exists(destinationpath): # create a temporary Gurl object so we can extract the # stored caching data so we can download only if the # file has changed on the server gurl_obj = Gurl.alloc().initWithOptions_({'file': destinationpath}) cache_data = gurl_obj.get_stored_headers() del gurl_obj # only works with NSURLSession (10.9 and newer) ignore_system_proxy = munkicommon.pref('IgnoreSystemProxies') options = { 'url': url, 'file': tempdownloadpath, 'follow_redirects': follow_redirects, 'ignore_system_proxy': ignore_system_proxy, 'can_resume': resume, 'additional_headers': header_dict_from_list(custom_headers), 'download_only_if_changed': onlyifnewer, 'cache_data': cache_data, 'logging_function': munkicommon.display_debug2 } munkicommon.display_debug2('Options: %s' % options) # Allow middleware to modify options if middleware: munkicommon.display_debug2('Processing options through middleware') # middleware module must have process_request_options function # and must return usable options options = middleware.process_request_options(options) munkicommon.display_debug2('Options: %s' % options) connection = Gurl.alloc().initWithOptions_(options) stored_percent_complete = -1 stored_bytes_received = 0 connection.start() try: while True: # if we did `while not connection.isDone()` we'd miss printing # messages and displaying percentages if we exit the loop first connection_done = connection.isDone() if message and connection.status and connection.status != 304: # log always, display if verbose is 1 or more # also display in MunkiStatus detail field munkicommon.display_status_minor(message) # now clear message so we don't display it again message = None if (str(connection.status).startswith('2') and connection.percentComplete != -1): if connection.percentComplete != stored_percent_complete: # display percent done if it has changed stored_percent_complete = connection.percentComplete munkicommon.display_percent_done(stored_percent_complete, 100) elif connection.bytesReceived != stored_bytes_received: # if we don't have percent done info, log bytes received stored_bytes_received = connection.bytesReceived munkicommon.display_detail('Bytes received: %s', stored_bytes_received) if connection_done: break except (KeyboardInterrupt, SystemExit): # safely kill the connection then re-raise connection.cancel() raise except Exception, err: # too general, I know # Let us out! ... Safely! Unexpectedly quit dialogs are annoying... connection.cancel() # Re-raise the error as a GurlError raise GurlError(-1, str(err))
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 # some globals packagedb = os.path.join(munkicommon.pref('ManagedInstallDir'), "b.receiptdb") 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
def get_url(url, destinationpath, custom_headers=None, message=None, onlyifnewer=False, resume=False, follow_redirects=False): """Gets an HTTP or HTTPS URL and stores it in destination path. Returns a dictionary of headers, which includes http_result_code and http_result_description. Will raise GurlError if Gurl returns an error. Will raise HTTPError if HTTP Result code is not 2xx or 304. If destinationpath already exists, you can set 'onlyifnewer' to true to indicate you only want to download the file only if it's newer on the server. If you set resume to True, Gurl will attempt to resume an interrupted download.""" tempdownloadpath = destinationpath + '.download' if os.path.exists(tempdownloadpath) and not resume: if resume and not os.path.exists(destinationpath): os.remove(tempdownloadpath) cache_data = None if onlyifnewer and os.path.exists(destinationpath): # create a temporary Gurl object so we can extract the # stored caching data so we can download only if the # file has changed on the server gurl_obj = Gurl.alloc().initWithOptions_({'file': destinationpath}) cache_data = gurl_obj.get_stored_headers() del gurl_obj # only works with NSURLSession (10.9 and newer) ignore_system_proxy = munkicommon.pref('IgnoreSystemProxies') options = {'url': url, 'file': tempdownloadpath, 'follow_redirects': follow_redirects, 'ignore_system_proxy': ignore_system_proxy, 'can_resume': resume, 'additional_headers': header_dict_from_list(custom_headers), 'download_only_if_changed': onlyifnewer, 'cache_data': cache_data, 'logging_function': munkicommon.display_debug2} munkicommon.display_debug2('Options: %s' % options) # Allow middleware to modify options if middleware: munkicommon.display_debug2('Processing options through middleware') # middleware module must have process_request_options function # and must return usable options options = middleware.process_request_options(options) munkicommon.display_debug2('Options: %s' % options) connection = Gurl.alloc().initWithOptions_(options) stored_percent_complete = -1 stored_bytes_received = 0 connection.start() try: while True: # if we did `while not connection.isDone()` we'd miss printing # messages and displaying percentages if we exit the loop first connection_done = connection.isDone() if message and connection.status and connection.status != 304: # log always, display if verbose is 1 or more # also display in MunkiStatus detail field munkicommon.display_status_minor(message) # now clear message so we don't display it again message = None if (str(connection.status).startswith('2') and connection.percentComplete != -1): if connection.percentComplete != stored_percent_complete: # display percent done if it has changed stored_percent_complete = connection.percentComplete munkicommon.display_percent_done( stored_percent_complete, 100) elif connection.bytesReceived != stored_bytes_received: # if we don't have percent done info, log bytes received stored_bytes_received = connection.bytesReceived munkicommon.display_detail( 'Bytes received: %s', stored_bytes_received) if connection_done: break except (KeyboardInterrupt, SystemExit): # safely kill the connection then re-raise connection.cancel() raise except Exception, err: # too general, I know # Let us out! ... Safely! Unexpectedly quit dialogs are annoying... connection.cancel() # Re-raise the error as a GurlError raise GurlError(-1, str(err))