mirror of
https://github.com/munki/munki.git
synced 2026-04-24 05:49:42 -05:00
Merge branch 'status-launch-and-cache-on-checksum'
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
# .DS_Store files!
|
||||
.DS_Store
|
||||
|
||||
# /code/Managed Software Update
|
||||
# build directory changes with each build
|
||||
|
||||
@@ -143,6 +143,9 @@ class MSUAppDelegate(NSObject):
|
||||
or consoleuser == u"loginwindow"):
|
||||
# Status Window only, so we should just quit
|
||||
munki.log("MSU", "exit_munkistatus")
|
||||
# clear launch trigger file so we aren't immediately
|
||||
# relaunched by launchd
|
||||
munki.clearLaunchTrigger()
|
||||
NSApp.terminate_(self)
|
||||
|
||||
# The managedsoftwareupdate run will have changed state preferences
|
||||
@@ -347,6 +350,7 @@ class MSUAppDelegate(NSObject):
|
||||
self.update_view_controller.updateNowBtn.setEnabled_(YES)
|
||||
self.update_view_controller.optionalSoftwareBtn.setHidden_(YES)
|
||||
else:
|
||||
self._listofupdates = []
|
||||
self.update_view_controller.updateNowBtn.setEnabled_(NO)
|
||||
self.getOptionalInstalls()
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ from Foundation import CFPreferencesAppSynchronize
|
||||
INSTALLATLOGOUTFILE = "/private/tmp/com.googlecode.munki.installatlogout"
|
||||
UPDATECHECKLAUNCHFILE = \
|
||||
"/private/tmp/.com.googlecode.munki.updatecheck.launchd"
|
||||
INSTALLWITHOUTLOGOUTFILE = \
|
||||
"/private/tmp/.com.googlecode.munki.managedinstall.launchd"
|
||||
MSULOGDIR = \
|
||||
"/Users/Shared/.com.googlecode.munki.ManagedSoftwareUpdate.logs"
|
||||
MSULOGFILE = "%s.log"
|
||||
@@ -253,8 +255,12 @@ def stringFromDate(nsdate):
|
||||
|
||||
def startUpdateCheck():
|
||||
'''Does launchd magic to run managedsoftwareupdate as root.'''
|
||||
result = call(["/usr/bin/touch", UPDATECHECKLAUNCHFILE])
|
||||
return result
|
||||
try:
|
||||
if not os.path.exists(UPDATECHECKLAUNCHFILE):
|
||||
open(UPDATECHECKLAUNCHFILE, 'w').close()
|
||||
return 0
|
||||
except (OSError, IOError):
|
||||
return 1
|
||||
|
||||
|
||||
def getAppleUpdates():
|
||||
@@ -348,22 +354,35 @@ def logoutAndUpdate():
|
||||
|
||||
try:
|
||||
if not os.path.exists(INSTALLATLOGOUTFILE):
|
||||
f = open(INSTALLATLOGOUTFILE, 'w')
|
||||
f.close()
|
||||
open(INSTALLATLOGOUTFILE, 'w').close()
|
||||
logoutNow()
|
||||
except (OSError, IOError):
|
||||
return 1
|
||||
|
||||
|
||||
def clearLaunchTrigger():
|
||||
'''Clear the trigger file that fast-launches us at loginwindow.
|
||||
typically because we have been launched in statusmode at the
|
||||
loginwindow to perform a logout-install.'''
|
||||
try:
|
||||
if os.path.exists(INSTALLATLOGOUTFILE):
|
||||
os.unlink(INSTALLATLOGOUTFILE)
|
||||
except (OSError, IOError):
|
||||
return 1
|
||||
|
||||
|
||||
def justUpdate():
|
||||
'''Trigger managedinstaller via launchd KeepAlive path trigger
|
||||
We touch a file that launchd is is watching
|
||||
launchd, in turn,
|
||||
launches managedsoftwareupdate --installwithnologout as root'''
|
||||
cmd = ["/usr/bin/touch",
|
||||
"/private/tmp/.com.googlecode.munki.managedinstall.launchd"]
|
||||
return call(cmd)
|
||||
|
||||
try:
|
||||
if not os.path.exists(INSTALLWITHOUTLOGOUTFILE):
|
||||
open(INSTALLWITHOUTLOGOUTFILE, 'w').close()
|
||||
return 0
|
||||
except (OSError, IOError):
|
||||
return 1
|
||||
|
||||
|
||||
def getRunningProcesses():
|
||||
"""Returns a list of paths of running processes"""
|
||||
|
||||
@@ -143,6 +143,50 @@ def initMunkiDirs():
|
||||
return True
|
||||
|
||||
|
||||
def runScript(script, display_name, runtype):
|
||||
"""Run an external script. Do not run if the permissions on the external
|
||||
script file are weaker than the current executable."""
|
||||
result = 0
|
||||
if os.path.exists(script):
|
||||
munkicommon.display_status('Performing %s tasks...' % display_name)
|
||||
else:
|
||||
return result
|
||||
|
||||
try:
|
||||
utils.verifyFileOnlyWritableByMunkiAndRoot(script)
|
||||
except utils.VerifyFilePermissionsError, e:
|
||||
# preflight/postflight is insecure, but if the currently executing
|
||||
# file is insecure too we are no worse off.
|
||||
try:
|
||||
utils.verifyFileOnlyWritableByMunkiAndRoot(__file__)
|
||||
except utils.VerifyFilePermissionsError, e:
|
||||
# OK, managedsoftwareupdate is insecure anyway - warn & execute.
|
||||
munkicommon.display_warning('Multiple munki executable scripts '
|
||||
'have insecure file permissions. Executing '
|
||||
'%s anyway. Error: %s' % (display_name, e))
|
||||
else:
|
||||
# Just the preflight/postflight is insecure. Do not execute.
|
||||
munkicommon.display_warning('Skipping execution of %s due to '
|
||||
'insecure file permissions. Error: %s' % (display_name, e))
|
||||
return result
|
||||
|
||||
try:
|
||||
result, stdout, stderr = utils.runExternalScript(
|
||||
script, allow_insecure=True, script_args=[runtype])
|
||||
if result:
|
||||
munkicommon.display_info('%s return code: %d'
|
||||
% (display_name, result))
|
||||
if stdout:
|
||||
munkicommon.display_info('%s stdout: %s' % (display_name, stdout))
|
||||
if stderr:
|
||||
munkicommon.display_info('%s stderr: %s' % (display_name, stderr))
|
||||
except utils.ScriptNotFoundError:
|
||||
pass # script is not required, so pass
|
||||
except utils.RunExternalScriptError, e:
|
||||
munkicommon.display_warning(str(e))
|
||||
return result
|
||||
|
||||
|
||||
def doInstallTasks(only_unattended=False):
|
||||
"""Perform our installation/removal tasks.
|
||||
|
||||
@@ -426,6 +470,7 @@ def main():
|
||||
# delete triggerfile if _not_ checkandinstallatstartup
|
||||
os.unlink(filename)
|
||||
if not user_triggered:
|
||||
# no trigger file was found -- how'd we get launched?
|
||||
munkicommon.cleanUpTmpDir()
|
||||
exit(0)
|
||||
|
||||
@@ -467,24 +512,22 @@ def main():
|
||||
# set munkicommon globals
|
||||
munkicommon.munkistatusoutput = options.munkistatusoutput
|
||||
munkicommon.verbose = options.verbose
|
||||
|
||||
if options.installonly:
|
||||
# we're only installing, not checking, so we should copy
|
||||
# some report values from the prior run
|
||||
munkicommon.readreport()
|
||||
|
||||
# start a new report
|
||||
munkicommon.report['StartTime'] = munkicommon.format_time()
|
||||
munkicommon.report['RunType'] = runtype
|
||||
# Clearing arrays must be run before any call to display_warning/error.
|
||||
munkicommon.report['Errors'] = []
|
||||
munkicommon.report['Warnings'] = []
|
||||
|
||||
# run the preflight script if it exists
|
||||
preflightscript = os.path.join(scriptdir, 'preflight')
|
||||
if os.path.exists(preflightscript):
|
||||
munkicommon.display_status('Performing preflight tasks...')
|
||||
try:
|
||||
result, stdout, stderr = utils.runExternalScript(
|
||||
preflightscript, runtype)
|
||||
if stdout:
|
||||
munkicommon.display_info('preflight stdout: %s', stdout)
|
||||
if stderr:
|
||||
munkicommon.display_info('preflight stderr: %s', stderr)
|
||||
except utils.ScriptNotFoundError:
|
||||
result = 0
|
||||
pass # script is not required, so pass
|
||||
except utils.RunExternalScriptError, e:
|
||||
result = 0
|
||||
munkicommon.display_warning(str(e))
|
||||
result = runScript(preflightscript, 'preflight', runtype)
|
||||
|
||||
if result:
|
||||
# non-zero return code means don't run
|
||||
@@ -562,19 +605,9 @@ def main():
|
||||
munkicommon.reset_warnings()
|
||||
munkicommon.rotate_main_log()
|
||||
|
||||
if options.installonly:
|
||||
# we're only installing, not checking, so we should copy
|
||||
# some report values from the prior run
|
||||
munkicommon.readreport()
|
||||
|
||||
# archive the previous session's report
|
||||
munkicommon.archive_report()
|
||||
# start a new report
|
||||
munkicommon.report['StartTime'] = munkicommon.format_time()
|
||||
munkicommon.report['RunType'] = runtype
|
||||
munkicommon.report['Errors'] = []
|
||||
munkicommon.report['Warnings'] = []
|
||||
|
||||
|
||||
munkicommon.log("### Starting managedsoftwareupdate run ###")
|
||||
if options.verbose:
|
||||
print 'Managed Software Update Tool'
|
||||
@@ -632,7 +665,8 @@ def main():
|
||||
munkicommon.savereport()
|
||||
exit(-1)
|
||||
|
||||
# send a notification event so MSU can update its display if needed
|
||||
# send a notification event so MSU can update its display
|
||||
# if needed
|
||||
sendUpdateNotification()
|
||||
|
||||
mustrestart = False
|
||||
@@ -660,7 +694,15 @@ def main():
|
||||
'not idle (keyboard or mouse activity).')
|
||||
else: # there are GUI users
|
||||
unused_force_action = updatecheck.checkForceInstallPackages()
|
||||
doInstallTasks(only_unattended=True)
|
||||
if not munkicommon.pref('SuppressAutoInstall'):
|
||||
doInstallTasks(only_unattended=True)
|
||||
else:
|
||||
munkicommon.log('Skipping unattended installs because '
|
||||
'SuppressAutoInstall is true.')
|
||||
# send a notification event so MSU can update its display
|
||||
# if needed
|
||||
sendUpdateNotification()
|
||||
|
||||
force_action = updatecheck.checkForceInstallPackages()
|
||||
# if any installs are still requiring force actions, just
|
||||
# initiate a logout to get started. blocking apps might
|
||||
@@ -674,6 +716,10 @@ def main():
|
||||
# however Apple Updates have not been affected by the
|
||||
# unattended install tasks (so that check is still valid).
|
||||
if appleupdatesavailable or munkiUpdatesAvailable():
|
||||
# it may have been more than a minute since we ran our
|
||||
# original updatecheck so tickle the updatecheck time
|
||||
# so MSU.app knows to display results immediately
|
||||
recordUpdateCheckResult(1)
|
||||
consoleuser = munkicommon.getconsoleuser()
|
||||
if consoleuser == u'loginwindow':
|
||||
# someone is logged in, but we're sitting at
|
||||
@@ -716,21 +762,7 @@ def main():
|
||||
|
||||
# run the postflight script if it exists
|
||||
postflightscript = os.path.join(scriptdir, 'postflight')
|
||||
if os.path.exists(postflightscript):
|
||||
munkicommon.display_status('Performing postflight tasks...')
|
||||
try:
|
||||
result, stdout, stderr = utils.runExternalScript(
|
||||
postflightscript, runtype)
|
||||
if result:
|
||||
munkicommon.display_info('postflight return code: %d' % result)
|
||||
if stdout:
|
||||
munkicommon.display_info('postflight stdout: %s', stdout)
|
||||
if stderr:
|
||||
munkicommon.display_info('postflight stderr: %s', stderr)
|
||||
except utils.ScriptNotFoundError:
|
||||
pass # script is not required, so pass
|
||||
except utils.RunExternalScriptError, e:
|
||||
munkicommon.display_warning(str(e))
|
||||
result = runScript(postflightscript, 'postflight', runtype)
|
||||
# we ignore the result of the postflight
|
||||
|
||||
munkicommon.cleanUpTmpDir()
|
||||
@@ -757,13 +789,24 @@ def main():
|
||||
# no-one is logged in and the machine has been idle
|
||||
# for a few seconds; kill the loginwindow
|
||||
# (which will cause us to run again)
|
||||
munkicommon.log(
|
||||
'Killing loginwindow so we will run again...')
|
||||
cmd = ['/usr/bin/killall', 'loginwindow']
|
||||
unused_retcode = subprocess.call(cmd)
|
||||
#munkicommon.log(
|
||||
# 'Killing loginwindow so we will run again...')
|
||||
#cmd = ['/usr/bin/killall', 'loginwindow']
|
||||
#unused_retcode = subprocess.call(cmd)
|
||||
# with the new LaunchAgent, we don't have to kill
|
||||
# the loginwindow
|
||||
pass
|
||||
else:
|
||||
# if the trigger file is present when we exit, we'll
|
||||
# be relaunched by launchd, so we need to remove it
|
||||
# to prevent automatic relaunch.
|
||||
munkicommon.log(
|
||||
'System not idle -- skipping killing loginwindow')
|
||||
'System not idle -- '
|
||||
'removing trigger file to prevent relaunch')
|
||||
try:
|
||||
os.unlink(checkandinstallatstartupflag)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -684,6 +684,7 @@ class AppleUpdates(object):
|
||||
return
|
||||
apple_updates = pl_dict.get('AppleUpdates', [])
|
||||
if apple_updates:
|
||||
munkicommon.report['AppleUpdates'] = apple_updates
|
||||
munkicommon.display_info(
|
||||
'The following Apple Software Updates are available to '
|
||||
'install:')
|
||||
|
||||
@@ -47,6 +47,10 @@ from Foundation import NSDate
|
||||
# This many hours before a force install deadline, start notifying the user.
|
||||
FORCE_INSTALL_WARNING_HOURS = 4
|
||||
|
||||
# XATTR name storing the ETAG of the file when downloaded via http(s).
|
||||
XATTR_ETAG = 'com.googlecode.munki.etag'
|
||||
# XATTR name storing the sha256 of the file after original download by munki.
|
||||
XATTR_SHA = 'com.googlecode.munki.sha256'
|
||||
|
||||
def makeCatalogDB(catalogitems):
|
||||
"""Takes an array of catalog items and builds some indexes so we can
|
||||
@@ -745,22 +749,19 @@ def download_installeritem(item_pl, installinfo, uninstalling=False):
|
||||
oldverbose = munkicommon.verbose
|
||||
munkicommon.verbose = oldverbose + 1
|
||||
dl_message = 'Downloading %s...' % pkgname
|
||||
expected_hash = item_pl.get(item_hash_key, None)
|
||||
try:
|
||||
changed = getResourceIfChangedAtomically(pkgurl, destinationpath,
|
||||
resume=True,
|
||||
message=dl_message)
|
||||
message=dl_message,
|
||||
expected_hash=expected_hash,
|
||||
verify=True)
|
||||
except MunkiDownloadError:
|
||||
munkicommon.verbose = oldverbose
|
||||
raise
|
||||
|
||||
# set verboseness back.
|
||||
munkicommon.verbose = oldverbose
|
||||
if changed:
|
||||
package_verified = verifySoftwarePackageIntegrity(destinationpath,
|
||||
item_pl,
|
||||
item_hash_key)
|
||||
if not package_verified:
|
||||
raise PackageVerificationError()
|
||||
|
||||
|
||||
def isItemInInstallInfo(manifestitem_pl, thelist, vers=''):
|
||||
@@ -1202,7 +1203,7 @@ def evidenceThisIsInstalled(item_pl):
|
||||
return False
|
||||
|
||||
|
||||
def verifySoftwarePackageIntegrity(file_path, item_pl, item_key):
|
||||
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
|
||||
@@ -1218,47 +1219,54 @@ def verifySoftwarePackageIntegrity(file_path, item_pl, item_key):
|
||||
|
||||
Args:
|
||||
file_path: The file to check integrity on.
|
||||
item_pl: The item plist which contains the reference values.
|
||||
item_key: The name of the key in plist which contains the hash.
|
||||
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 = getInstallerItemBasename(file_path)
|
||||
if always_hash:
|
||||
chash = munkicommon.getsha256hash(file_path)
|
||||
|
||||
if not mode:
|
||||
return True
|
||||
return (True, chash)
|
||||
elif mode.lower() == 'none':
|
||||
munkicommon.display_warning('Package integrity checking is disabled.')
|
||||
return True
|
||||
return (True, chash)
|
||||
elif mode.lower() == 'hash' or mode.lower() == 'hash_strict':
|
||||
if item_key in item_pl:
|
||||
if item_hash:
|
||||
munkicommon.display_status('Verifying package integrity...')
|
||||
item_hash = item_pl[item_key]
|
||||
if (item_hash is not 'N/A' and
|
||||
item_hash == munkicommon.getsha256hash(file_path)):
|
||||
return True
|
||||
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_pl.get('name'))
|
||||
return False
|
||||
item_name)
|
||||
return (False, chash)
|
||||
else:
|
||||
if mode.lower() == 'hash_strict':
|
||||
munkicommon.display_error(
|
||||
'Reference hash value for %s is missing in catalog.'
|
||||
% item_pl.get('name'))
|
||||
return False
|
||||
% item_name)
|
||||
return (False, chash)
|
||||
else:
|
||||
munkicommon.display_warning(
|
||||
'Reference hash value missing for %s -- package '
|
||||
'integrity verification skipped.' % item_pl.get('name'))
|
||||
return True
|
||||
'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
|
||||
return (False, chash)
|
||||
|
||||
|
||||
def getAutoRemovalItems(installinfo, cataloglist):
|
||||
@@ -2334,11 +2342,7 @@ def curl(url, destinationpath, onlyifnewer=False, etag=None, resume=False,
|
||||
# let's try to resume this download
|
||||
print >> fileobj, 'continue-at -'
|
||||
# if an existing etag, only resume if etags still match.
|
||||
tempetag = None
|
||||
if ('com.googlecode.munki.etag' in
|
||||
xattr.listxattr(tempdownloadpath)):
|
||||
tempetag = xattr.getxattr(tempdownloadpath,
|
||||
'com.googlecode.munki.etag')
|
||||
tempetag = getxattr(tempdownloadpath, XATTR_ETAG)
|
||||
if tempetag:
|
||||
# Note: If-Range is more efficient, but the response
|
||||
# confuses curl (Error: 33 if etag not match).
|
||||
@@ -2503,8 +2507,7 @@ def curl(url, destinationpath, onlyifnewer=False, etag=None, resume=False,
|
||||
# try asking it anything challenging.
|
||||
os.remove(tempdownloadpath)
|
||||
elif header.get('etag'):
|
||||
xattr.setxattr(tempdownloadpath,
|
||||
'com.googlecode.munki.etag', header['etag'])
|
||||
xattr.setxattr(tempdownloadpath, XATTR_ETAG, header['etag'])
|
||||
# TODO: should we log this diagnostic here (we didn't previously)?
|
||||
# Currently for a pkg all that is logged on failure is:
|
||||
# "WARNING: Download of Firefox failed." with no detail. Logging at
|
||||
@@ -2575,10 +2578,36 @@ def getDownloadCachePath(destinationpathprefix, url):
|
||||
destinationpathprefix, getInstallerItemBasename(url))
|
||||
|
||||
|
||||
def writeCachedChecksum(file_path, fhash=None):
|
||||
"""Write the sha256 checksum of a file to an xattr so we do not need to
|
||||
calculate it again. Optionally pass the recently calculated hash value.
|
||||
"""
|
||||
if not fhash:
|
||||
fhash = munkicommon.getsha256hash(file_path)
|
||||
if len(fhash) == 64:
|
||||
xattr.setxattr(file_path, XATTR_SHA, fhash)
|
||||
return fhash
|
||||
return None
|
||||
|
||||
|
||||
def getxattr(file, attr):
|
||||
"""Get a named xattr from a file. Return None if not present"""
|
||||
if attr in xattr.listxattr(file):
|
||||
return xattr.getxattr(file, attr)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def getResourceIfChangedAtomically(url, destinationpath,
|
||||
message=None, resume=False):
|
||||
"""Gets file from a URL, checking first to see if it has changed on the
|
||||
server.
|
||||
message=None, resume=False,
|
||||
expected_hash=None,
|
||||
verify=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.
|
||||
|
||||
@@ -2587,19 +2616,54 @@ def getResourceIfChangedAtomically(url, destinationpath,
|
||||
|
||||
Raises a MunkiDownloadError derived class if there is an error."""
|
||||
|
||||
url_parse = urlparse.urlparse(url)
|
||||
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']:
|
||||
return getHTTPfileIfChangedAtomically(
|
||||
changed = getHTTPfileIfChangedAtomically(
|
||||
url, destinationpath, message, resume)
|
||||
elif url_parse.scheme in ['file']:
|
||||
return getFileIfChangedAtomically(
|
||||
changed = getFileIfChangedAtomically(
|
||||
url_parse.path, destinationpath)
|
||||
# TODO: in theory NFS, AFP, or SMB could be supported here.
|
||||
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 getFileIfChangedAtomically(path, destinationpath):
|
||||
"""Gets file from path, checking first to see if it has changed on the
|
||||
@@ -2700,10 +2764,9 @@ def getHTTPfileIfChangedAtomically(url, destinationpath,
|
||||
if os.path.exists(destinationpath):
|
||||
getonlyifnewer = True
|
||||
# see if we have an etag attribute
|
||||
if 'com.googlecode.munki.etag' in xattr.listxattr(destinationpath):
|
||||
etag = getxattr(destinationpath, XATTR_ETAG)
|
||||
if etag:
|
||||
getonlyifnewer = False
|
||||
etag = xattr.getxattr(destinationpath,
|
||||
'com.googlecode.munki.etag')
|
||||
|
||||
try:
|
||||
header = curl(url,
|
||||
@@ -2743,8 +2806,7 @@ def getHTTPfileIfChangedAtomically(url, destinationpath,
|
||||
os.utime(destinationpath, (time.time(), modtimeint))
|
||||
if header.get('etag'):
|
||||
# store etag in extended attribute for future use
|
||||
xattr.setxattr(destinationpath,
|
||||
'com.googlecode.munki.etag', header['etag'])
|
||||
xattr.setxattr(destinationpath, XATTR_ETAG, header['etag'])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -91,11 +91,12 @@ def verifyFileOnlyWritableByMunkiAndRoot(file_path):
|
||||
'%s is not secure! %s' % (file_path, e.args[0]))
|
||||
|
||||
|
||||
def runExternalScript(script, *args):
|
||||
def runExternalScript(script, allow_insecure=False, script_args=[]):
|
||||
"""Run a script (e.g. preflight/postflight) and return its exit status.
|
||||
|
||||
Args:
|
||||
script: string path to the script to execute.
|
||||
allow_insecure: bool skip the permissions check of executable.
|
||||
args: args to pass to the script.
|
||||
Returns:
|
||||
Tuple. (integer exit status from script, str stdout, str stderr).
|
||||
@@ -106,23 +107,25 @@ def runExternalScript(script, *args):
|
||||
if not os.path.exists(script):
|
||||
raise ScriptNotFoundError('script does not exist: %s' % script)
|
||||
|
||||
try:
|
||||
verifyFileOnlyWritableByMunkiAndRoot(script)
|
||||
except VerifyFilePermissionsError, e:
|
||||
msg = ('Skipping execution due to failed file permissions '
|
||||
'verification: %s\n%s' % (script, str(e)))
|
||||
raise RunExternalScriptError(msg)
|
||||
if not allow_insecure:
|
||||
try:
|
||||
verifyFileOnlyWritableByMunkiAndRoot(script)
|
||||
except VerifyFilePermissionsError, e:
|
||||
msg = ('Skipping execution due to failed file permissions '
|
||||
'verification: %s\n%s' % (script, str(e)))
|
||||
raise RunExternalScriptError(msg)
|
||||
|
||||
if os.access(script, os.X_OK):
|
||||
cmd = [script]
|
||||
if args:
|
||||
cmd.extend(args)
|
||||
if script_args:
|
||||
cmd.extend(script_args)
|
||||
proc = subprocess.Popen(cmd, shell=False,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(stdout, stderr) = proc.communicate()
|
||||
return proc.returncode, stdout, stderr
|
||||
return proc.returncode, stdout.decode('UTF-8','replace'), \
|
||||
stderr.decode('UTF-8','replace')
|
||||
else:
|
||||
raise RunExternalScriptError('%s not executable' % script)
|
||||
|
||||
|
||||
@@ -317,6 +317,7 @@ cp -X "$MUNKIROOT/code/client/munkilib/version.plist" "$COREROOT/usr/local/munki
|
||||
if [ "$SVNREV" -lt "1302" ]; then
|
||||
echo $SVNREV > "$COREROOT/usr/local/munki/munkilib/svnversion"
|
||||
fi
|
||||
|
||||
# add Build Number and Git Revision to version.plist
|
||||
/usr/libexec/PlistBuddy -c "Delete :BuildNumber" "$COREROOT/usr/local/munki/munkilib/version.plist" 2>/dev/null
|
||||
/usr/libexec/PlistBuddy -c "Add :BuildNumber string $SVNREV" "$COREROOT/usr/local/munki/munkilib/version.plist"
|
||||
@@ -362,6 +363,10 @@ done
|
||||
# Set permissions.
|
||||
chmod -R go-w "$ADMINROOT/usr/local/munki"
|
||||
chmod +x "$ADMINROOT/usr/local/munki"
|
||||
# make paths.d file
|
||||
mkdir -p "$ADMINROOT/private/etc/paths.d"
|
||||
echo "/usr/local/munki" > "$ADMINROOT/private/etc/paths.d/munki"
|
||||
chmod -R 755 "$ADMINROOT/private"
|
||||
|
||||
# Create package info file.
|
||||
ADMINSIZE=`du -sk $ADMINROOT | cut -f1`
|
||||
@@ -527,6 +532,7 @@ sudo chown -hR root:wheel "$COREROOT/usr"
|
||||
sudo chown -hR root:admin "$COREROOT/Library"
|
||||
|
||||
sudo chown -hR root:wheel "$ADMINROOT/usr"
|
||||
sudo chown -hR root:wheel "$ADMINROOT/private"
|
||||
|
||||
sudo chown -hR root:admin "$APPROOT/Applications"
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<string>com.googlecode.munki.MunkiStatus</string>
|
||||
<key>LimitLoadToSessionType</key>
|
||||
<array>
|
||||
<string>LoginWindow</string>
|
||||
<string>Aqua</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.googlecode.munki.MunkiStatusLogout</string>
|
||||
<key>LimitLoadToSessionType</key>
|
||||
<array>
|
||||
<string>LoginWindow</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>ManagedSoftwareUpdateMode</key>
|
||||
<string>MunkiStatus</string>
|
||||
</dict>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Utilities/Managed Software Update.app/Contents/MacOS/Managed Software Update</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<false/>
|
||||
<key>KeepAlive</key>
|
||||
<dict>
|
||||
<key>PathState</key>
|
||||
<dict>
|
||||
<key>/var/run/com.googlecode.munki.MunkiStatus</key>
|
||||
<true/>
|
||||
<key>/private/tmp/com.googlecode.munki.installatlogout</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -18,7 +18,19 @@
|
||||
<string>--logoutinstall</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>KeepAlive</key>
|
||||
<dict>
|
||||
<key>PathState</key>
|
||||
<dict>
|
||||
<key>/private/tmp/com.googlecode.munki.installatlogout</key>
|
||||
<true/>
|
||||
<key>/Users/Shared/.com.googlecode.munki.checkandinstallatstartup</key>
|
||||
<true/>
|
||||
<key>/Users/Shared/.com.googlecode.munki.installatstartup</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.8.0</string>
|
||||
<string>0.8.0.1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
Reference in New Issue
Block a user