Example #1
0
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
Example #2
0
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
Example #3
0
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
Example #4
0
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
Example #5
0
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
Example #6
0
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
Example #7
0
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')
Example #8
0
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')
Example #9
0
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
Example #10
0
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)
Example #11
0
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
Example #12
0
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)
Example #13
0
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
Example #14
0
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
Example #15
0
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
Example #16
0
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
Example #17
0
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)
Example #18
0
 def test_display_warning_with_str_msg_str_arg(self):
     munkicommon.display_warning(MSG_STR, ARG_STR)
Example #19
0
 def test_display_warning_with_unicode_msg_unicode_arg(self):
     munkicommon.display_warning(MSG_UNI, ARG_UNI)
Example #20
0
         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':
Example #21
0
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)
Example #22
0
         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':
Example #23
0
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))
Example #24
0
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
Example #25
0
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
Example #26
0
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))
Example #27
0
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
Example #28
0
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