def run_softwareupdate(options_list, stop_allowed=False, mode=None, results=None): '''Runs /usr/sbin/softwareupdate with options. Provides user feedback via command line or MunkiStatus''' if results == None: # we're not interested in the results, # but need to create a temporary dict anyway results = {} # wrapping with /usr/bin/script so we can get pseudo-unbuffered # output cmd = ['/usr/bin/script', '-q', '-t', '1', '/dev/null', '/usr/sbin/softwareupdate'] osvers = int(os.uname()[2].split('.')[0]) if osvers > 9: cmd.append('-v') cmd.extend(options_list) # bump up verboseness so we get download percentage done feedback. oldverbose = munkicommon.verbose munkicommon.verbose = oldverbose + 1 try: job = launchd.Job(cmd) job.start() except launchd.LaunchdJobException, err: munkicommon.display_warning( 'Error with launchd job (%s): %s', cmd, str(err)) munkicommon.display_warning('Skipping softwareupdate run.') return -3
def run_softwareupdate(options_list, stop_allowed=False, mode=None, results=None): '''Runs /usr/sbin/softwareupdate with options. Provides user feedback via command line or MunkiStatus''' if results == None: # we're not interested in the results, # but need to create a temporary dict anyway results = {} # wrapping with /usr/bin/script so we can get pseudo-unbuffered # output cmd = [ '/usr/bin/script', '-q', '-t', '1', '/dev/null', '/usr/sbin/softwareupdate' ] osvers = int(os.uname()[2].split('.')[0]) if osvers > 9: cmd.append('-v') cmd.extend(options_list) # bump up verboseness so we get download percentage done feedback. oldverbose = munkicommon.verbose munkicommon.verbose = oldverbose + 1 try: job = launchd.Job(cmd) job.start() except launchd.LaunchdJobException, err: munkicommon.display_warning('Error with launchd job (%s): %s', cmd, str(err)) munkicommon.display_warning('Skipping softwareupdate run.') return -3
def checkForSoftwareUpdates(forcecheck=True): '''Does our Apple Software Update check if needed''' sucatalog = os.path.join(swupdCacheDir(temp=False), 'apple.sucatalog') catcksum = munkicommon.getsha256hash(sucatalog) try: catalogchanged = cacheAppleSUScatalog() except ReplicationError, err: munkicommon.display_warning('Could not download Apple SUS catalog:') munkicommon.display_warning('\t', err) return False
def import_middleware(): '''Check munki folder for a python file that starts with 'middleware'. If the file exists and has a callable 'process_request_options' attribute, the module is loaded under the 'middleware' name''' required_function_name = 'process_request_options' munki_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) for filename in os.listdir(munki_dir): if (filename.startswith('middleware') and os.path.splitext(filename)[1] == '.py'): name = os.path.splitext(filename)[0] filepath = os.path.join(munki_dir, filename) _tmp = imp.load_source(name, filepath) if hasattr(_tmp, required_function_name): if callable(getattr(_tmp, required_function_name)): munkicommon.display_debug1( 'Loading middleware module %s' % filename) globals()['middleware'] = _tmp return else: munkicommon.display_warning( '%s attribute in %s is not callable.' % (required_function_name, filepath)) munkicommon.display_warning('Ignoring %s' % filepath) else: munkicommon.display_warning( '%s does not have a %s function' % (filepath, required_function_name)) munkicommon.display_warning('Ignoring %s' % filepath) return
def import_middleware(): '''Check munki folder for a python file that starts with 'middleware'. If the file exists and has a callable 'process_request_options' attribute, the module is loaded under the 'middleware' name''' required_function_name = 'process_request_options' munki_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) for filename in os.listdir(munki_dir): if (filename.startswith('middleware') and os.path.splitext(filename)[1] == '.py'): name = os.path.splitext(filename)[0] filepath = os.path.join(munki_dir, filename) _tmp = imp.load_source(name, filepath) if hasattr(_tmp, required_function_name): if callable(getattr(_tmp, required_function_name)): munkicommon.display_debug1('Loading middleware module %s' % filename) globals()['middleware'] = _tmp return else: munkicommon.display_warning( '%s attribute in %s is not callable.' % (required_function_name, filepath)) munkicommon.display_warning('Ignoring %s' % filepath) else: munkicommon.display_warning('%s does not have a %s function' % (filepath, required_function_name)) munkicommon.display_warning('Ignoring %s' % filepath) return
def setupSoftwareUpdateCheck(): '''Set defaults for root user and current host. Needed for Leopard.''' CFPreferencesSetValue('AgreedToLicenseAgreement', True, 'com.apple.SoftwareUpdate', kCFPreferencesCurrentUser, kCFPreferencesCurrentHost) CFPreferencesSetValue('AutomaticDownload', True, 'com.apple.SoftwareUpdate', kCFPreferencesCurrentUser, kCFPreferencesCurrentHost) CFPreferencesSetValue('LaunchAppInBackground', True, 'com.apple.SoftwareUpdate', kCFPreferencesCurrentUser, kCFPreferencesCurrentHost) if not CFPreferencesAppSynchronize('com.apple.SoftwareUpdate'): munkicommon.display_warning( 'Error setting com.apple.SoftwareUpdate ByHost preferences')
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 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 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 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 leopardDownloadAvailableUpdates(catalogURL): '''Clunky process to download Apple updates in Leopard''' softwareupdateapp = "/System/Library/CoreServices/Software Update.app" softwareupdateappbin = os.path.join(softwareupdateapp, "Contents/MacOS/Software Update") softwareupdatecheck = os.path.join(softwareupdateapp, "Contents/Resources/SoftwareUpdateCheck") try: # record mode of Software Update.app executable rawmode = os.stat(softwareupdateappbin).st_mode oldmode = stat.S_IMODE(rawmode) # set mode of Software Update.app executable so it won't launch # yes, this is a hack. So sue me. os.chmod(softwareupdateappbin, 0) except OSError, err: munkicommon.display_warning( 'Error with os.stat(Softare Update.app): %s', str(err)) munkicommon.display_warning('Skipping Apple SUS check.') return -2
def leopardDownloadAvailableUpdates(catalogURL): '''Clunky process to download Apple updates in Leopard''' softwareupdateapp = "/System/Library/CoreServices/Software Update.app" softwareupdateappbin = os.path.join(softwareupdateapp, "Contents/MacOS/Software Update") softwareupdatecheck = os.path.join( softwareupdateapp, "Contents/Resources/SoftwareUpdateCheck") try: # record mode of Software Update.app executable rawmode = os.stat(softwareupdateappbin).st_mode oldmode = stat.S_IMODE(rawmode) # set mode of Software Update.app executable so it won't launch # yes, this is a hack. So sue me. os.chmod(softwareupdateappbin, 0) except OSError, err: munkicommon.display_warning( 'Error with os.stat(Softare Update.app): %s', str(err)) munkicommon.display_warning('Skipping Apple SUS check.') return -2
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 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 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 test_display_warning_with_str_msg_str_arg(self): munkicommon.display_warning(MSG_STR, ARG_STR)
def test_display_warning_with_unicode_msg_unicode_arg(self): munkicommon.display_warning(MSG_UNI, ARG_UNI)
munkicommon.display_status(output) results['installed'].append(output[10:]) else: pass # don't display. # softwareupdate logging "Installed" at the end of a # successful download-only session is odd. elif output.startswith('Done '): # 10.5. Successful install of package name. munkicommon.display_status(output) results['installed'].append(output[5:]) elif output.startswith('Downloading ') and mode == 'install': # This is 10.5 & 10.7 behaviour # for an entirely missing subpackage. munkicommon.display_warning( 'A necessary subpackage is not available on disk ' 'during an Apple Software Update installation ' 'run: %s' % output) results['download'].append(output[12:]) elif output.startswith('Package failed:'): # Doesn't tell us which package. munkicommon.display_error('Apple update failed to install: %s' % output) elif output.startswith('x '): # don't display this, it's just confusing pass elif 'Missing bundle identifier' in output: # don't display this, it's noise pass elif output == '': pass elif osvers == 9 and output[0] in '.012468':
munkicommon.display_status(output) results['installed'].append(output[10:]) else: pass # don't display. # softwareupdate logging "Installed" at the end of a # successful download-only session is odd. elif output.startswith('Done '): # 10.5. Successful install of package name. munkicommon.display_status(output) results['installed'].append(output[5:]) elif output.startswith('Downloading ') and mode == 'install': # This is 10.5 & 10.7 behaviour # for an entirely missing subpackage. munkicommon.display_warning( 'A necessary subpackage is not available on disk ' 'during an Apple Software Update installation ' 'run: %s' % output) results['download'].append(output[12:]) elif output.startswith('Package failed:'): # Doesn't tell us which package. munkicommon.display_error( 'Apple update failed to install: %s' % output) elif output.startswith('x '): # don't display this, it's just confusing pass elif 'Missing bundle identifier' in output: # don't display this, it's noise pass elif output == '': pass elif osvers == 9 and output[0] in '.012468':
def curl(url, destinationpath, cert_info=None, custom_headers=None, donotrecurse=False, etag=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 curl 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 have an ETag from the current destination path, you can pass that to download the file only if it is different. Finally, if you set resume to True, curl will attempt to resume an interrupted download. You'll get an error if the existing file is complete; if the file has changed since the first download attempt, you'll get a mess.""" header = {} header['http_result_code'] = '000' header['http_result_description'] = '' curldirectivepath = os.path.join(munkicommon.tmpdir, 'curl_temp') tempdownloadpath = destinationpath + '.download' # we're writing all the curl options to a file and passing that to # curl so we avoid the problem of URLs showing up in a process listing try: fileobj = open(curldirectivepath, mode='w') print >> fileobj, 'silent' # no progress meter print >> fileobj, 'show-error' # print error msg to stderr print >> fileobj, 'no-buffer' # don't buffer output print >> fileobj, 'fail' # throw error if download fails print >> fileobj, 'dump-header -' # dump headers to stdout print >> fileobj, 'speed-time = 30' # give up if too slow d/l print >> fileobj, 'output = "%s"' % tempdownloadpath print >> fileobj, 'ciphers = HIGH,!ADH' #use only secure >=128 bit SSL print >> fileobj, 'url = "%s"' % url munkicommon.display_debug2('follow_redirects is %s', follow_redirects) if follow_redirects: print >> fileobj, 'location' # follow redirects if cert_info: cacert = cert_info.get('cacert') capath = cert_info.get('capath') cert = cert_info.get('cert') key = cert_info.get('key') if cacert: if not os.path.isfile(cacert): raise CurlError(-1, 'No CA cert at %s' % cacert) print >> fileobj, 'cacert = "%s"' % cacert if capath: if not os.path.isdir(capath): raise CurlError(-2, 'No CA directory at %s' % capath) print >> fileobj, 'capath = "%s"' % capath if cert: if not os.path.isfile(cert): raise CurlError(-3, 'No client cert at %s' % cert) print >> fileobj, 'cert = "%s"' % cert if key: if not os.path.isfile(key): raise CurlError(-4, 'No client key at %s' % key) print >> fileobj, 'key = "%s"' % key if os.path.exists(destinationpath): if etag: escaped_etag = etag.replace('"','\\"') print >> fileobj, ('header = "If-None-Match: %s"' % escaped_etag) elif onlyifnewer: print >> fileobj, 'time-cond = "%s"' % destinationpath else: os.remove(destinationpath) if os.path.exists(tempdownloadpath): if resume and not os.path.exists(destinationpath): # let's try to resume this download print >> fileobj, 'continue-at -' # if an existing etag, only resume if etags still match. tempetag = getxattr(tempdownloadpath, XATTR_ETAG) if tempetag: # Note: If-Range is more efficient, but the response # confuses curl (Error: 33 if etag not match). escaped_etag = tempetag.replace('"','\\"') print >> fileobj, ('header = "If-Match: %s"' % escaped_etag) else: os.remove(tempdownloadpath) # Add any additional headers specified in custom_headers # custom_headers must be an array of strings with valid HTTP # header format. if custom_headers: for custom_header in custom_headers: custom_header = custom_header.strip().encode('utf-8') if re.search(r'^[\w-]+:.+', custom_header): print >> fileobj, ('header = "%s"' % custom_header) else: munkicommon.display_warning( 'Skipping invalid HTTP header: %s' % custom_header) fileobj.close() except Exception, e: raise CurlError(-5, 'Error writing curl directive: %s' % str(e))
def installAppleUpdates(): '''Uses /usr/sbin/softwareupdate to install previously downloaded updates. Returns True if a restart is needed after install, False otherwise.''' msg = "Installing available Apple Software Updates..." if munkicommon.munkistatusoutput: munkistatus.message(msg) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(msg) else: munkicommon.display_status(msg) restartneeded = restartNeeded() # use our filtered local catalog catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/local_install.sucatalog') if not os.path.exists(catalogpath): munkicommon.display_error( 'Missing local Software Update catalog at %s', catalogpath) # didn't do anything, so no restart needed return False installlist = getSoftwareUpdateInfo() installresults = {'installed':[], 'download':[]} catalogURL = 'file://localhost' + urllib2.quote(catalogpath) retcode = run_softwareupdate(['--CatalogURL', catalogURL, '-i', '-a'], mode='install', results=installresults) if not 'InstallResults' in munkicommon.report: munkicommon.report['InstallResults'] = [] for item in installlist: rep = {} rep['name'] = item.get('display_name') rep['version'] = item.get('version_to_install', '') rep['applesus'] = True rep['productKey'] = item.get('productKey', '') message = "Apple Software Update install of %s-%s: %s" if rep['name'] in installresults['installed']: rep['status'] = 0 install_status = 'SUCCESSFUL' elif rep['name'] in installresults['download']: rep['status'] = -1 install_status = 'FAILED due to missing package.' munkicommon.display_warning( 'Apple update %s, %s failed. A sub-package was missing ' 'on disk at time of install.' % (rep['name'], rep['productKey'])) else: rep['status'] = -2 install_status = 'FAILED for unknown reason' munkicommon.display_warning( 'Apple update %s, %s failed to install. No record of ' 'success or failure.' % (rep['name'],rep['productKey'])) munkicommon.report['InstallResults'].append(rep) log_msg = message % (rep['name'], rep['version'], install_status) munkicommon.log(log_msg, "Install.log") if retcode: # there was an error munkicommon.display_error("softwareupdate error: %s" % retcode) # clean up our now stale local cache cachedir = os.path.join(swupdCacheDir()) if os.path.exists(cachedir): unused_retcode = subprocess.call(['/bin/rm', '-rf', cachedir]) # remove the now invalid appleUpdatesFile try: os.unlink(appleUpdatesFile()) except OSError: pass # Also clear our pref value for last check date. We may have # just installed an update which is a pre-req for some other update. # Let's check again soon. munkicommon.set_pref('LastAppleSoftwareUpdateCheck', None) return restartneeded
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 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 installAppleUpdates(): '''Uses /usr/sbin/softwareupdate to install previously downloaded updates. Returns True if a restart is needed after install, False otherwise.''' msg = "Installing available Apple Software Updates..." if munkicommon.munkistatusoutput: munkistatus.message(msg) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(msg) else: munkicommon.display_status(msg) restartneeded = restartNeeded() # use our filtered local catalog catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/local_install.sucatalog') if not os.path.exists(catalogpath): munkicommon.display_error( 'Missing local Software Update catalog at %s', catalogpath) # didn't do anything, so no restart needed return False installlist = getSoftwareUpdateInfo() installresults = {'installed': [], 'download': []} catalogURL = 'file://localhost' + urllib2.quote(catalogpath) retcode = run_softwareupdate(['--CatalogURL', catalogURL, '-i', '-a'], mode='install', results=installresults) if not 'InstallResults' in munkicommon.report: munkicommon.report['InstallResults'] = [] for item in installlist: rep = {} rep['name'] = item.get('display_name') rep['version'] = item.get('version_to_install', '') rep['applesus'] = True rep['productKey'] = item.get('productKey', '') message = "Apple Software Update install of %s-%s: %s" if rep['name'] in installresults['installed']: rep['status'] = 0 install_status = 'SUCCESSFUL' elif rep['name'] in installresults['download']: rep['status'] = -1 install_status = 'FAILED due to missing package.' munkicommon.display_warning( 'Apple update %s, %s failed. A sub-package was missing ' 'on disk at time of install.' % (rep['name'], rep['productKey'])) else: rep['status'] = -2 install_status = 'FAILED for unknown reason' munkicommon.display_warning( 'Apple update %s, %s failed to install. No record of ' 'success or failure.' % (rep['name'], rep['productKey'])) munkicommon.report['InstallResults'].append(rep) log_msg = message % (rep['name'], rep['version'], install_status) munkicommon.log(log_msg, "Install.log") if retcode: # there was an error munkicommon.display_error("softwareupdate error: %s" % retcode) # clean up our now stale local cache cachedir = os.path.join(swupdCacheDir()) if os.path.exists(cachedir): unused_retcode = subprocess.call(['/bin/rm', '-rf', cachedir]) # remove the now invalid appleUpdatesFile try: os.unlink(appleUpdatesFile()) except OSError: pass # Also clear our pref value for last check date. We may have # just installed an update which is a pre-req for some other update. # Let's check again soon. munkicommon.set_pref('LastAppleSoftwareUpdateCheck', None) return restartneeded