Auth restart support for Managed Software Center.app

This commit is contained in:
Greg Neagle
2017-04-20 14:52:02 -05:00
parent 4b1c0c9949
commit 98a4892985
26 changed files with 1702 additions and 530 deletions
+385
View File
@@ -0,0 +1,385 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2017 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.
"""
authrestartd
Created by Greg Neagle on 2017-04-15.
Much code based on and adapted from autopkgserver by Per Olofsson
A helper tool for FileVault authorized restarts. Allows non-privileged users
to get certain info about FileVault, and to store a password for authrestart.
Root user can trigger an authrestart, possibly using the password stored
earlier by a non-privileged user.
"""
import os
import sys
import time
import stat
import logging
import logging.handlers
import SocketServer
import socket
import plistlib
import struct
from munkilib import authrestart
from munkilib import launchd
from munkilib import prefs
APPNAME = 'authrestartd'
VERSION = '0.1'
class FDEUtilError(Exception):
'''Exception to raise if there is any error in FDEUtil'''
pass
class FDEUtil(object):
'''Class for working with fdesetup'''
def __init__(self, server, request, uid):
'''Arguments:
request A request in plist format.
uid The uid of the requestor
'''
self.server = server
self.log = server.log
self.request = request
self.uid = uid
def verify_request(self):
'''Make sure copy request has everything we need'''
self.log.debug('Verifying request')
for key in ['task']:
if not key in self.request:
raise FDEUtilError('No %s in request' % key)
def store_password(self, password):
'''Stores a password for later use for authrestart'''
self.server.stored_password = password
def handle(self):
'''Handle our request'''
self.verify_request()
if self.request['task'] == 'restart':
# Attempt to perform an authrestart, falling back to a regular
# restart
self.log.info('Restart request from uid %s', self.uid)
if self.uid == 0:
authrestart.do_authorized_or_normal_restart(
password=self.server.stored_password)
return 'RESTARTING'
else:
self.log.info('Restart request denied')
raise FDEUtilError('Restart may only be triggered by root')
elif self.request['task'] == 'store_password':
# store a password for later fdesetup authrestart
self.log.info('Store password request')
username = self.request.get('username')
# don't store the password if the user isn't enabled for FileVault
if (username and
not authrestart.can_attempt_auth_restart_for(username)):
self.log.info('User %s can\'t do auth restart', username)
raise FDEUtilError(
'User %s can\'t do FileVault authrestart' % username)
password = self.request.get('password')
if not password:
self.log.info('No password in request')
raise FDEUtilError('No password provided')
self.store_password(password)
self.log.info('Password stored.')
return 'DONE'
elif self.request['task'] == 'verify_can_attempt_auth_restart':
# Check if we have all the required bits to attempt an auth
# restart.
self.log.info('Verify ready for auth restart')
if authrestart.can_attempt_auth_restart(
have_password=bool(self.server.stored_password)):
self.log.info('Ready for auth restart attempt')
return 'READY'
else:
self.log.info('Not ready for auth restart attempt')
raise FDEUtilError('Not ready for auth restart attempt')
elif self.request['task'] == 'verify_recovery_key_present':
# Check if a plist containing a recovery key or password is
# present. Return a boolean.
self.log.info('Verify recovery key request')
if authrestart.get_auth_restart_key(quiet=True) == '':
self.log.info('No valid recovery key plist')
raise FDEUtilError('No recovery key plist')
self.log.info('Valid recovery key plist found')
return 'PRESENT'
elif self.request['task'] == 'verify_user':
# Check to see if we can perform an authrestart for this user.
# FileVault must be active, the hardware must support authrestart,
# and the user must be enabled for FileVault.
username = self.request.get('username')
if not username:
self.log.info('Verify user request with no username')
raise FDEUtilError('No username provided')
self.log.info('Verify user request for %s', username)
if not authrestart.can_attempt_auth_restart_for(username):
self.log.info(
'User %s can\'t do FileVault authrestart' % username)
raise FDEUtilError('User not authorized for FileVault')
self.log.info('User %s ok for auth restart', username)
return 'USER VERIFIED'
elif self.request['task'] == 'verify_filevault':
# check if FileVault is active
self.log.info('Verify FileVault request')
if not authrestart.filevault_is_active():
self.log.info('FileVault is not active.')
raise FDEUtilError('FileVault is not active')
self.log.info('FileVault is active.')
return 'FILEVAULT ON'
else:
self.log.info('Unknown task request: %s', self.request['task'])
raise FDEUtilError('Unknown task')
class RunHandler(SocketServer.StreamRequestHandler):
'''Handler for restarthelper run requests'''
def verify_request_syntax(self, plist):
'''Verify the basic syntax of request plist.'''
# Keep a list of error messages.
errors = list()
# Root should be a dictionary.
if not isinstance(plist, dict):
errors.append('Request root is not a dictionary')
# Bail out early if it's not.
return False, errors
syntax_ok = True
# TO-DO: Actual verification!
return syntax_ok, errors
def getpeerid(self):
'''
Get peer credentials on a UNIX domain socket.
Returns uid, gids.
'''
# /usr/include/sys/ucred.h
#
# struct xucred {
# u_int cr_version; /* structure layout version */
# uid_t cr_uid; /* effective user id */
# short cr_ngroups; /* number of advisory groups */
# gid_t cr_groups[NGROUPS]; /* advisory group list */
# };
# pylint: disable=invalid-name
LOCAL_PEERCRED = 0x001
XUCRED_VERSION = 0
NGROUPS = 16
# pylint: enable=invalid-name
cr_version = 0
cr_uid = 1
cr_ngroups = 2
cr_groups = 3
xucred_fmt = 'IIh%dI' % NGROUPS
res = struct.unpack(
xucred_fmt,
self.request.getsockopt(
0, LOCAL_PEERCRED, struct.calcsize(xucred_fmt)))
if res[cr_version] != XUCRED_VERSION:
raise OSError('Incompatible struct xucred version')
return res[cr_uid], res[cr_groups:cr_groups + res[cr_ngroups]]
def handle(self):
'''Handle an incoming run request.'''
try:
# Log through server parent.
self.log = self.server.log
self.log.debug('Handling request')
# Get uid and primary gid of connecting peer.
uid, gids = self.getpeerid()
gid = gids[0]
self.log.debug(
'Got request from uid %d gid %d' % (uid, gid))
# Receive a plist.
plist_string = self.request.recv(8192)
# Try to parse it.
try:
plist = plistlib.readPlistFromString(plist_string)
except BaseException as err:
self.log.error('Malformed request')
self.request.send('ERROR:Malformed request\n')
return
self.log.debug('Parsed request plist')
# Verify the plist syntax.
syntax_ok, errors = self.verify_request_syntax(plist)
if not syntax_ok:
self.log.error('Plist syntax error')
self.request.send(''.join(['ERROR:%s\n' % e for e in errors]))
return
self.log.info(
'Dispatching worker to process request for user %d' % (uid))
try:
fdeutil = FDEUtil(self.server, plist, uid)
result = fdeutil.handle()
self.request.send(u'OK:%s\n' % result)
except FDEUtilError as err:
self.request.send(u'ERROR:%s\n' % unicode(err))
except BaseException as err:
self.log.error('Run failed: %s' % unicode(err))
self.request.send(u'ERROR:%s\n' % unicode(err))
except BaseException as err:
self.log.error('Caught exception: %s' % repr(err))
self.request.send('ERROR:Caught exception: %s' % repr(err))
return
class RestartHelperDaemonError(Exception):
'''Exception to raise for RestartHelperDaemon errors'''
pass
class RestartHelperDaemon(SocketServer.UnixStreamServer):
'''Daemon that runs as root,
receiving requests for fdesetup tasks including authrestart.'''
allow_reuse_address = True
request_queue_size = 10
timeout = 10
def __init__(self, socket_fd, RequestHandlerClass):
# Avoid initialization of UnixStreamServer as we need to open the
# socket from a file descriptor instead of creating our own.
self.socket = socket.fromfd(
socket_fd, socket.AF_UNIX, socket.SOCK_STREAM)
self.socket.listen(self.request_queue_size)
SocketServer.BaseServer.__init__(self,
self.socket.getsockname(),
RequestHandlerClass)
self.timed_out = False
self.stored_password = None
def setup_logging(self):
'''Configure logging'''
try:
self.log = logging.getLogger(APPNAME)
self.log.setLevel(logging.DEBUG)
log_console = logging.StreamHandler()
log_console.setLevel(logging.DEBUG)
# store the log in the same directory as ManagedSoftwareUpdate.log
logfilepath = os.path.join(
os.path.dirname(prefs.pref('LogFile')), APPNAME + '.log')
log_file = logging.handlers.RotatingFileHandler(
logfilepath, 'a', 100000, 9, 'utf-8')
log_file.setLevel(logging.DEBUG)
console_formatter = logging.Formatter('%(message)s')
file_formatter = logging.Formatter(
'%(asctime)s %(module)s[%(process)d]: '
'%(message)s (%(funcName)s)')
log_console.setFormatter(console_formatter)
log_file.setFormatter(file_formatter)
self.log.addHandler(log_console)
self.log.addHandler(log_file)
except (OSError, IOError) as err:
raise RestartHelperDaemonError(
'Can\'t open log: %s' % (err.strerror))
def handle_timeout(self):
self.timed_out = True
def main(argv):
'''Start our daemon, connect to socket and process requests'''
# Make sure we're launched as root
if os.geteuid() != 0:
print >>sys.stderr, '%s must be run as root.' % APPNAME
# Sleep to avoid respawn.
time.sleep(10)
return 1
# Make sure that the executable and all containing directories are owned
# by root:wheel or root:admin, and not writeable by other users.
root_uid = 0
wheel_gid = 0
admin_gid = 80
exepath = os.path.realpath(os.path.abspath(__file__))
path_ok = True
while True:
info = os.stat(exepath)
if info.st_uid != root_uid:
print >> sys.stderr, '%s must be owned by root.' % exepath
path_ok = False
if info.st_gid not in (wheel_gid, admin_gid):
print >> sys.stderr, '%s must have group wheel or admin.' % exepath
path_ok = False
if info.st_mode & stat.S_IWOTH:
print >> sys.stderr, '%s mustn\'t be world writeable.' % exepath
path_ok = False
exepath = os.path.dirname(exepath)
if exepath == '/':
break
if not path_ok:
# Sleep to avoid immediate respawn.
time.sleep(10)
return 1
# Get socket file descriptors from launchd.
socket_fd = launchd.get_socket_fd('authrestartd')
if not socket_fd:
print >> sys.stderr, 'No socket provided to us by launchd'
time.sleep(10)
return 1
# Create the daemon object.
daemon = RestartHelperDaemon(socket_fd, RunHandler)
daemon.setup_logging()
daemon.log.info('%s v%s starting', APPNAME, VERSION)
# Once we're launched, we run until restart
while True:
daemon.handle_request()
if __name__ == '__main__':
sys.exit(main(sys.argv))
+6 -7
View File
@@ -62,7 +62,6 @@ else:
from munkilib import info
from munkilib import installer
from munkilib import installinfo
from munkilib import launch
from munkilib import munkilog
from munkilib import munkistatus
from munkilib import osinstaller
@@ -74,6 +73,8 @@ else:
from munkilib import utils
from munkilib import FoundationPlist
import munkilib.authrestart.client as authrestartd
def signal_handler(signum, dummy_frame):
"""Handle any signals we've been told to.
@@ -333,7 +334,10 @@ def doRestart():
if not consoleuser or consoleuser == u'loginwindow':
# no-one is logged in or we're at the loginwindow
time.sleep(5)
authrestart.do_authorized_or_normal_restart()
# try to use authrestartd to do an auth restart; if that fails
# do it directly
if not authrestartd.restart():
authrestart.do_authorized_or_normal_restart()
else:
if display.munkistatusoutput:
# someone is logged in and we're using Managed Software Center.
@@ -531,11 +535,6 @@ def warn_if_server_is_default(server):
def main():
"""Main"""
if launch.get_socket_fd():
munkilog.log('Launched via socket')
time.sleep(10)
sys.exit()
# install handler for SIGTERM
signal.signal(signal.SIGTERM, signal_handler)
@@ -17,18 +17,18 @@
authrestart.py
Created by Greg Neagle on 2016-12-14.
Functions originally written by Wes Whetstone, Summer/Fall 2016
Initial work by Wes Whetstone, Summer/Fall 2016
Functions supporting FileVault authrestart.
"""
import subprocess
from . import display
from . import osutils
from . import prefs
from .. import display
from .. import osutils
from .. import prefs
from . import FoundationPlist
from .. import FoundationPlist
def filevault_is_active():
@@ -77,46 +77,79 @@ def supports_auth_restart():
return False
def get_auth_restart_key():
def is_fv_user(username):
"""Returns a boolean indicating if username is in the list of FileVault
authorized users"""
cmd = ['/usr/bin/fdesetup', 'list']
try:
userlist = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
return False
# output is in the format
# jsmith,911D2742-7983-436D-9FA3-3F6B7421684B
# tstark,5B0EBEE6-0917-47B2-BFF3-78A9DE437D65
for line in userlist.splitlines():
if line.split(',')[0] == username:
return True
return False
def can_attempt_auth_restart_for(username):
'''Returns a boolean to indicate if all the needed conditions are present
for us to attempt an authrestart with username's password'''
os_version_tuple = osutils.getOsVersion(as_tuple=True)
return (os_version_tuple >= (10, 8) and
prefs.pref('PerformAuthRestarts') and filevault_is_active() and
supports_auth_restart() and is_fv_user(username))
def get_auth_restart_key(quiet=False):
"""Returns recovery key as a string... If we failed
to get the proper information, returns an empty string"""
to get the proper information, returns an empty string.
If quiet is set, fail silently"""
# checks to see if recovery key preference is set
recoverykeyplist = prefs.pref('RecoveryKeyFile')
if not recoverykeyplist:
display.display_warning(
"RecoveryKeyFile preference is not set")
if not quiet:
display.display_debug1('RecoveryKeyFile preference is not set')
return ''
display.display_debug1(
'RecoveryKeyFile preference is set to %s...', recoverykeyplist)
if not quiet:
display.display_debug1(
'RecoveryKeyFile preference is set to %s...', recoverykeyplist)
# try to get the recovery key from the defined location
try:
keyplist = FoundationPlist.readPlist(recoverykeyplist)
recovery_key = keyplist['RecoveryKey'].strip()
return recovery_key
except FoundationPlist.NSPropertyListSerializationException:
display.display_error(
'We had trouble getting info from %s...', recoverykeyplist)
if not quiet:
display.display_error(
'We had trouble getting info from %s...', recoverykeyplist)
return ''
except KeyError:
display.display_error(
'Problem with key: RecoveryKey in %s...', recoverykeyplist)
if not quiet:
display.display_error(
'Problem with key: RecoveryKey in %s...', recoverykeyplist)
return ''
def can_attempt_auth_restart():
def can_attempt_auth_restart(have_password=False):
'''Returns a boolean to indicate if all the needed conditions are present
for us to attempt an authrestart'''
os_version_tuple = osutils.getOsVersion(as_tuple=True)
return (os_version_tuple >= (10, 8) and
prefs.pref('PerformAuthRestarts') and filevault_is_active() and
supports_auth_restart() and get_auth_restart_key() != '')
prefs.pref('PerformAuthRestarts') and
filevault_is_active() and supports_auth_restart() and
(get_auth_restart_key(quiet=True) != '' or have_password))
def perform_auth_restart():
def perform_auth_restart(password=None):
"""When called this will perform an authorized restart. Before trying
to perform an authorized restart it checks to see if the machine supports
the feature. If supported it will then look for the defined plist containing
a key called RecoveryKey. It will use that value to perform the restart"""
the feature. If supported it will look for the defined plist containing
a key called RecoveryKey. If this doesn't exist, it will use a password
(or recovery key) passed into the function. It will use that value to
perform the restart."""
display.display_debug1(
'Checking if performing an Auth Restart is fully supported...')
if not supports_auth_restart():
@@ -124,18 +157,18 @@ def perform_auth_restart():
"Machine doesn't support Authorized Restarts...")
return False
display.display_debug1('Machine supports Authorized Restarts...')
recovery_key = get_auth_restart_key()
recovery_key = get_auth_restart_key() or password
if not recovery_key:
return False
key = {'Password': recovery_key}
inputplist = FoundationPlist.writePlistToString(key)
display.display_info('Attempting an Authorized Restart now...')
cmd = subprocess.Popen(
proc = subprocess.Popen(
['/usr/bin/fdesetup', 'authrestart', '-inputplist'],
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
(dummy_out, err) = cmd.communicate(input=inputplist)
err = proc.communicate(input=inputplist)[1]
os_version_tuple = osutils.getOsVersion(as_tuple=True)
if os_version_tuple >= (10, 12) and 'System is being restarted' in err:
return True
@@ -146,17 +179,17 @@ def perform_auth_restart():
return True
def do_authorized_or_normal_restart():
def do_authorized_or_normal_restart(password=None):
'''Do an authrestart if allowed/possible, else do a normal restart.'''
display.display_info('Restarting now.')
os_version_tuple = osutils.getOsVersion(as_tuple=True)
if (prefs.pref('PerformAuthRestarts')
and prefs.pref('RecoveryKeyFile')
and os_version_tuple >= (10, 8)):
if (prefs.pref('PerformAuthRestarts') and
(prefs.pref('RecoveryKeyFile') or password) and
os_version_tuple >= (10, 8)):
if filevault_is_active():
display.display_debug1('Configured to perform AuthRestarts...')
# try to perform an auth restart
if not perform_auth_restart():
if not perform_auth_restart(password=password):
# if we got to here then the auth restart failed
# notify that it did then perform a normal restart
display.display_warning(
+200
View File
@@ -0,0 +1,200 @@
# encoding: utf-8
#
# Copyright 2017 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.
"""
authrestart.client.py
Created by Greg Neagle on 2017-04-15.
Routines for communicating with authrestartd.
Socket communications code adapted from autopkg's PkgCreator by Per Olofsson
"""
import os
import plistlib
import select
import socket
import sys
AUTHRESTARTD_SOCKET = "/var/run/authrestartd"
class AuthRestartClientError(Exception):
'''Exception to raise for errors in AuthRestartClient'''
pass
class AuthRestartClient(object):
'''Handles communication with authrestartd daemon'''
def connect(self):
'''Connect to authrestartd'''
try:
#pylint: disable=attribute-defined-outside-init
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
#pylint: enable=attribute-defined-outside-init
self.socket.connect(AUTHRESTARTD_SOCKET)
except socket.error as err:
raise AuthRestartClientError(
"Couldn't connect to authrestartd: %s" % err.strerror)
def send_request(self, request):
'''Send a request to authrestartd'''
self.socket.send(plistlib.writePlistToString(request))
with os.fdopen(self.socket.fileno()) as fileref:
# use select so we don't hang indefinitely if authrestartd dies
ready = select.select([fileref], [], [], 2)
if ready[0]:
reply = fileref.read()
else:
reply = ''
if reply:
return reply.rstrip()
else:
return "ERROR:No reply"
def disconnect(self):
'''Disconnect from authrestartd'''
self.socket.close()
def process(self, request):
'''Send a request and return the result'''
try:
self.connect()
result = self.send_request(request)
finally:
self.disconnect()
return result
def fv_is_active(self):
'''Returns a boolean to indicate if FileVault is active'''
result = self.process({'task': 'verify_filevault'})
return result.startswith('OK')
def verify_user(self, username):
'''Returns True if username can unlock the FV volume'''
request = {'task': 'verify_user', 'username': username}
result = self.process(request)
return result.startswith('OK')
def verify_recovery_key_present(self):
'''Returns True if plist containing a FV recovery key is present'''
request = {'task': 'verify_recovery_key_present'}
result = self.process(request)
return result.startswith('OK')
def verify_can_attempt_auth_restart(self):
'''Returns True if we are ready to attempt an auth restart'''
request = {'task': 'verify_can_attempt_auth_restart'}
result = self.process(request)
return result.startswith('OK')
def store_password(self, password, username=None):
'''Stores a FV password with authrestartd'''
request = {'task': 'store_password', 'password': password}
if username:
request['username'] = username
result = self.process(request)
if not result.startswith('OK'):
raise AuthRestartClientError(result)
def restart(self):
'''Returns True if restart was successful'''
result = self.process({'task': 'restart'})
if not result.startswith('OK'):
raise AuthRestartClientError(result)
# Higher-level wrapper functions that swallow AuthRestartClientErrors
def fv_is_active():
'''Returns True if FileVault can be verified to be active,
False otherwise'''
try:
return AuthRestartClient().fv_is_active()
except AuthRestartClientError:
return False
def verify_user(username):
'''Returns True if user can be verified to be able to perform an
authrestart, False otherwise'''
try:
return AuthRestartClient().verify_user(username)
except AuthRestartClientError:
return False
def verify_recovery_key_present():
'''Returns True if we have a plist with a FileVault recovery key,
False otherwise'''
try:
return AuthRestartClient().verify_recovery_key_present()
except AuthRestartClientError:
return False
def verify_can_attempt_auth_restart():
'''Returns True if we have what we need to attempt an auth restart'''
try:
return AuthRestartClient().verify_can_attempt_auth_restart()
except AuthRestartClientError:
return False
def store_password(password, username=None):
'''Stores a password for later authrestart usage.
Returns boolean to indicate success/failure'''
try:
AuthRestartClient().store_password(password, username=username)
return True
except AuthRestartClientError:
return False
def restart():
'''Performs a restart -- authenticated if possible.
Returns boolean to indicate success/failure'''
try:
AuthRestartClient().restart()
return True
except AuthRestartClientError:
return False
def test():
import getpass
import pwd
print 'FileVault is active: %s' % fv_is_active()
print 'Recovery key is present: %s' % verify_recovery_key_present()
username = pwd.getpwuid(os.getuid()).pw_name
print '%s is FV user: %s' % (username, verify_user(username))
password = getpass.getpass('Enter password: ')
if password:
if username == 'root':
username = None
if store_password(password, username=username):
print 'store_password was successful'
else:
print 'store_password failed'
print 'Can attempt auth restart: %s' % verify_can_attempt_auth_restart()
answer = raw_input('Test auth restart (y/n)? ')
if answer.lower().startswith('y'):
print 'Attempting auth restart...'
if restart():
print 'restart was successfully triggered'
else:
print 'restart failed'
-56
View File
@@ -1,56 +0,0 @@
# encoding: utf-8
#
# Copyright 2017 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.
"""
launch.__init__.py
Created by Greg Neagle on 2017-04-14.
Code for getting socket file descriptor from launchd.
Returns a file descriptor for our socket.
"""
from .. import osutils
SOCKET_NAME = 'managedsoftwareupdated'
def get_socket_fd():
'''Get socket file descriptors from launchd.'''
os_version = osutils.getOsVersion(as_tuple=True)
if os_version >= (10, 10):
# use new launchd api
from . import launch2
try:
sockets = launch2.launch_activate_socket(SOCKET_NAME)
except launch2.LaunchDError:
# no sockets found
return None
return sockets[0]
else:
# use old launchd api
from . import launch1
try:
socket_dict = launch1.get_launchd_socket_fds()
except launch1.LaunchDCheckInError:
# no sockets found
return None
if SOCKET_NAME not in socket_dict:
# no sockets found with the expected name
return None
return socket_dict[SOCKET_NAME][0]
-254
View File
@@ -1,254 +0,0 @@
#
# Copyright 2010 Per Olofsson
#
# 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
#
# http://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.
from ctypes import *
libc = CDLL("/usr/lib/libc.dylib")
c_launch_data_t = c_void_p
# size_t launch_data_array_get_count(const launch_data_t)
launch_data_array_get_count = libc.launch_data_array_get_count
launch_data_array_get_count.restype = c_size_t
launch_data_array_get_count.argtypes = [c_launch_data_t]
#launch_data_t launch_data_array_get_index(const launch_data_t, size_t) __ld_getter;
launch_data_array_get_index = libc.launch_data_array_get_index
launch_data_array_get_index.restype = c_launch_data_t
launch_data_array_get_index.argtypes = [c_launch_data_t, c_size_t]
# size_t launch_data_dict_get_count(const launch_data_t)
launch_data_dict_get_count = libc.launch_data_dict_get_count
launch_data_dict_get_count.restype = c_size_t
launch_data_dict_get_count.argtypes = [c_launch_data_t]
# launch_data_t launch_data_dict_lookup(const launch_data_t, const char *)
launch_data_dict_lookup = libc.launch_data_dict_lookup
launch_data_dict_lookup.restype = c_launch_data_t
launch_data_dict_lookup.argtypes = [c_launch_data_t, c_char_p]
#void launch_data_dict_iterate(const launch_data_t, void (*)(const launch_data_t, const char *, void *), void *) __ld_iterator(1, 2)
DICTITCALLBACK = CFUNCTYPE(c_void_p, c_launch_data_t, c_char_p, c_void_p)
launch_data_dict_iterate = libc.launch_data_dict_iterate
launch_data_dict_iterate.restype = None
launch_data_dict_iterate.argtypes = [c_launch_data_t, DICTITCALLBACK, c_void_p]
# void launch_data_free(launch_data_t)
launch_data_free = libc.launch_data_free
launch_data_free.restype = None
launch_data_free.argtypes = [c_launch_data_t]
# int launch_data_get_errno(const launch_data_t)
launch_data_get_errno = libc.launch_data_get_errno
launch_data_get_errno.restype = c_int
launch_data_get_errno.argtypes = [c_launch_data_t]
# int launch_data_get_fd(const launch_data_t)
launch_data_get_fd = libc.launch_data_get_fd
launch_data_get_fd.restype = c_int
launch_data_get_fd.argtypes = [c_launch_data_t]
# launch_data_type_t launch_data_get_type(const launch_data_t)
launch_data_get_type = libc.launch_data_get_type
launch_data_get_type.restype = c_launch_data_t
launch_data_get_type.argtypes = [c_launch_data_t]
# launch_data_t launch_data_new_string(const char *)
launch_data_new_string = libc.launch_data_new_string
launch_data_new_string.restype = c_launch_data_t
launch_data_new_string.argtypes = [c_char_p]
# launch_data_t launch_msg(const launch_data_t)
launch_msg = libc.launch_msg
launch_msg.restype = c_launch_data_t
launch_msg.argtypes = [c_launch_data_t]
LAUNCH_KEY_SUBMITJOB = c_char_p("SubmitJob")
LAUNCH_KEY_REMOVEJOB = c_char_p("RemoveJob")
LAUNCH_KEY_STARTJOB = c_char_p("StartJob")
LAUNCH_KEY_STOPJOB = c_char_p("StopJob")
LAUNCH_KEY_GETJOB = c_char_p("GetJob")
LAUNCH_KEY_GETJOBS = c_char_p("GetJobs")
LAUNCH_KEY_CHECKIN = c_char_p("CheckIn")
LAUNCH_JOBKEY_LABEL = c_char_p("Label")
LAUNCH_JOBKEY_DISABLED = c_char_p("Disabled")
LAUNCH_JOBKEY_USERNAME = c_char_p("UserName")
LAUNCH_JOBKEY_GROUPNAME = c_char_p("GroupName")
LAUNCH_JOBKEY_TIMEOUT = c_char_p("TimeOut")
LAUNCH_JOBKEY_EXITTIMEOUT = c_char_p("ExitTimeOut")
LAUNCH_JOBKEY_INITGROUPS = c_char_p("InitGroups")
LAUNCH_JOBKEY_SOCKETS = c_char_p("Sockets")
LAUNCH_JOBKEY_MACHSERVICES = c_char_p("MachServices")
LAUNCH_JOBKEY_MACHSERVICELOOKUPPOLICIES = c_char_p("MachServiceLookupPolicies")
LAUNCH_JOBKEY_INETDCOMPATIBILITY = c_char_p("inetdCompatibility")
LAUNCH_JOBKEY_ENABLEGLOBBING = c_char_p("EnableGlobbing")
LAUNCH_JOBKEY_PROGRAMARGUMENTS = c_char_p("ProgramArguments")
LAUNCH_JOBKEY_PROGRAM = c_char_p("Program")
LAUNCH_JOBKEY_ONDEMAND = c_char_p("OnDemand")
LAUNCH_JOBKEY_KEEPALIVE = c_char_p("KeepAlive")
LAUNCH_JOBKEY_LIMITLOADTOHOSTS = c_char_p("LimitLoadToHosts")
LAUNCH_JOBKEY_LIMITLOADFROMHOSTS = c_char_p("LimitLoadFromHosts")
LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE = c_char_p("LimitLoadToSessionType")
LAUNCH_JOBKEY_RUNATLOAD = c_char_p("RunAtLoad")
LAUNCH_JOBKEY_ROOTDIRECTORY = c_char_p("RootDirectory")
LAUNCH_JOBKEY_WORKINGDIRECTORY = c_char_p("WorkingDirectory")
LAUNCH_JOBKEY_ENVIRONMENTVARIABLES = c_char_p("EnvironmentVariables")
LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES = c_char_p("UserEnvironmentVariables")
LAUNCH_JOBKEY_UMASK = c_char_p("Umask")
LAUNCH_JOBKEY_NICE = c_char_p("Nice")
LAUNCH_JOBKEY_HOPEFULLYEXITSFIRST = c_char_p("HopefullyExitsFirst")
LAUNCH_JOBKEY_HOPEFULLYEXITSLAST = c_char_p("HopefullyExitsLast")
LAUNCH_JOBKEY_LOWPRIORITYIO = c_char_p("LowPriorityIO")
LAUNCH_JOBKEY_SESSIONCREATE = c_char_p("SessionCreate")
LAUNCH_JOBKEY_STARTONMOUNT = c_char_p("StartOnMount")
LAUNCH_JOBKEY_SOFTRESOURCELIMITS = c_char_p("SoftResourceLimits")
LAUNCH_JOBKEY_HARDRESOURCELIMITS = c_char_p("HardResourceLimits")
LAUNCH_JOBKEY_STANDARDINPATH = c_char_p("StandardInPath")
LAUNCH_JOBKEY_STANDARDOUTPATH = c_char_p("StandardOutPath")
LAUNCH_JOBKEY_STANDARDERRORPATH = c_char_p("StandardErrorPath")
LAUNCH_JOBKEY_DEBUG = c_char_p("Debug")
LAUNCH_JOBKEY_WAITFORDEBUGGER = c_char_p("WaitForDebugger")
LAUNCH_JOBKEY_QUEUEDIRECTORIES = c_char_p("QueueDirectories")
LAUNCH_JOBKEY_WATCHPATHS = c_char_p("WatchPaths")
LAUNCH_JOBKEY_STARTINTERVAL = c_char_p("StartInterval")
LAUNCH_JOBKEY_STARTCALENDARINTERVAL = c_char_p("StartCalendarInterval")
LAUNCH_JOBKEY_BONJOURFDS = c_char_p("BonjourFDs")
LAUNCH_JOBKEY_LASTEXITSTATUS = c_char_p("LastExitStatus")
LAUNCH_JOBKEY_PID = c_char_p("PID")
LAUNCH_JOBKEY_THROTTLEINTERVAL = c_char_p("ThrottleInterval")
LAUNCH_JOBKEY_LAUNCHONLYONCE = c_char_p("LaunchOnlyOnce")
LAUNCH_JOBKEY_ABANDONPROCESSGROUP = c_char_p("AbandonProcessGroup")
LAUNCH_JOBKEY_IGNOREPROCESSGROUPATSHUTDOWN = c_char_p("IgnoreProcessGroupAtShutdown")
LAUNCH_JOBKEY_POLICIES = c_char_p("Policies")
LAUNCH_JOBKEY_ENABLETRANSACTIONS = c_char_p("EnableTransactions")
LAUNCH_JOBPOLICY_DENYCREATINGOTHERJOBS = c_char_p("DenyCreatingOtherJobs")
LAUNCH_JOBINETDCOMPATIBILITY_WAIT = c_char_p("Wait")
LAUNCH_JOBKEY_MACH_RESETATCLOSE = c_char_p("ResetAtClose")
LAUNCH_JOBKEY_MACH_HIDEUNTILCHECKIN = c_char_p("HideUntilCheckIn")
LAUNCH_JOBKEY_MACH_DRAINMESSAGESONCRASH = c_char_p("DrainMessagesOnCrash")
LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT = c_char_p("SuccessfulExit")
LAUNCH_JOBKEY_KEEPALIVE_NETWORKSTATE = c_char_p("NetworkState")
LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE = c_char_p("PathState")
LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBACTIVE = c_char_p("OtherJobActive")
LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBENABLED = c_char_p("OtherJobEnabled")
LAUNCH_JOBKEY_KEEPALIVE_AFTERINITIALDEMAND = c_char_p("AfterInitialDemand")
LAUNCH_JOBKEY_CAL_MINUTE = c_char_p("Minute")
LAUNCH_JOBKEY_CAL_HOUR = c_char_p("Hour")
LAUNCH_JOBKEY_CAL_DAY = c_char_p("Day")
LAUNCH_JOBKEY_CAL_WEEKDAY = c_char_p("Weekday")
LAUNCH_JOBKEY_CAL_MONTH = c_char_p("Month")
LAUNCH_JOBKEY_RESOURCELIMIT_CORE = c_char_p("Core")
LAUNCH_JOBKEY_RESOURCELIMIT_CPU = c_char_p("CPU")
LAUNCH_JOBKEY_RESOURCELIMIT_DATA = c_char_p("Data")
LAUNCH_JOBKEY_RESOURCELIMIT_FSIZE = c_char_p("FileSize")
LAUNCH_JOBKEY_RESOURCELIMIT_MEMLOCK = c_char_p("MemoryLock")
LAUNCH_JOBKEY_RESOURCELIMIT_NOFILE = c_char_p("NumberOfFiles")
LAUNCH_JOBKEY_RESOURCELIMIT_NPROC = c_char_p("NumberOfProcesses")
LAUNCH_JOBKEY_RESOURCELIMIT_RSS = c_char_p("ResidentSetSize")
LAUNCH_JOBKEY_RESOURCELIMIT_STACK = c_char_p("Stack")
LAUNCH_JOBKEY_DISABLED_MACHINETYPE = c_char_p("MachineType")
LAUNCH_JOBKEY_DISABLED_MODELNAME = c_char_p("ModelName")
LAUNCH_JOBSOCKETKEY_TYPE = c_char_p("SockType")
LAUNCH_JOBSOCKETKEY_PASSIVE = c_char_p("SockPassive")
LAUNCH_JOBSOCKETKEY_BONJOUR = c_char_p("Bonjour")
LAUNCH_JOBSOCKETKEY_SECUREWITHKEY = c_char_p("SecureSocketWithKey")
LAUNCH_JOBSOCKETKEY_PATHNAME = c_char_p("SockPathName")
LAUNCH_JOBSOCKETKEY_PATHMODE = c_char_p("SockPathMode")
LAUNCH_JOBSOCKETKEY_NODENAME = c_char_p("SockNodeName")
LAUNCH_JOBSOCKETKEY_SERVICENAME = c_char_p("SockServiceName")
LAUNCH_JOBSOCKETKEY_FAMILY = c_char_p("SockFamily")
LAUNCH_JOBSOCKETKEY_PROTOCOL = c_char_p("SockProtocol")
LAUNCH_JOBSOCKETKEY_MULTICASTGROUP = c_char_p("MulticastGroup")
(
LAUNCH_DATA_DICTIONARY,
LAUNCH_DATA_ARRAY,
LAUNCH_DATA_FD,
LAUNCH_DATA_INTEGER,
LAUNCH_DATA_REAL,
LAUNCH_DATA_BOOL,
LAUNCH_DATA_STRING,
LAUNCH_DATA_OPAQUE,
LAUNCH_DATA_ERRNO,
LAUNCH_DATA_MACHPORT
) = range(1, 11)
class LaunchDCheckInError(Exception):
pass
def get_launchd_socket_fds():
"""Check in with launchd to get socket file descriptors."""
# Return a dictionary with keys pointing to lists of file descriptors.
launchd_socket_fds = dict()
# Callback for dict iterator.
def add_socket(launch_array, name, context=None):
if launch_data_get_type(launch_array) != LAUNCH_DATA_ARRAY:
raise LaunchDCheckInError("Could not get file descriptor array: Type mismatch")
fds = list()
for i in range(launch_data_array_get_count(launch_array)):
data_fd = launch_data_array_get_index(launch_array, i)
if launch_data_get_type(data_fd) != LAUNCH_DATA_FD:
raise LaunchDCheckInError("Could not get file descriptor array entry: Type mismatch")
fds.append(launch_data_get_fd(data_fd))
launchd_socket_fds[name] = fds
# Wrap in try/finally to free resources allocated during lookup.
try:
# Create a checkin request.
checkin_request = launch_data_new_string(LAUNCH_KEY_CHECKIN);
if checkin_request == None:
raise LaunchDCheckInError("Could not create checkin request")
# Check the checkin response.
checkin_response = launch_msg(checkin_request);
if checkin_response == None:
raise LaunchDCheckInError("Error checking in")
if launch_data_get_type(checkin_response) == LAUNCH_DATA_ERRNO:
errno = launch_data_get_errno(checkin_response)
raise LaunchDCheckInError("Checkin failed")
# Get a dictionary of sockets.
sockets = launch_data_dict_lookup(checkin_response, LAUNCH_JOBKEY_SOCKETS);
if sockets == None:
raise LaunchDCheckInError("Could not get socket dictionary from checkin response")
if launch_data_get_type(sockets) != LAUNCH_DATA_DICTIONARY:
raise LaunchDCheckInError("Could not get socket dictionary from checkin response: Type mismatch")
# Iterate over the items with add_socket callback.
launch_data_dict_iterate(sockets, DICTITCALLBACK(add_socket), None)
return launchd_socket_fds
finally:
if checkin_response is not None:
launch_data_free(checkin_response)
if checkin_request is not None:
launch_data_free(checkin_request)
@@ -2,23 +2,27 @@
#
# Copyright 2011-2017 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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,
# 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.
"""
launchd.py
launchd
Created by Greg Neagle on 2011-07-22.
get_socket_fd and refactoring by Greg Neagle on 2017-04-14.
A wrapper for using launchd to run a process as root outside of munki's
Code for getting a socket file descriptor from launchd.
Returns a file descriptor for a socket defined in a launchd plist.
-and-
A wrapper for using launchd to run a process as root outside of Munki's
process space. Needed to properly run /usr/sbin/softwareupdate, for example.
"""
@@ -27,8 +31,37 @@ import subprocess
import time
import uuid
from . import osutils
from . import FoundationPlist
from .. import osutils
from .. import FoundationPlist
def get_socket_fd(socket_name):
'''Get socket file descriptors from launchd.'''
os_version = osutils.getOsVersion(as_tuple=True)
if os_version >= (10, 10):
# use new launchd api
from . import launch2
try:
sockets = launch2.launch_activate_socket(socket_name)
except launch2.LaunchDError:
# no sockets found
return None
return sockets[0]
else:
# use old launchd api
from . import launch1
try:
socket_dict = launch1.get_launchd_socket_fds()
except launch1.LaunchDCheckInError:
# no sockets found
return None
if socket_name not in socket_dict:
# no sockets found with the expected name
return None
return socket_dict[socket_name][0]
class LaunchdJobException(Exception):
@@ -177,4 +210,4 @@ class Job(object):
if __name__ == '__main__':
print 'This is a library of support tools for the Munki Suite.'
print 'This is a library of support tools for the Munki Suite.'
+271
View File
@@ -0,0 +1,271 @@
#
# Copyright 2010 Per Olofsson
#
# 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
#
# http://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.
'''Python wrapper for original launchd checkin API'''
# pylint: disable=wildcard-import
# pylint: disable=unused-wildcard-import
from ctypes import *
# pylint: enable=unused-wildcard-import
# pylint: enable=wildcard-import
# pylint: disable=invalid-name
libc = CDLL("/usr/lib/libc.dylib")
# pylint: enable=invalid-name
c_launch_data_t = c_void_p
# size_t launch_data_array_get_count(const launch_data_t)
launch_data_array_get_count = libc.launch_data_array_get_count
launch_data_array_get_count.restype = c_size_t
launch_data_array_get_count.argtypes = [c_launch_data_t]
#launch_data_t launch_data_array_get_index(const launch_data_t, size_t)
# __ld_getter;
launch_data_array_get_index = libc.launch_data_array_get_index
launch_data_array_get_index.restype = c_launch_data_t
launch_data_array_get_index.argtypes = [c_launch_data_t, c_size_t]
# size_t launch_data_dict_get_count(const launch_data_t)
launch_data_dict_get_count = libc.launch_data_dict_get_count
launch_data_dict_get_count.restype = c_size_t
launch_data_dict_get_count.argtypes = [c_launch_data_t]
# launch_data_t launch_data_dict_lookup(const launch_data_t, const char *)
launch_data_dict_lookup = libc.launch_data_dict_lookup
launch_data_dict_lookup.restype = c_launch_data_t
launch_data_dict_lookup.argtypes = [c_launch_data_t, c_char_p]
#void launch_data_dict_iterate(const launch_data_t, void (*)
# (const launch_data_t, const char *, void *), void *) __ld_iterator(1, 2)
DICTITCALLBACK = CFUNCTYPE(c_void_p, c_launch_data_t, c_char_p, c_void_p)
launch_data_dict_iterate = libc.launch_data_dict_iterate
launch_data_dict_iterate.restype = None
launch_data_dict_iterate.argtypes = [c_launch_data_t, DICTITCALLBACK, c_void_p]
# void launch_data_free(launch_data_t)
launch_data_free = libc.launch_data_free
launch_data_free.restype = None
launch_data_free.argtypes = [c_launch_data_t]
# int launch_data_get_errno(const launch_data_t)
launch_data_get_errno = libc.launch_data_get_errno
launch_data_get_errno.restype = c_int
launch_data_get_errno.argtypes = [c_launch_data_t]
# int launch_data_get_fd(const launch_data_t)
launch_data_get_fd = libc.launch_data_get_fd
launch_data_get_fd.restype = c_int
launch_data_get_fd.argtypes = [c_launch_data_t]
# launch_data_type_t launch_data_get_type(const launch_data_t)
launch_data_get_type = libc.launch_data_get_type
launch_data_get_type.restype = c_launch_data_t
launch_data_get_type.argtypes = [c_launch_data_t]
# launch_data_t launch_data_new_string(const char *)
launch_data_new_string = libc.launch_data_new_string
launch_data_new_string.restype = c_launch_data_t
launch_data_new_string.argtypes = [c_char_p]
# launch_data_t launch_msg(const launch_data_t)
launch_msg = libc.launch_msg
launch_msg.restype = c_launch_data_t
launch_msg.argtypes = [c_launch_data_t]
LAUNCH_KEY_SUBMITJOB = c_char_p("SubmitJob")
LAUNCH_KEY_REMOVEJOB = c_char_p("RemoveJob")
LAUNCH_KEY_STARTJOB = c_char_p("StartJob")
LAUNCH_KEY_STOPJOB = c_char_p("StopJob")
LAUNCH_KEY_GETJOB = c_char_p("GetJob")
LAUNCH_KEY_GETJOBS = c_char_p("GetJobs")
LAUNCH_KEY_CHECKIN = c_char_p("CheckIn")
LAUNCH_JOBKEY_LABEL = c_char_p("Label")
LAUNCH_JOBKEY_DISABLED = c_char_p("Disabled")
LAUNCH_JOBKEY_USERNAME = c_char_p("UserName")
LAUNCH_JOBKEY_GROUPNAME = c_char_p("GroupName")
LAUNCH_JOBKEY_TIMEOUT = c_char_p("TimeOut")
LAUNCH_JOBKEY_EXITTIMEOUT = c_char_p("ExitTimeOut")
LAUNCH_JOBKEY_INITGROUPS = c_char_p("InitGroups")
LAUNCH_JOBKEY_SOCKETS = c_char_p("Sockets")
LAUNCH_JOBKEY_MACHSERVICES = c_char_p("MachServices")
LAUNCH_JOBKEY_MACHSERVICELOOKUPPOLICIES = c_char_p("MachServiceLookupPolicies")
LAUNCH_JOBKEY_INETDCOMPATIBILITY = c_char_p("inetdCompatibility")
LAUNCH_JOBKEY_ENABLEGLOBBING = c_char_p("EnableGlobbing")
LAUNCH_JOBKEY_PROGRAMARGUMENTS = c_char_p("ProgramArguments")
LAUNCH_JOBKEY_PROGRAM = c_char_p("Program")
LAUNCH_JOBKEY_ONDEMAND = c_char_p("OnDemand")
LAUNCH_JOBKEY_KEEPALIVE = c_char_p("KeepAlive")
LAUNCH_JOBKEY_LIMITLOADTOHOSTS = c_char_p("LimitLoadToHosts")
LAUNCH_JOBKEY_LIMITLOADFROMHOSTS = c_char_p("LimitLoadFromHosts")
LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE = c_char_p("LimitLoadToSessionType")
LAUNCH_JOBKEY_RUNATLOAD = c_char_p("RunAtLoad")
LAUNCH_JOBKEY_ROOTDIRECTORY = c_char_p("RootDirectory")
LAUNCH_JOBKEY_WORKINGDIRECTORY = c_char_p("WorkingDirectory")
LAUNCH_JOBKEY_ENVIRONMENTVARIABLES = c_char_p("EnvironmentVariables")
LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES = c_char_p("UserEnvironmentVariables")
LAUNCH_JOBKEY_UMASK = c_char_p("Umask")
LAUNCH_JOBKEY_NICE = c_char_p("Nice")
LAUNCH_JOBKEY_HOPEFULLYEXITSFIRST = c_char_p("HopefullyExitsFirst")
LAUNCH_JOBKEY_HOPEFULLYEXITSLAST = c_char_p("HopefullyExitsLast")
LAUNCH_JOBKEY_LOWPRIORITYIO = c_char_p("LowPriorityIO")
LAUNCH_JOBKEY_SESSIONCREATE = c_char_p("SessionCreate")
LAUNCH_JOBKEY_STARTONMOUNT = c_char_p("StartOnMount")
LAUNCH_JOBKEY_SOFTRESOURCELIMITS = c_char_p("SoftResourceLimits")
LAUNCH_JOBKEY_HARDRESOURCELIMITS = c_char_p("HardResourceLimits")
LAUNCH_JOBKEY_STANDARDINPATH = c_char_p("StandardInPath")
LAUNCH_JOBKEY_STANDARDOUTPATH = c_char_p("StandardOutPath")
LAUNCH_JOBKEY_STANDARDERRORPATH = c_char_p("StandardErrorPath")
LAUNCH_JOBKEY_DEBUG = c_char_p("Debug")
LAUNCH_JOBKEY_WAITFORDEBUGGER = c_char_p("WaitForDebugger")
LAUNCH_JOBKEY_QUEUEDIRECTORIES = c_char_p("QueueDirectories")
LAUNCH_JOBKEY_WATCHPATHS = c_char_p("WatchPaths")
LAUNCH_JOBKEY_STARTINTERVAL = c_char_p("StartInterval")
LAUNCH_JOBKEY_STARTCALENDARINTERVAL = c_char_p("StartCalendarInterval")
LAUNCH_JOBKEY_BONJOURFDS = c_char_p("BonjourFDs")
LAUNCH_JOBKEY_LASTEXITSTATUS = c_char_p("LastExitStatus")
LAUNCH_JOBKEY_PID = c_char_p("PID")
LAUNCH_JOBKEY_THROTTLEINTERVAL = c_char_p("ThrottleInterval")
LAUNCH_JOBKEY_LAUNCHONLYONCE = c_char_p("LaunchOnlyOnce")
LAUNCH_JOBKEY_ABANDONPROCESSGROUP = c_char_p("AbandonProcessGroup")
LAUNCH_JOBKEY_IGNOREPROCESSGROUPATSHUTDOWN = c_char_p(
"IgnoreProcessGroupAtShutdown")
LAUNCH_JOBKEY_POLICIES = c_char_p("Policies")
LAUNCH_JOBKEY_ENABLETRANSACTIONS = c_char_p("EnableTransactions")
LAUNCH_JOBPOLICY_DENYCREATINGOTHERJOBS = c_char_p("DenyCreatingOtherJobs")
LAUNCH_JOBINETDCOMPATIBILITY_WAIT = c_char_p("Wait")
LAUNCH_JOBKEY_MACH_RESETATCLOSE = c_char_p("ResetAtClose")
LAUNCH_JOBKEY_MACH_HIDEUNTILCHECKIN = c_char_p("HideUntilCheckIn")
LAUNCH_JOBKEY_MACH_DRAINMESSAGESONCRASH = c_char_p("DrainMessagesOnCrash")
LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT = c_char_p("SuccessfulExit")
LAUNCH_JOBKEY_KEEPALIVE_NETWORKSTATE = c_char_p("NetworkState")
LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE = c_char_p("PathState")
LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBACTIVE = c_char_p("OtherJobActive")
LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBENABLED = c_char_p("OtherJobEnabled")
LAUNCH_JOBKEY_KEEPALIVE_AFTERINITIALDEMAND = c_char_p("AfterInitialDemand")
LAUNCH_JOBKEY_CAL_MINUTE = c_char_p("Minute")
LAUNCH_JOBKEY_CAL_HOUR = c_char_p("Hour")
LAUNCH_JOBKEY_CAL_DAY = c_char_p("Day")
LAUNCH_JOBKEY_CAL_WEEKDAY = c_char_p("Weekday")
LAUNCH_JOBKEY_CAL_MONTH = c_char_p("Month")
LAUNCH_JOBKEY_RESOURCELIMIT_CORE = c_char_p("Core")
LAUNCH_JOBKEY_RESOURCELIMIT_CPU = c_char_p("CPU")
LAUNCH_JOBKEY_RESOURCELIMIT_DATA = c_char_p("Data")
LAUNCH_JOBKEY_RESOURCELIMIT_FSIZE = c_char_p("FileSize")
LAUNCH_JOBKEY_RESOURCELIMIT_MEMLOCK = c_char_p("MemoryLock")
LAUNCH_JOBKEY_RESOURCELIMIT_NOFILE = c_char_p("NumberOfFiles")
LAUNCH_JOBKEY_RESOURCELIMIT_NPROC = c_char_p("NumberOfProcesses")
LAUNCH_JOBKEY_RESOURCELIMIT_RSS = c_char_p("ResidentSetSize")
LAUNCH_JOBKEY_RESOURCELIMIT_STACK = c_char_p("Stack")
LAUNCH_JOBKEY_DISABLED_MACHINETYPE = c_char_p("MachineType")
LAUNCH_JOBKEY_DISABLED_MODELNAME = c_char_p("ModelName")
LAUNCH_JOBSOCKETKEY_TYPE = c_char_p("SockType")
LAUNCH_JOBSOCKETKEY_PASSIVE = c_char_p("SockPassive")
LAUNCH_JOBSOCKETKEY_BONJOUR = c_char_p("Bonjour")
LAUNCH_JOBSOCKETKEY_SECUREWITHKEY = c_char_p("SecureSocketWithKey")
LAUNCH_JOBSOCKETKEY_PATHNAME = c_char_p("SockPathName")
LAUNCH_JOBSOCKETKEY_PATHMODE = c_char_p("SockPathMode")
LAUNCH_JOBSOCKETKEY_NODENAME = c_char_p("SockNodeName")
LAUNCH_JOBSOCKETKEY_SERVICENAME = c_char_p("SockServiceName")
LAUNCH_JOBSOCKETKEY_FAMILY = c_char_p("SockFamily")
LAUNCH_JOBSOCKETKEY_PROTOCOL = c_char_p("SockProtocol")
LAUNCH_JOBSOCKETKEY_MULTICASTGROUP = c_char_p("MulticastGroup")
(
LAUNCH_DATA_DICTIONARY,
LAUNCH_DATA_ARRAY,
LAUNCH_DATA_FD,
LAUNCH_DATA_INTEGER,
LAUNCH_DATA_REAL,
LAUNCH_DATA_BOOL,
LAUNCH_DATA_STRING,
LAUNCH_DATA_OPAQUE,
LAUNCH_DATA_ERRNO,
LAUNCH_DATA_MACHPORT
) = range(1, 11)
class LaunchDCheckInError(Exception):
'''Exception to raise if there is a checkin error'''
pass
def get_launchd_socket_fds():
"""Check in with launchd to get socket file descriptors."""
# Return a dictionary with keys pointing to lists of file descriptors.
launchd_socket_fds = dict()
def add_socket(launch_array, name, context=None):
'''Callback for dict iterator.'''
if launch_data_get_type(launch_array) != LAUNCH_DATA_ARRAY:
raise LaunchDCheckInError(
"Could not get file descriptor array: Type mismatch")
fds = list()
for i in range(launch_data_array_get_count(launch_array)):
data_fd = launch_data_array_get_index(launch_array, i)
if launch_data_get_type(data_fd) != LAUNCH_DATA_FD:
raise LaunchDCheckInError(
"Could not get file descriptor array entry: Type mismatch")
fds.append(launch_data_get_fd(data_fd))
launchd_socket_fds[name] = fds
# Wrap in try/finally to free resources allocated during lookup.
try:
# Create a checkin request.
checkin_request = launch_data_new_string(LAUNCH_KEY_CHECKIN)
if checkin_request is None:
raise LaunchDCheckInError("Could not create checkin request")
# Check the checkin response.
checkin_response = launch_msg(checkin_request)
if checkin_response is None:
raise LaunchDCheckInError("Error checking in")
if launch_data_get_type(checkin_response) == LAUNCH_DATA_ERRNO:
errno = launch_data_get_errno(checkin_response)
raise LaunchDCheckInError("Checkin failed")
# Get a dictionary of sockets.
sockets = launch_data_dict_lookup(
checkin_response, LAUNCH_JOBKEY_SOCKETS)
if sockets is None:
raise LaunchDCheckInError(
"Could not get socket dictionary from checkin response")
if launch_data_get_type(sockets) != LAUNCH_DATA_DICTIONARY:
raise LaunchDCheckInError(
"Could not get socket dictionary from checkin response: "
"Type mismatch")
# Iterate over the items with add_socket callback.
launch_data_dict_iterate(sockets, DICTITCALLBACK(add_socket), None)
return launchd_socket_fds
finally:
if checkin_response is not None:
launch_data_free(checkin_response)
if checkin_request is not None:
launch_data_free(checkin_request)
@@ -12,19 +12,27 @@
# 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.
'''Python wrapper for updated launchd checkin API'''
import os
# pylint: disable=wildcard-import
# pylint: disable=unused-wildcard-import
from ctypes import *
# pylint: enable=unused-wildcard-import
# pylint: enable=wildcard-import
# pylint: disable=invalid-name
libc = CDLL("/usr/lib/libc.dylib")
# pylint: enable=invalid-name
# int launch_activate_socket(const char *name, int **fds, size_t *cnt)
libc.launch_activate_socket.restype = c_int
libc.launch_activate_socket.argtypes = [c_char_p, POINTER(POINTER(c_int)), POINTER(c_size_t)]
libc.launch_activate_socket.argtypes = [c_char_p, POINTER(POINTER(c_int)),
POINTER(c_size_t)]
class LaunchDError(Exception):
'''Exception to raise if there is a checkin error'''
pass
def launch_activate_socket(name):
@@ -36,8 +44,9 @@ def launch_activate_socket(name):
cnt = c_size_t(0)
err = libc.launch_activate_socket(name, byref(fds), byref(cnt))
if err:
raise LaunchDError("Failed to retrieve sockets from launchd: %s" % os.strerror(err))
raise LaunchDError("Failed to retrieve sockets from launchd: %s"
% os.strerror(err))
# Return a list of file descriptors.
return list(fds[x] for x in xrange(cnt.value))