Files
munki/code/client/logouthelper
David Aguilar 3bb91cabca munki: rename "/usr/local/munki/python" symlink to "munki-python" (#997)
Avoid masking the system /usr/bin/python by calling our symlink
"munki-python" instead of "python".

Closes #996
2020-07-07 13:58:34 -07:00

219 lines
7.9 KiB
Plaintext
Executable File

#!/usr/local/munki/munki-python
# encoding: utf-8
#
# Copyright 2011-2020 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
logouthelper
Created by Greg Neagle on 2011-06-21.
A helper tool for forced logouts to allow munki to force install items by
a certain deadline.
"""
from __future__ import absolute_import
# standard libs
import os
import time
# our libs
from munkilib import info
from munkilib import munkilog
from munkilib import osutils
from munkilib import prefs
from munkilib import processes
from munkilib import FoundationPlist
# Apple frameworks via PyObjC
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
# No name 'Foo' in module 'Bar' warnings. Disable them.
# pylint: disable=E0611
from Foundation import NSDate
from Foundation import NSDictionary
from Foundation import NSDistributedNotificationCenter
from Foundation import NSNotificationDeliverImmediately
from Foundation import NSNotificationPostToAllSessions
# pylint: enable=E0611
NOTIFICATION_MINS = [240, 180, 120, 90, 60, 45, 30, 15, 10, 5]
MANDATORY_NOTIFICATIONS = [60, 30, 10, 5]
PROCESS_ID = 'com.googlecode.munki.logouthelper'
def log(msg):
'''Logs messages from this tool with an identifier'''
munkilog.log('%s: %s' % (PROCESS_ID, msg))
def earliest_force_install_date():
'''Check installable packages for force_install_after_dates
Returns None or earliest force_install_after_date converted to local time
'''
earliest_date = None
managed_install_dir = prefs.pref('ManagedInstallDir')
installinfo_types = {
'InstallInfo.plist' : 'managed_installs',
'AppleUpdates.plist': 'AppleUpdates'
}
installinfopath = os.path.join(managed_install_dir, 'InstallInfo.plist')
try:
installinfo = FoundationPlist.readPlist(installinfopath)
except FoundationPlist.NSPropertyListSerializationException:
return None
for plist_name in installinfo_types:
key_to_check = installinfo_types[plist_name]
plist_path = os.path.join(managed_install_dir, plist_name)
try:
installinfo = FoundationPlist.readPlist(plist_path)
except FoundationPlist.NSPropertyListSerializationException:
continue
for install in installinfo.get(key_to_check, []):
force_install_date = install.get('force_install_after_date')
if force_install_date:
force_install_date = info.subtract_tzoffset_from_date(
force_install_date)
if not earliest_date or force_install_date < earliest_date:
earliest_date = force_install_date
return earliest_date
def alert_user_of_forced_logout(info_dict=None):
'''Uses Managed Software Center.app to notify the user of an
upcoming forced logout.
Args:
info: dict of data to send with the notification.
'''
consoleuser = osutils.getconsoleuser()
if not processes.find_processes(
exe="/Applications/Managed Software Center.app",
user=consoleuser):
# Managed Software Center.app isn't running.
# Use our LaunchAgent to start
# Managed Software Center.app in the user context.
launchfile = '/var/run/com.googlecode.munki.ManagedSoftwareCenter'
fileref = open(launchfile, 'w')
fileref.close()
# now wait a bit for it to launch before proceeding
# because if we don't, sending the logoutwarn notification
# may fall on deaf ears.
time.sleep(5)
if os.path.exists(launchfile):
os.unlink(launchfile)
# if set, convert Python dictionary to NSDictionary.
if info_dict is not None:
info_dict = NSDictionary.dictionaryWithDictionary_(info_dict)
# cause MSC.app to display the Forced Logout warning
dnc = NSDistributedNotificationCenter.defaultCenter()
dnc.postNotificationName_object_userInfo_options_(
'com.googlecode.munki.ManagedSoftwareUpdate.logoutwarn',
None, info_dict,
NSNotificationDeliverImmediately + NSNotificationPostToAllSessions)
# make sure flag is in place to cause munki to install at logout
fileref = open('/private/tmp/com.googlecode.munki.installatlogout', 'w')
fileref.close()
def main():
'''Check for logged-in users and upcoming forced installs;
notify the user if needed; sleep a minute and do it again.'''
if prefs.pref('LogToSyslog'):
munkilog.configure_syslog()
log('launched')
sent_notifications = []
logout_time_override = None
# datetime of now plus largest MANDATORY_NOTIFICATIONS value (with padding).
minimum_notifications_logout_time = NSDate.date().addTimeInterval_(
60 * max(MANDATORY_NOTIFICATIONS) + 30)
while True:
if not osutils.currentGUIusers():
# no-one is logged in, so bail
log('no-one logged in')
time.sleep(10) # makes launchd happy
log('exited')
exit(0)
# we check each time because items might have been added or removed
# from the list; or their install date may have been changed.
next_logout_time = earliest_force_install_date()
if not next_logout_time:
# no forced logout needed, so bail
log('no forced installs found')
time.sleep(10) # makes launchd happy
log('exited')
exit(0)
if logout_time_override is None:
logout_time = next_logout_time
else:
# allow the new next_logout_time from InstallInfo to be used
# if it has changed to a later time since when we decided to
# override it.
if next_logout_time > logout_time_override:
logout_time = next_logout_time
log('reset logout_time to: %s' % logout_time)
logout_time_override = None
sent_notifications = []
# always give at least MANDATORY_NOTIFICATIONS warnings
if logout_time < minimum_notifications_logout_time:
for mandatory_notification in MANDATORY_NOTIFICATIONS:
if mandatory_notification not in sent_notifications:
# logout time is in the past, and a mandatory notification
# has not been sent, so reset the logout_time to the future.
log('%d minute notification not sent.'
% mandatory_notification)
logout_time = NSDate.date(
).addTimeInterval_(60 * mandatory_notification + 30)
log('reset logout_time to: %s' % logout_time)
logout_time_override = logout_time
break
minutes_until_logout = int(logout_time.timeIntervalSinceNow() / 60)
info_dict = {'logout_time': logout_time}
if minutes_until_logout in NOTIFICATION_MINS:
sent_notifications.append(minutes_until_logout)
log('Warning user of %s minutes until forced logout'
% minutes_until_logout)
alert_user_of_forced_logout(info_dict)
elif minutes_until_logout < 1:
log('Forced logout in 60 seconds')
alert_user_of_forced_logout(info_dict)
time.sleep(60)
if minutes_until_logout < 1:
break
if osutils.currentGUIusers() and earliest_force_install_date():
log('Beginning forced logout')
processes.force_logout_now()
log('exited')
exit(0)
if __name__ == '__main__':
main()