[38876] branches/gsoc08-framework/MacPorts_Framework
armahg at macports.org
armahg at macports.org
Fri Aug 1 07:07:23 PDT 2008
Revision: 38876
http://trac.macosforge.org/projects/macports/changeset/38876
Author: armahg at macports.org
Date: 2008-08-01 07:07:22 -0700 (Fri, 01 Aug 2008)
Log Message:
-----------
Added BetterAuthorization and MPHelper files. Project set up as described in BetterAuthorizationSample documentation but crashes on running Test Bundle. Time to debug
Modified Paths:
--------------
branches/gsoc08-framework/MacPorts_Framework/MPHelperTool.m
branches/gsoc08-framework/MacPorts_Framework/MPInterpreter.h
branches/gsoc08-framework/MacPorts_Framework/MPInterpreter.m
branches/gsoc08-framework/MacPorts_Framework/MPInterpreterTest.h
branches/gsoc08-framework/MacPorts_Framework/MPInterpreterTest.m
branches/gsoc08-framework/MacPorts_Framework/MPMacPortsTest.h
branches/gsoc08-framework/MacPorts_Framework/MPMacPortsTest.m
branches/gsoc08-framework/MacPorts_Framework/MacPorts.Framework.xcodeproj/project.pbxproj
Added Paths:
-----------
branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLib.c
branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLib.h
branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLibInstallTool.c
branches/gsoc08-framework/MacPorts_Framework/MPHelperCommon.c
branches/gsoc08-framework/MacPorts_Framework/MPHelperCommon.h
Added: branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLib.c
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLib.c (rev 0)
+++ branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLib.c 2008-08-01 14:07:22 UTC (rev 38876)
@@ -0,0 +1,2420 @@
+/*
+ File: BetterAuthorizationSampleLib.c
+
+ Contains: Implementation of reusable code for privileged helper tools.
+
+ Written by: DTS
+
+ Copyright: Copyright (c) 2007 Apple Inc. All Rights Reserved.
+
+ Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple, Inc.
+ ("Apple") in consideration of your agreement to the following terms, and your
+ use, installation, modification or redistribution of this Apple software
+ constitutes acceptance of these terms. If you do not agree with these terms,
+ please do not use, install, modify or redistribute this Apple software.
+
+ In consideration of your agreement to abide by the following terms, and subject
+ to these terms, Apple grants you a personal, non-exclusive license, under Apple's
+ copyrights in this original Apple software (the "Apple Software"), to use,
+ reproduce, modify and redistribute the Apple Software, with or without
+ modifications, in source and/or binary forms; provided that if you redistribute
+ the Apple Software in its entirety and without modifications, you must retain
+ this notice and the following text and disclaimers in all such redistributions of
+ the Apple Software. Neither the name, trademarks, service marks or logos of
+ Apple, Inc. may be used to endorse or promote products derived from the
+ Apple Software without specific prior written permission from Apple. Except as
+ expressly stated in this notice, no other rights or licenses, express or implied,
+ are granted by Apple herein, including but not limited to any patent rights that
+ may be infringed by your derivative works or by other works in which the Apple
+ Software may be incorporated.
+
+ The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
+ WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
+ WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
+ COMBINATION WITH YOUR PRODUCTS.
+
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
+ OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
+ (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+// Define BAS_PRIVATE so that we pick up our private definitions from
+// "BetterAuthorizationSampleLib.h".
+
+#define BAS_PRIVATE 1
+
+#include "BetterAuthorizationSampleLib.h"
+
+#include <launch.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/event.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+
+// At runtime BAS only requires CoreFoundation. However, at build time we need
+// CoreServices for the various OSStatus error codes in "MacErrors.h". Thus, by default,
+// we include CoreServices at build time. However, you can flip this switch to check
+// that you're not accidentally using any other CoreServices things.
+
+#if 1
+ #include <CoreServices/CoreServices.h>
+#else
+ #warning Do not ship this way!
+ #include <CoreFoundation/CoreFoundation.h>
+ #include "/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/MacErrors.h"
+#endif
+
+//////////////////////////////////////////////////////////////////////////////////
+#pragma mark ***** Constants
+
+enum {
+ kIdleTimeoutInSeconds = 120, // if we get no requests in 2 minutes, we quit
+ kWatchdogTimeoutInSeconds = 65 // any given request must be completely in 65 seconds
+};
+
+// IMPORTANT:
+// These values must be greater than 60 seconds. If a job runs for less than 60
+// seconds, launchd will consider it to have failed.
+
+// kBASMaxNumberOfKBytes has two uses:
+//
+// 1. When receiving a dictionary, it is used to limit the size of the incoming
+// data. This ensures that a non-privileged client can't exhaust the
+// address space of a privileged helper tool.
+//
+// 2. Because it's less than 4 GB, this limit ensures that the dictionary size
+// can be sent as an architecture-neutral uint32_t.
+
+#define kBASMaxNumberOfKBytes (1024 * 1024)
+
+// A hard-wired file system path for the UNIX domain socket; %s is the placeholder
+// for the bundle ID (in file system representation).
+
+#define kBASSocketPathFormat "/var/run/%s.socket"
+
+// The key used to get our describe our socket in the launchd property list file.
+
+#define kLaunchDSocketDictKey "MasterSocket"
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Common Code
+
+extern int BASOSStatusToErrno(OSStatus errNum)
+ // See comment in header.
+{
+ int retval;
+
+ #define CASE(ident) \
+ case k ## ident ## Err: \
+ retval = ident; \
+ break
+ switch (errNum) {
+ case noErr:
+ retval = 0;
+ break;
+ case kENORSRCErr:
+ retval = ESRCH; // no ENORSRC on Mac OS X, so use ESRCH
+ break;
+ case memFullErr:
+ retval = ENOMEM;
+ break;
+ CASE(EDEADLK);
+ CASE(EAGAIN);
+ case kEOPNOTSUPPErr:
+ retval = ENOTSUP;
+ break;
+ CASE(EPROTO);
+ CASE(ETIME);
+ CASE(ENOSR);
+ CASE(EBADMSG);
+ case kECANCELErr:
+ retval = ECANCELED; // note spelling difference
+ break;
+ CASE(ENOSTR);
+ CASE(ENODATA);
+ CASE(EINPROGRESS);
+ CASE(ESRCH);
+ CASE(ENOMSG);
+ default:
+ if ( (errNum <= kEPERMErr) && (errNum >= kENOMSGErr) ) {
+ retval = (-3200 - errNum) + 1; // OT based error
+ } else if ( (errNum >= errSecErrnoBase) && (errNum <= (errSecErrnoBase + ELAST)) ) {
+ retval = (int) errNum - errSecErrnoBase; // POSIX based error
+ } else {
+ retval = (int) errNum; // just return the value unmodified
+ }
+ }
+ #undef CASE
+ return retval;
+}
+
+extern OSStatus BASErrnoToOSStatus(int errNum)
+ // See comment in header.
+{
+ OSStatus retval;
+
+ if ( errNum == 0 ) {
+ retval = noErr;
+ } else if ( (errNum >= EPERM) && (errNum <= ELAST) ) {
+ retval = (OSStatus) errNum + errSecErrnoBase;
+ } else {
+ retval = (int) errNum; // just return the value unmodified
+ }
+
+ return retval;
+}
+
+static Boolean BASIsBinaryPropertyListData(const void * plistBuffer, size_t plistSize)
+ // Make sure that whatever is passed into the buffer that will
+ // eventually become a plist (and then sequentially a dictionary)
+ // is NOT in binary format.
+{
+ static const char kBASBinaryPlistWatermark[6] = "bplist";
+
+ assert(plistBuffer != NULL);
+
+ return (plistSize >= sizeof(kBASBinaryPlistWatermark))
+ && (memcmp(plistBuffer, kBASBinaryPlistWatermark, sizeof(kBASBinaryPlistWatermark)) == 0);
+}
+
+static void NormaliseOSStatusErrorCode(OSStatus *errPtr)
+ // Normalise the cancelled error code to reduce the number of checks that our clients
+ // have to do. I made this a function in case I ever want to expand this to handle
+ // more than just this one case.
+{
+ assert(errPtr != NULL);
+
+ if ( (*errPtr == errAuthorizationCanceled) || (*errPtr == (errSecErrnoBase + ECANCELED)) ) {
+ *errPtr = userCanceledErr;
+ }
+}
+
+static int BASRead(int fd, void *buf, size_t bufSize, size_t *bytesRead)
+ // A wrapper around <x-man-page://2/read> that keeps reading until either
+ // bufSize bytes are read or until EOF is encountered, in which case you get
+ // EPIPE.
+ //
+ // If bytesRead is not NULL, *bytesRead will be set to the number
+ // of bytes successfully read. On success, this will always be equal to
+ // bufSize. On error, it indicates how much was read before the error
+ // occurred (which could be zero).
+{
+ int err;
+ char * cursor;
+ size_t bytesLeft;
+ ssize_t bytesThisTime;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+ assert(buf != NULL);
+ // bufSize may be 0
+ assert(bufSize <= kBASMaxNumberOfKBytes);
+ // bytesRead may be NULL
+
+ err = 0;
+ bytesLeft = bufSize;
+ cursor = (char *) buf;
+ while ( (err == 0) && (bytesLeft != 0) ) {
+ bytesThisTime = read(fd, cursor, bytesLeft);
+ if (bytesThisTime > 0) {
+ cursor += bytesThisTime;
+ bytesLeft -= bytesThisTime;
+ } else if (bytesThisTime == 0) {
+ err = EPIPE;
+ } else {
+ assert(bytesThisTime == -1);
+
+ err = errno;
+ assert(err != 0);
+ if (err == EINTR) {
+ err = 0; // let's loop again
+ }
+ }
+ }
+ if (bytesRead != NULL) {
+ *bytesRead = bufSize - bytesLeft;
+ }
+
+ return err;
+}
+
+static int BASWrite(int fd, const void *buf, size_t bufSize, size_t *bytesWritten)
+ // A wrapper around <x-man-page://2/write> that keeps writing until either
+ // all the data is written or an error occurs, in which case
+ // you get EPIPE.
+ //
+ // If bytesWritten is not NULL, *bytesWritten will be set to the number
+ // of bytes successfully written. On success, this will always be equal to
+ // bufSize. On error, it indicates how much was written before the error
+ // occurred (which could be zero).
+{
+ int err;
+ char * cursor;
+ size_t bytesLeft;
+ ssize_t bytesThisTime;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+ assert(buf != NULL);
+ // bufSize may be 0
+ assert(bufSize <= kBASMaxNumberOfKBytes);
+ // bytesWritten may be NULL
+
+ // SIGPIPE occurs when you write to pipe or socket
+ // whose other end has been closed. The default action
+ // for SIGPIPE is to terminate the process. That's
+ // probably not what you wanted. So, in the debug build,
+ // we check that you've set the signal action to SIG_IGN
+ // (ignore). Of course, you could be building a program
+ // that needs SIGPIPE to work in some special way, in
+ // which case you should define BAS_WRITE_CHECK_SIGPIPE
+ // to 0 to bypass this check.
+
+ #if !defined(BAS_WRITE_CHECK_SIGPIPE)
+ #define BAS_WRITE_CHECK_SIGPIPE 1
+ #endif
+ #if !defined(NDEBUG) && BAS_WRITE_CHECK_SIGPIPE
+ {
+ int junk;
+ struct stat sb;
+ struct sigaction currentSignalState;
+ int val;
+ socklen_t valLen;
+
+ junk = fstat(fd, &sb);
+ assert(junk == 0);
+
+ if ( S_ISFIFO(sb.st_mode) || S_ISSOCK(sb.st_mode) ) {
+ junk = sigaction(SIGPIPE, NULL, ¤tSignalState);
+ assert(junk == 0);
+
+ valLen = sizeof(val);
+ junk = getsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &val, &valLen);
+ assert(junk == 0);
+ assert(valLen == sizeof(val));
+
+ // If you hit this assertion, you need to either disable SIGPIPE in
+ // your process or on the specific socket you're writing to. The
+ // standard code for the former is:
+ //
+ // (void) signal(SIGPIPE, SIG_IGN);
+ //
+ // You typically add this code to your main function.
+ //
+ // The standard code for the latter is:
+ //
+ // static const int kOne = 1;
+ // err = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &kOne, sizeof(kOne));
+ //
+ // You typically do this just after creating the socket.
+
+ assert( (currentSignalState.sa_handler == SIG_IGN) || (val == 1) );
+ }
+ }
+ #endif
+
+ err = 0;
+ bytesLeft = bufSize;
+ cursor = (char *) buf;
+ while ( (err == 0) && (bytesLeft != 0) ) {
+ bytesThisTime = write(fd, cursor, bytesLeft);
+ if (bytesThisTime > 0) {
+ cursor += bytesThisTime;
+ bytesLeft -= bytesThisTime;
+ } else if (bytesThisTime == 0) {
+ assert(false);
+ err = EPIPE;
+ } else {
+ assert(bytesThisTime == -1);
+
+ err = errno;
+ assert(err != 0);
+ if (err == EINTR) {
+ err = 0; // let's loop again
+ }
+ }
+ }
+ if (bytesWritten != NULL) {
+ *bytesWritten = bufSize - bytesLeft;
+ }
+
+ return err;
+}
+
+static int BASReadDictionary(int fdIn, CFDictionaryRef *dictPtr)
+ // Create a CFDictionary by reading the XML data from fdIn.
+ // It first reads the size of the XML data, then allocates a
+ // buffer for that data, then reads the data in, and finally
+ // unflattens the data into a CFDictionary.
+ //
+ // On success, the caller is responsible for releasing *dictPtr.
+ //
+ // See also the companion routine, BASWriteDictionary, below.
+{
+ int err = 0;
+ uint32_t dictSize;
+ void * dictBuffer;
+ CFDataRef dictData;
+ CFPropertyListRef dict;
+
+ // Pre-conditions
+
+ assert(fdIn >= 0);
+ assert( dictPtr != NULL);
+ assert(*dictPtr == NULL);
+
+ dictBuffer = NULL;
+ dictData = NULL;
+ dict = NULL;
+
+ // Read the data size and allocate a buffer. Always read the length as a big-endian
+ // uint32_t, so that the app and the helper tool can be different architectures.
+
+ err = BASRead(fdIn, &dictSize, sizeof(dictSize), NULL);
+ if (err == 0) {
+ dictSize = OSSwapBigToHostInt32(dictSize);
+ if (dictSize == 0) {
+ // According to the C language spec malloc(0) may return NULL (although the Mac OS X
+ // malloc doesn't ever do this), so we specifically check for and error out in
+ // that case.
+ err = EINVAL;
+ } else if (dictSize > kBASMaxNumberOfKBytes) {
+ // Abitrary limit to prevent potentially hostile client overwhelming us with data.
+ err = EINVAL;
+ }
+ }
+ if (err == 0) {
+ dictBuffer = malloc( (size_t) dictSize);
+ if (dictBuffer == NULL) {
+ err = ENOMEM;
+ }
+ }
+
+ // Read the data and unflatten.
+
+ if (err == 0) {
+ err = BASRead(fdIn, dictBuffer, dictSize, NULL);
+ }
+ if ( (err == 0) && BASIsBinaryPropertyListData(dictBuffer, dictSize) ) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ if (err == 0) {
+ dictData = CFDataCreateWithBytesNoCopy(NULL, dictBuffer, dictSize, kCFAllocatorNull);
+ if (dictData == NULL) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ }
+ if (err == 0) {
+ dict = CFPropertyListCreateFromXMLData(NULL, dictData, kCFPropertyListImmutable, NULL);
+ if (dict == NULL) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ }
+ if ( (err == 0) && (CFGetTypeID(dict) != CFDictionaryGetTypeID()) ) {
+ err = EINVAL; // only CFDictionaries need apply
+ }
+ // CFShow(dict);
+
+ // Clean up.
+
+ if (err != 0) {
+ if (dict != NULL) {
+ CFRelease(dict);
+ }
+ dict = NULL;
+ }
+ *dictPtr = (CFDictionaryRef) dict;
+ free(dictBuffer);
+ if (dictData != NULL) {
+ CFRelease(dictData);
+ }
+
+ assert( (err == 0) == (*dictPtr != NULL) );
+
+ return err;
+}
+
+static int BASWriteDictionary(CFDictionaryRef dict, int fdOut)
+ // Write a dictionary to a file descriptor by flattening
+ // it into XML. Send the size of the XML before sending
+ // the data so that BASReadDictionary knows how much to
+ // read.
+ //
+ // See also the companion routine, BASReadDictionary, above.
+{
+ int err = 0;
+ CFDataRef dictData;
+ uint32_t dictSize;
+
+ // Pre-conditions
+
+ assert(dict != NULL);
+ assert(fdOut >= 0);
+
+ dictData = NULL;
+
+ // Get the dictionary as XML data.
+
+ dictData = CFPropertyListCreateXMLData(NULL, dict);
+ if (dictData == NULL) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+
+ // Send the length, then send the data. Always send the length as a big-endian
+ // uint32_t, so that the app and the helper tool can be different architectures.
+ //
+ // The MoreAuthSample version of this code erroneously assumed that CFDataGetBytePtr
+ // can fail and thus allocated an extra buffer to copy the data into. In reality,
+ // CFDataGetBytePtr can't fail, so this version of the code doesn't do the unnecessary
+ // allocation.
+
+ if ( (err == 0) && (CFDataGetLength(dictData) > kBASMaxNumberOfKBytes) ) {
+ err = EINVAL;
+ }
+ if (err == 0) {
+ dictSize = OSSwapHostToBigInt32( CFDataGetLength(dictData) );
+ err = BASWrite(fdOut, &dictSize, sizeof(dictSize), NULL);
+ }
+ if (err == 0) {
+ err = BASWrite(fdOut, CFDataGetBytePtr(dictData), CFDataGetLength(dictData), NULL);
+ }
+
+ if (dictData != NULL) {
+ CFRelease(dictData);
+ }
+
+ return err;
+}
+
+// When we pass a descriptor, we have to pass at least one byte
+// of data along with it, otherwise the recvmsg call will not
+// block if the descriptor hasn't been written to the other end
+// of the socket yet.
+
+static const char kDummyData = 'D';
+
+// Due to a kernel bug in Mac OS X 10.4.x and earlier <rdar://problem/4650646>,
+// you will run into problems if you write data to a socket while a process is
+// trying to receive a descriptor from that socket. A common symptom of this
+// problem is that, if you write two descriptors back-to-back, the second one
+// just disappears.
+//
+// To avoid this problem, we explicitly ACK all descriptor transfers.
+// After writing a descriptor, the sender reads an ACK byte from the socket.
+// After reading a descriptor, the receiver sends an ACK byte (kACKData)
+// to unblock the sender.
+
+static const char kACKData = 'A';
+
+static int BASReadDescriptor(int fd, int *fdRead)
+ // Read a descriptor from fd and place it in *fdRead.
+ //
+ // On success, the caller is responsible for closing *fdRead.
+ //
+ // See the associated BASWriteDescriptor, below.
+{
+ int err;
+ int junk;
+ struct msghdr msg;
+ struct iovec iov;
+ struct {
+ struct cmsghdr hdr;
+ int fd;
+ } control;
+ char dummyData;
+ ssize_t bytesReceived;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+ assert( fdRead != NULL);
+ assert(*fdRead == -1);
+
+ iov.iov_base = (char *) &dummyData;
+ iov.iov_len = sizeof(dummyData);
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = (caddr_t) &control;
+ msg.msg_controllen = sizeof(control);
+ msg.msg_flags = MSG_WAITALL;
+
+ do {
+ bytesReceived = recvmsg(fd, &msg, 0);
+ if (bytesReceived == sizeof(dummyData)) {
+ if ( (dummyData != kDummyData)
+ || (msg.msg_flags != 0)
+ || (msg.msg_control == NULL)
+ || (msg.msg_controllen != sizeof(control))
+ || (control.hdr.cmsg_len != sizeof(control))
+ || (control.hdr.cmsg_level != SOL_SOCKET)
+ || (control.hdr.cmsg_type != SCM_RIGHTS)
+ || (control.fd < 0) ) {
+ err = EINVAL;
+ } else {
+ *fdRead = control.fd;
+ err = 0;
+ }
+ } else if (bytesReceived == 0) {
+ err = EPIPE;
+ } else {
+ assert(bytesReceived == -1);
+
+ err = errno;
+ assert(err != 0);
+ }
+ } while (err == EINTR);
+
+ // Send the ACK. If that fails, we have to act like we never got the
+ // descriptor in our to maintain our post condition.
+
+ if (err == 0) {
+ err = BASWrite(fd, &kACKData, sizeof(kACKData), NULL);
+ if (err != 0) {
+ junk = close(*fdRead);
+ assert(junk == 0);
+ *fdRead = -1;
+ }
+ }
+
+ assert( (err == 0) == (*fdRead >= 0) );
+
+ return err;
+}
+
+static int BASWriteDescriptor(int fd, int fdToWrite)
+ // Write the descriptor fdToWrite to fd.
+ //
+ // See the associated BASReadDescriptor, above.
+{
+ int err;
+ struct msghdr msg;
+ struct iovec iov;
+ struct {
+ struct cmsghdr hdr;
+ int fd;
+ } control;
+ ssize_t bytesSent;
+ char ack;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+ assert(fdToWrite >= 0);
+
+ control.hdr.cmsg_len = sizeof(control);
+ control.hdr.cmsg_level = SOL_SOCKET;
+ control.hdr.cmsg_type = SCM_RIGHTS;
+ control.fd = fdToWrite;
+
+ iov.iov_base = (char *) &kDummyData;
+ iov.iov_len = sizeof(kDummyData);
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = (caddr_t) &control;
+ msg.msg_controllen = control.hdr.cmsg_len;
+ msg.msg_flags = 0;
+ do {
+ bytesSent = sendmsg(fd, &msg, 0);
+ if (bytesSent == sizeof(kDummyData)) {
+ err = 0;
+ } else {
+ assert(bytesSent == -1);
+
+ err = errno;
+ assert(err != 0);
+ }
+ } while (err == EINTR);
+
+ // After writing the descriptor, try to read an ACK back from the
+ // recipient. If that fails, or we get the wrong ACK, we've failed.
+
+ if (err == 0) {
+ err = BASRead(fd, &ack, sizeof(ack), NULL);
+ if ( (err == 0) && (ack != kACKData) ) {
+ err = EINVAL;
+ }
+ }
+
+ return err;
+}
+
+extern void BASCloseDescriptorArray(
+ CFArrayRef descArray
+)
+ // See comment in header.
+{
+ int junk;
+ CFIndex descCount;
+ CFIndex descIndex;
+
+ // I decided to allow descArray to be NULL because it makes it
+ // easier to call this routine using the code.
+ //
+ // BASCloseDescriptorArray((CFArrayRef) CFDictionaryGetValue(response, CFSTR(kBASDescriptorArrayKey)));
+
+ if (descArray != NULL) {
+ if (CFGetTypeID(descArray) == CFArrayGetTypeID()) {
+ descCount = CFArrayGetCount(descArray);
+
+ for (descIndex = 0; descIndex < descCount; descIndex++) {
+ CFNumberRef thisDescNum;
+ int thisDesc;
+
+ thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(descArray, descIndex);
+ if ( (thisDescNum == NULL)
+ || (CFGetTypeID(thisDescNum) != CFNumberGetTypeID())
+ || ! CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc) ) {
+ assert(false);
+ } else {
+ assert(thisDesc >= 0);
+ junk = close(thisDesc);
+ assert(junk == 0);
+ }
+ }
+ } else {
+ assert(false);
+ }
+ }
+}
+
+static int BASReadDictioanaryTranslatingDescriptors(int fd, CFDictionaryRef *dictPtr)
+ // Reads a dictionary and its associated descriptors (if any) from fd,
+ // putting the dictionary (modified to include the translated descriptor
+ // numbers) in *dictPtr.
+ //
+ // On success, the caller is responsible for releasing *dictPtr and for
+ // closing any descriptors it references (BASCloseDescriptorArray makes
+ // the second part easy).
+{
+ int err;
+ int junk;
+ CFDictionaryRef dict;
+ CFArrayRef incomingDescs;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+ assert( dictPtr != NULL);
+ assert(*dictPtr == NULL);
+
+ dict = NULL;
+
+ // Read the dictionary.
+
+ err = BASReadDictionary(fd, &dict);
+
+ // Now read the descriptors, if any.
+
+ if (err == 0) {
+ incomingDescs = (CFArrayRef) CFDictionaryGetValue(dict, CFSTR(kBASDescriptorArrayKey));
+ if (incomingDescs == NULL) {
+ // No descriptors. Not much to do. Just use dict as the response,
+ // NULLing it out so that we don't release it at the end.
+
+ *dictPtr = dict;
+ dict = NULL;
+ } else {
+ CFMutableArrayRef translatedDescs;
+ CFMutableDictionaryRef mutableDict;
+ CFIndex descCount;
+ CFIndex descIndex;
+
+ // We have descriptors, so there's lots of stuff to do. Have to
+ // receive each of the descriptors assemble them into the
+ // translatedDesc array, then create a mutable dictionary based
+ // on response (mutableDict) and replace the
+ // kBASDescriptorArrayKey with translatedDesc.
+
+ translatedDescs = NULL;
+ mutableDict = NULL;
+
+ // Start by checking incomingDescs.
+
+ if ( CFGetTypeID(incomingDescs) != CFArrayGetTypeID() ) {
+ err = EINVAL;
+ }
+
+ // Create our output data.
+
+ if (err == 0) {
+ translatedDescs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ if (translatedDescs == NULL) {
+ err = coreFoundationUnknownErr;
+ }
+ }
+ if (err == 0) {
+ mutableDict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
+ if (mutableDict == NULL) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ }
+
+ // Now read each incoming descriptor, appending the results
+ // to translatedDescs as we go. By keeping our working results
+ // in translatedDescs, we make sure that we can clean up if
+ // we fail.
+
+ if (err == 0) {
+ descCount = CFArrayGetCount(incomingDescs);
+
+ // We don't actually depend on the descriptor values in the
+ // response (that is, the elements of incomingDescs), because
+ // they only make sense it the context of the sending process.
+ // All we really care about is the number of elements, which
+ // tells us how many times to go through this loop. However,
+ // just to be paranoid, in the debug build I check that the
+ // incoming array is well formed.
+
+ #if !defined(NDEBUG)
+ for (descIndex = 0; descIndex < descCount; descIndex++) {
+ int thisDesc;
+ CFNumberRef thisDescNum;
+
+ thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(incomingDescs, descIndex);
+ assert(thisDescNum != NULL);
+ assert(CFGetTypeID(thisDescNum) == CFNumberGetTypeID());
+ assert(CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc));
+ assert(thisDesc >= 0);
+ }
+ #endif
+
+ // Here's the real work. For descCount times, read a descriptor
+ // from fd, wrap it in a CFNumber, and append it to translatedDescs.
+ // Note that we have to be very careful not to leak a descriptor
+ // if we get an error here.
+
+ for (descIndex = 0; descIndex < descCount; descIndex++) {
+ int thisDesc;
+ CFNumberRef thisDescNum;
+
+ thisDesc = -1;
+ thisDescNum = NULL;
+
+ err = BASReadDescriptor(fd, &thisDesc);
+ if (err == 0) {
+ thisDescNum = CFNumberCreate(NULL, kCFNumberIntType, &thisDesc);
+ if (thisDescNum == NULL) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ }
+ if (err == 0) {
+ CFArrayAppendValue(translatedDescs, thisDescNum);
+ // The descriptor is now stashed in translatedDescs,
+ // so this iteration of the loop is no longer responsible
+ // for closing it.
+ thisDesc = -1;
+ }
+
+ if (thisDescNum != NULL) {
+ CFRelease(thisDescNum);
+ }
+ if (thisDesc != -1) {
+ junk = close(thisDesc);
+ assert(junk == 0);
+ }
+
+ if (err != 0) {
+ break;
+ }
+ }
+ }
+
+ // Clean up and establish output parameters.
+
+ if (err == 0) {
+ CFDictionarySetValue(mutableDict, CFSTR(kBASDescriptorArrayKey), translatedDescs);
+ *dictPtr = mutableDict;
+ } else {
+ BASCloseDescriptorArray(translatedDescs);
+ if (mutableDict != NULL) {
+ CFRelease(mutableDict);
+ }
+ }
+ if (translatedDescs != NULL) {
+ CFRelease(translatedDescs);
+ }
+ }
+ }
+
+ if (dict != NULL) {
+ CFRelease(dict);
+ }
+
+ assert( (err == 0) == (*dictPtr != NULL) );
+
+ return err;
+}
+
+static int BASWriteDictionaryAndDescriptors(CFDictionaryRef dict, int fd)
+ // Writes a dictionary and its associated descriptors to fd.
+{
+ int err;
+ CFArrayRef descArray;
+ CFIndex descCount;
+ CFIndex descIndex;
+
+ // Pre-conditions
+
+ assert(dict != NULL);
+ assert(fd >= 0);
+
+ // Write the dictionary.
+
+ err = BASWriteDictionary(dict, fd);
+
+ // Process any descriptors. The descriptors are indicated by
+ // a special key in the dictionary. If that key is present,
+ // it's a CFArray of CFNumbers that present the descriptors to be
+ // passed.
+
+ if (err == 0) {
+ descArray = (CFArrayRef) CFDictionaryGetValue(dict, CFSTR(kBASDescriptorArrayKey));
+
+ // We only do the following if the special key is present.
+
+ if (descArray != NULL) {
+
+ // If it's not an array, that's bad.
+
+ if ( CFGetTypeID(descArray) != CFArrayGetTypeID() ) {
+ err = EINVAL;
+ }
+
+ // Loop over the array, getting each descriptor and writing it.
+
+ if (err == 0) {
+ descCount = CFArrayGetCount(descArray);
+
+ for (descIndex = 0; descIndex < descCount; descIndex++) {
+ CFNumberRef thisDescNum;
+ int thisDesc;
+
+ thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(descArray, descIndex);
+ if ( (thisDescNum == NULL)
+ || (CFGetTypeID(thisDescNum) != CFNumberGetTypeID())
+ || ! CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc) ) {
+ err = EINVAL;
+ }
+ if (err == 0) {
+ err = BASWriteDescriptor(fd, thisDesc);
+ }
+
+ if (err != 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return err;
+}
+
+static OSStatus FindCommand(
+ CFDictionaryRef request,
+ const BASCommandSpec commands[],
+ size_t * commandIndexPtr
+)
+ // FindCommand is a simple utility routine for checking that the
+ // command name within a request is valid (that is, matches one of the command
+ // names in the BASCommandSpec array).
+ //
+ // On success, *commandIndexPtr will be the index of the requested command
+ // in the commands array. On error, the value in *commandIndexPtr is undefined.
+{
+ OSStatus retval = noErr;
+ CFStringRef commandStr;
+ char * command;
+ UInt32 commandSize = 0;
+ size_t index = 0;
+
+ // Pre-conditions
+
+ assert(request != NULL);
+ assert(commands != NULL);
+ assert(commands[0].commandName != NULL); // there must be at least one command
+ assert(commandIndexPtr != NULL);
+
+ command = NULL;
+
+ // Get the command as a C string. To prevent untrusted command string from
+ // trying to run us out of memory, we limit its length to 1024 UTF-16 values.
+
+ commandStr = CFDictionaryGetValue(request, CFSTR(kBASCommandKey));
+ if ( (commandStr == NULL) || (CFGetTypeID(commandStr) != CFStringGetTypeID()) ) {
+ retval = paramErr;
+ }
+ commandSize = CFStringGetLength(commandStr);
+ if ( (retval == noErr) && (commandSize > 1024) ) {
+ retval = paramErr;
+ }
+ if (retval == noErr) {
+ size_t bufSize;
+
+ bufSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(commandStr), kCFStringEncodingUTF8) + 1;
+ command = malloc(bufSize);
+
+ if (command == NULL) {
+ retval = memFullErr;
+ } else if ( ! CFStringGetCString(commandStr, command, bufSize, kCFStringEncodingUTF8) ) {
+ retval = coreFoundationUnknownErr;
+ }
+ }
+
+ // Search the commands array for that command.
+
+ if (retval == noErr) {
+ do {
+ if ( strcmp(commands[index].commandName, command) == 0 ) {
+ *commandIndexPtr = index;
+ break;
+ }
+ index += 1;
+ if (commands[index].commandName == NULL) {
+ retval = BASErrnoToOSStatus(ENOENT);
+ break;
+ }
+ } while (true);
+ }
+
+ free(command);
+
+ return retval;
+}
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Tool Code
+
+/*
+ Watchdog Timer
+ --------------
+ BetterAuthorizationSampleLib's privileged helper tool server is single threaded. Thus,
+ it's possible for a broken or malicious client to stop progress within the helper
+ tool simply by sending the tool half a request. The single thread of execution
+ within the tool will wait forever for the rest of the request and, while it's
+ waiting, it won't be able to service other requests. Clearly this is not good.
+
+ I contemplated a number of solutions to this problem, but eventually settled
+ on a very simple solution. When it starts processing a request, the tool
+ starts a watchdog timer. If the timer expires, the tool dies. The single
+ request that the tool is blocked on will fail (because our end of the per-connection
+ socket for that request closed when we died) and subsequent requests will
+ relaunch the tool on demand, courtesy of launchd.
+
+ I use SIGALRM to implement this functionality. As stated in our header, the
+ BetterAuthorizationSampleLib code claims this signal and our clients are required not
+ to use it. Also, the default disposition for SIGALRM is to quit the process,
+ which is exactly what I want.
+*/
+
+static void EnableWatchdog(void)
+ // Start the watchdog timer. If you don't call DisableWatchdog before the
+ // timer expires, the process will die with a SIGALRM.
+{
+ (void) alarm(kWatchdogTimeoutInSeconds);
+}
+
+static void DisableWatchdog(void)
+ // Disable the watchdog timer.
+{
+ (void) alarm(0);
+}
+
+#if ! defined(NDEBUG)
+
+ static bool CommandArraySizeMatchesCommandProcArraySize(
+ const BASCommandSpec commands[],
+ const BASCommandProc commandProcs[]
+ )
+ {
+ size_t commandCount;
+ size_t procCount;
+
+ commandCount = 0;
+ while ( commands[commandCount].commandName != NULL ) {
+ commandCount += 1;
+ }
+
+ procCount = 0;
+ while ( commandProcs[procCount] != NULL ) {
+ procCount += 1;
+ }
+
+ return (commandCount == procCount);
+ }
+
+#endif
+
+/*
+ On-The-'Wire' Protocol
+ ----------------------
+ The on-the-'wire' protocol for a BetterAuthorizationSampleLib connection (from the
+ perspective of the client) is:
+
+ connect
+
+ send AuthorizationExternalForm (32 byte blob)
+ send request dictionary length (4 bytes, uint32_t, big endian)
+ send request dictionary (N bytes, flattened CFPropertyList)
+
+ read response dictionary length (4 bytes, uint32_t, big endian)
+ read response dictionary (N bytes, flattened CFPropertyList)
+ for each descriptor in dictionary
+ read 1 byte ('D') with attached descriptor
+ write 1 byte ('A')
+
+ close
+*/
+
+static int HandleConnection(
+ aslclient asl,
+ aslmsg aslMsg,
+ const BASCommandSpec commands[],
+ const BASCommandProc commandProcs[],
+ int fd
+)
+ // This routine handles a single connection from a client. This connection, in
+ // turn, represents a single command (request/response pair). commands is the
+ // list of valid commands. commandProc is a callback to call to actually
+ // execute a command. Finally, fd is the file descriptor from which the request
+ // should be read, and to which the response should be sent.
+{
+ int retval;
+ OSStatus junk;
+ int junkInt;
+ AuthorizationExternalForm extAuth;
+ AuthorizationRef auth = NULL;
+ CFDictionaryRef request = NULL;
+ size_t commandIndex;
+ CFMutableDictionaryRef response = NULL;
+ OSStatus commandProcStatus;
+
+ // Pre-conditions
+
+ // asl may be NULL
+ // aslMsg may be NULL
+ assert(commands != NULL);
+ assert(commands[0].commandName != NULL); // there must be at least one command
+ assert(commandProcs != NULL);
+ assert( CommandArraySizeMatchesCommandProcArraySize(commands, commandProcs) );
+ assert(fd >= 0);
+
+ // Read in the external authorization reference.
+ retval = BASRead(fd, &extAuth, sizeof(extAuth), NULL);
+
+ // Internalize external authorization reference.
+ if (retval == 0) {
+ retval = BASOSStatusToErrno( AuthorizationCreateFromExternalForm(&extAuth, &auth) );
+ }
+
+ // Read in CFDictionaryRef request (the command and its arguments).
+ if (retval == 0) {
+ retval = BASReadDictionary(fd, &request);
+ }
+
+ // Create a mutable response dictionary before calling the client.
+ if (retval == 0) {
+ response = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ if (response == NULL) {
+ retval = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ }
+
+ // Errors that occur within this block are considered command errors, that is, they're
+ // reported to the client in the kBASErrorKey value of the response dictionary
+ // (that is, BASExecuteRequestInHelperTool returns noErr and valid response dictionary with
+ // an error value in the kBASErrorKey entry of the dictionary). In contrast, other errors
+ // are considered IPC errors and generally result in a the client getting an error status
+ // back from BASExecuteRequestInHelperTool.
+ //
+ // Notably a request with an unrecognised command string will return an error code
+ // in the response, as opposed to an IPC error. This means that a client can check
+ // whether a tool supports a particular command without triggering an IPC teardown.
+
+ if (retval == 0) {
+ // Get the command name from the request dictionary and check to see whether or
+ // not the command is valid by comparing with the BASCommandSpec array. Also,
+ // if the command is valid, return the associated right (if any).
+
+ commandProcStatus = FindCommand(request, commands, &commandIndex);
+
+ // Acquire the associated right for the command. If rightName is NULL, the
+ // commandProc is required to do its own authorization.
+
+ if ( (commandProcStatus == noErr) && (commands[commandIndex].rightName != NULL) ) {
+ AuthorizationItem item = { commands[commandIndex].rightName, 0, NULL, 0 };
+ AuthorizationRights rights = { 1, &item };
+
+ commandProcStatus = AuthorizationCopyRights(
+ auth,
+ &rights,
+ kAuthorizationEmptyEnvironment,
+ kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed,
+ NULL
+ );
+ }
+
+ // Call callback to execute command based on the request.
+
+ if (commandProcStatus == noErr) {
+ commandProcStatus = commandProcs[commandIndex](auth, commands[commandIndex].userData, request, response, asl, aslMsg);
+
+ if (commandProcStatus == noErr) {
+ junkInt = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Command callback succeeded");
+ assert(junkInt == 0);
+ } else {
+ junkInt = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Command callback failed: %ld", (long) commandProcStatus);
+ assert(junkInt == 0);
+ }
+ }
+
+ // If the command didn't insert its own error value, we use its function
+ // result as the error value.
+
+ if ( ! CFDictionaryContainsKey(response, CFSTR(kBASErrorKey)) ) {
+ CFNumberRef numRef;
+
+ numRef = CFNumberCreate(NULL, kCFNumberSInt32Type, &commandProcStatus);
+ if (numRef == NULL) {
+ retval = BASOSStatusToErrno( coreFoundationUnknownErr );
+ } else {
+ CFDictionaryAddValue(response, CFSTR(kBASErrorKey), numRef);
+ CFRelease(numRef);
+ }
+ }
+ }
+
+ // Write response back to the client.
+ if (retval == 0) {
+ retval = BASWriteDictionaryAndDescriptors(response, fd);
+ }
+
+ // Clean up.
+
+ if (response != NULL) {
+ // If there are any descriptors in response, we've now passed them off to the client,
+ // so we can (and must) close our references to them.
+ BASCloseDescriptorArray( CFDictionaryGetValue(response, CFSTR(kBASDescriptorArrayKey)) );
+ CFRelease(response);
+ }
+ if (request != NULL) {
+ CFRelease(request);
+ }
+ if (auth != NULL) {
+ junk = AuthorizationFree(auth, kAuthorizationFlagDefaults);
+ assert(junk == noErr);
+ }
+
+ return retval;
+}
+
+#if !defined(NDEBUG)
+
+ static void WaitForDebugger(aslclient asl, aslmsg aslMsg)
+ // You can force a debug version of the tool to stop and wait on
+ // launch using the following Terminal command:
+ //
+ // $ sudo launchctl stop com.example.BetterAuthorizationSample
+ // $ sudo launchctl setenv BASWaitForDebugger 1
+ {
+ int err;
+ const char *value;
+
+ // asl may be NULL
+ // aslMsg may be NULL
+
+ value = getenv("BASWaitForDebugger");
+ if ( ((value != NULL) && (atoi(value) != 0)) ) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Waiting for debugger");
+ assert(err == 0);
+ (void) pause();
+ }
+ }
+
+#endif
+
+static int CheckInWithLaunchd(aslclient asl, aslmsg aslMsg, const char **errStrPtr)
+ // Checks in with launchd and gets back our listening socket.
+ // Returns the socket as the function result (or -1 on error).
+ // Also, on error, set *errStrPtr to a error string suitable
+ // for logging with ASL. If the message contains a %m, which
+ // causes ASL to log errno, errno will be set appropriately.
+{
+ int err;
+ launch_data_t checkinRequest = NULL;
+ launch_data_t checkinResponse = NULL;
+ launch_data_t socketsDict;
+ launch_data_t fdArray;
+ launch_data_t fdData;
+ int fd = -1;
+
+ // Pre-conditions
+
+ // asl may be NULL
+ // aslMsg may be NULL
+ assert( errStrPtr != NULL);
+ assert(*errStrPtr == NULL);
+
+ // Check in with launchd. Create a checkin request, then run it, then
+ // check if we got an error.
+
+ checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN);
+ if (checkinRequest == NULL) {
+ *errStrPtr = "Could not create checkin request: %m";
+ goto done;
+ }
+ checkinResponse = launch_msg(checkinRequest);
+ if (checkinResponse == NULL) {
+ *errStrPtr = "Error checking in: %m";
+ goto done;
+ }
+ if (launch_data_get_type(checkinResponse) == LAUNCH_DATA_ERRNO) {
+ errno = launch_data_get_errno(checkinResponse); // set errno so %m picks it up
+ *errStrPtr = "Checkin failed: %m";
+ goto done;
+ }
+
+ // Retrieve the dictionary of sockets entries from the job. This corresponds to the
+ // value of the "Sockets" key in our plist file.
+
+ socketsDict = launch_data_dict_lookup(checkinResponse, LAUNCH_JOBKEY_SOCKETS);
+ if (socketsDict == NULL) {
+ *errStrPtr = "Could not get socket dictionary from checkin response: %m";
+ goto done;
+ }
+ if (launch_data_get_type(socketsDict) != LAUNCH_DATA_DICTIONARY) {
+ *errStrPtr = "Could not get socket dictionary from checkin response: Type mismatch";
+ goto done;
+ }
+ if (launch_data_dict_get_count(socketsDict) > 1) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_WARNING, "Some sockets in dictionary will be ignored");
+ assert(err == 0);
+ }
+
+ // Get the dictionary value from the key "MasterSocket", as defined in the launchd
+ // property list file.
+
+ fdArray = launch_data_dict_lookup(socketsDict, kLaunchDSocketDictKey);
+ if (fdArray == NULL) {
+ *errStrPtr = "Could not get file descriptor array: %m";
+ goto done;
+ }
+ if (launch_data_get_type(fdArray) != LAUNCH_DATA_ARRAY) {
+ *errStrPtr = "Could not get file descriptor array: Type mismatch";
+ goto done;
+ }
+ if (launch_data_array_get_count(fdArray) > 1) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_WARNING, "Some sockets in array will be ignored");
+ assert(err == 0);
+ }
+
+ // Get the socket file descriptor from the array.
+
+ fdData = launch_data_array_get_index(fdArray, 0);
+ if (fdData == NULL) {
+ *errStrPtr = "Could not get file descriptor array entry: %m";
+ goto done;
+ }
+ if (launch_data_get_type(fdData) != LAUNCH_DATA_FD) {
+ *errStrPtr = "Could not get file descriptor array entry: Type mismatch";
+ goto done;
+ }
+ fd = launch_data_get_fd(fdData);
+ assert(fd >= 0);
+
+ // The following was used to debug a problem with launchd <rdar://problem/5410487>.
+ // I'm going to leave it in, disabled, until that problem is resolved.
+
+ if (false) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Listening descriptor is %d", fd);
+ assert(err == 0);
+ }
+
+done:
+ if (checkinResponse != NULL) {
+ launch_data_free(checkinResponse);
+ }
+ if (checkinRequest != NULL) {
+ launch_data_free(checkinRequest);
+ }
+
+ return fd;
+}
+
+static int SetNonBlocking(int fd, Boolean nonBlocking)
+ // Sets the non-blocking state of fd.
+{
+ int err;
+ int flags;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+
+ // Get the flags.
+
+ err = 0;
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ err = errno;
+ }
+
+ // If the current state of O_NONBLOCK doesn't match the required
+ // state, toggle that flag and set it back.
+
+ if ( (err == 0) && (((flags & O_NONBLOCK) != 0) != nonBlocking) ) {
+ flags ^= O_NONBLOCK;
+ err = fcntl(fd, F_SETFL, flags);
+ if (err < 0) {
+ err = errno;
+ }
+ }
+
+ return err;
+}
+
+extern int BASHelperToolMain(
+ const BASCommandSpec commands[],
+ const BASCommandProc commandProcs[]
+)
+ // See comment in header.
+{
+ const char * errStr = NULL;
+ int err;
+ aslclient asl = NULL;
+ aslmsg aslMsg = NULL;
+ sig_t pipeSet;
+ int listener;
+ int kq;
+ struct kevent initEvent;
+
+ // Pre-conditions
+
+ assert(commands != NULL);
+ assert(commands[0].commandName != NULL); // there must be at least one command
+ assert(commandProcs != NULL);
+ assert( CommandArraySizeMatchesCommandProcArraySize(commands, commandProcs) );
+
+ // Create a new ASL client object, and a template message for any messages that
+ // we log. We don't care if these fail because ASL will do the right thing
+ // if you pass it NULL (that is, nothing).
+
+ asl = asl_open(NULL, "HelperTools", ASL_OPT_STDERR);
+ assert(asl != NULL);
+
+ aslMsg = asl_new(ASL_TYPE_MSG);
+ assert(aslMsg != NULL);
+
+ err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Starting up");
+ assert(err == 0);
+
+ #if !defined(NDEBUG)
+ WaitForDebugger(asl, aslMsg);
+ #endif
+
+ // Set up the signal handlers we are interested in.
+ //
+ // o SIGTERM -- launchd sends us this when it wants us to quit. We don't
+ // actually need to set up a handler because the default behaviour (process
+ // termination) is fine.
+ //
+ // o SIGALRM -- No need to set it up because the default behaviour (process
+ // termination) is fine. See the "Watchdog Timer" comment (above) for details.
+ //
+ // o SIGPIPE -- We don't want to quit when write to a dead socket, so we
+ // ignore this signal.
+
+ pipeSet = signal(SIGPIPE, SIG_IGN);
+ if (pipeSet == SIG_ERR) {
+ errStr = "Could not ignore SIGPIPE: %m";
+ goto done;
+ }
+
+ // Check in with launchd and get our listening socket.
+
+ listener = CheckInWithLaunchd(asl, aslMsg, &errStr);
+ if (listener < 0) {
+ assert(errStr != NULL);
+ goto done;
+ }
+
+ // Create a kqueue and wrap the listening socket in it.
+
+ kq = kqueue();
+ if (kq < 0) {
+ errStr = "Could not create kqueue: %m";
+ goto done;
+ }
+
+ EV_SET(&initEvent, listener, EVFILT_READ, EV_ADD, 0, 0, NULL);
+ err = kevent(kq, &initEvent, 1, NULL, 0, NULL);
+ if (err < 0) {
+ errStr = "Could not add listening socket to kqueue: %m";
+ goto done;
+ }
+
+ // Force the listening socket to non-blocking mode. Without this, our timeout
+ // handling won't work properly. Specifically, we could get stuck in an accept
+ // if a connection request appears and then disappears. Eventually the watchdog
+ // would clean up, but that's not a great solution.
+
+ err = SetNonBlocking(listener, true);
+ if (err != 0) {
+ errno = err; // for %m
+ errStr = "Could not check/set socket flags: %m";
+ goto done;
+ }
+
+ // Loop servicing connection requests one at a time.
+
+ while (true) {
+ int eventCount;
+ struct kevent thisEvent;
+ int thisConnection;
+ int thisConnectionError;
+ struct sockaddr_storage clientAddr; // we don't need this info, but accept won't let us ignore it
+ socklen_t clientAddrLen = sizeof(clientAddr);
+ static const struct timespec kIdleTimeout = { kIdleTimeoutInSeconds , 0 };
+
+ // Wait on the kqueue for a connection request.
+
+ eventCount = kevent(kq, NULL, 0, &thisEvent, 1, &kIdleTimeout);
+ if (eventCount == 0) {
+ // We've hit our idle timer. Just break out of the connection loop.
+ break;
+ } else if (eventCount == -1) {
+ // We got some sort of error from kevent; quit with an error.
+ errStr = "Unexpected error while listening for connections: %m";
+ goto done;
+ }
+
+ // From this point on, we're running on the watchdog timer. If we get
+ // stuck anywhere, the watchdog will fire eventually and we'll quit.
+
+ EnableWatchdog();
+
+ // The accept should never get stuck because this is a non-blocking
+ // socket.
+
+ thisConnection = accept(thisEvent.ident, (struct sockaddr *) &clientAddr, &clientAddrLen);
+ if (thisConnection == -1) {
+ if (errno == EWOULDBLOCK) {
+ // If the incoming connection just disappeared (perhaps the client
+ // died before we accepted the connection), don't log that as an error
+ // and don't quit.
+ err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Connection disappeared before we could accept it: %m");
+ assert(err == 0);
+ } else {
+ // Other errors mean that we're in a very weird state; we respond by
+ // failing out with an error.
+ errStr = "Unexpected error while accepting a connection: %m";
+ goto done;
+ }
+ }
+
+ // Because the accept can fail in a non-fatal fashion, thisConnection can be
+ // -1 here. In that case, we just skip the next step.
+
+ if (thisConnection != -1) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Request started");
+ assert(err == 0);
+
+ // thisConnection inherits its non-blocking setting from listener, but
+ // we want it to be blocking from here on in, so we switch the status.
+ // We're now relying on the watchdog to kill us if we get stuck.
+
+ thisConnectionError = BASErrnoToOSStatus( SetNonBlocking(thisConnection, false) );
+
+ // Entering heavy liftiing. We have a separate routine to actually
+ // read the request from the connection, call the client, and send
+ // the reply.
+
+ if (thisConnectionError == noErr) {
+ thisConnectionError = HandleConnection(asl, aslMsg, commands, commandProcs, thisConnection);
+ }
+
+ err = close(thisConnection);
+ assert(err == 0);
+
+ if (thisConnectionError == 0) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Request finished");
+ } else {
+ errno = thisConnectionError; // so it can be picked up by %m
+ err = asl_log(asl, aslMsg, ASL_LEVEL_ERR, "Request failed: %m");
+ }
+ assert(err == 0);
+ }
+
+ DisableWatchdog();
+ }
+
+done:
+ // At this point, errStr is either NULL, in which case we're quitting because
+ // of our idle timer, or non-NULL, in which case we're dying with an error.
+
+ // We expect the caller to immediately quit once we return. Thus, we
+ // don't bother cleaning up any resources we have allocated here, including
+ // asl, aslMsg, and kq.
+
+ if (errStr != NULL) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_ERR, errStr);
+ assert(err == 0);
+ }
+ err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Shutting down");
+ assert(err == 0);
+ return (errStr == NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** App Code
+
+extern void BASSetDefaultRules(
+ AuthorizationRef auth,
+ const BASCommandSpec commands[],
+ CFStringRef bundleID,
+ CFStringRef descriptionStringTableName
+)
+ // See comment in header.
+{
+ OSStatus err;
+ CFBundleRef bundle;
+ size_t commandIndex;
+
+ // Pre-conditions
+
+ assert(auth != NULL);
+ assert(commands != NULL);
+ assert(commands[0].commandName != NULL); // there must be at least one command
+ assert(bundleID != NULL);
+ // descriptionStringTableName may be NULL
+
+ bundle = CFBundleGetBundleWithIdentifier(bundleID);
+ assert(bundle != NULL);
+
+ // For each command, set up the default authorization right specification, as
+ // indicated by the command specification.
+
+ commandIndex = 0;
+ while (commands[commandIndex].commandName != NULL) {
+ // Some no-obvious assertions:
+
+ // If you have a right name, you must supply a default rule.
+ // If you have no right name, you can't supply a default rule.
+
+ assert( (commands[commandIndex].rightName == NULL) == (commands[commandIndex].rightDefaultRule == NULL) );
+
+ // If you have no right name, you can't supply a right description.
+ // OTOH, if you have a right name, you may supply a NULL right description
+ // (in which case you get no custom prompt).
+
+ assert( (commands[commandIndex].rightName != NULL) || (commands[commandIndex].rightDescriptionKey == NULL) );
+
+ // If there's a right name but no current right specification, set up the
+ // right specification.
+
+ if (commands[commandIndex].rightName != NULL) {
+ err = AuthorizationRightGet(commands[commandIndex].rightName, (CFDictionaryRef*) NULL);
+ if (err == errAuthorizationDenied) {
+ CFStringRef thisDescription;
+ CFStringRef thisRule;
+
+ // The right is not already defined. Set up a definition based on
+ // the fields in the command specification.
+
+ thisRule = CFStringCreateWithCString(
+ kCFAllocatorDefault,
+ commands[commandIndex].rightDefaultRule,
+ kCFStringEncodingUTF8
+ );
+ assert(thisRule != NULL);
+
+ thisDescription = NULL;
+ if (commands[commandIndex].rightDescriptionKey != NULL) {
+ thisDescription = CFStringCreateWithCString (
+ kCFAllocatorDefault,
+ commands[commandIndex].rightDescriptionKey,
+ kCFStringEncodingUTF8
+ );
+ assert(thisDescription != NULL);
+ }
+
+ err = AuthorizationRightSet(
+ auth, // authRef
+ commands[commandIndex].rightName, // rightName
+ thisRule, // rightDefinition
+ thisDescription, // descriptionKey
+ bundle, // bundle
+ descriptionStringTableName // localeTableName
+ ); // NULL indicates "Localizable.strings"
+ assert(err == noErr);
+
+ if (thisDescription != NULL) {
+ CFRelease(thisDescription);
+ }
+ if (thisRule != NULL) {
+ CFRelease(thisRule);
+ }
+ } else {
+ // A right already exists (err == noErr) or any other error occurs, we
+ // assume that it has been set up in advance by the system administrator or
+ // this is the second time we've run. Either way, there's nothing more for
+ // us to do.
+ }
+ }
+ commandIndex += 1;
+ }
+}
+
+extern OSStatus BASExecuteRequestInHelperTool(
+ AuthorizationRef auth,
+ const BASCommandSpec commands[],
+ CFStringRef bundleID,
+ CFDictionaryRef request,
+ CFDictionaryRef * response
+)
+ // See comment in header.
+{
+ OSStatus retval = noErr;
+ int junk;
+ size_t commandIndex;
+ char bundleIDC[PATH_MAX];
+ int fd = -1;
+ struct sockaddr_un addr;
+ AuthorizationExternalForm extAuth;
+
+ // Pre-conditions
+
+ assert(auth != NULL);
+ assert(commands != NULL);
+ assert(commands[0].commandName != NULL); // there must be at least one command
+ assert(bundleID != NULL);
+ assert(request != NULL);
+ assert( response != NULL);
+ assert(*response == NULL);
+
+ // For debugging.
+
+ assert(CFDictionaryContainsKey(request, CFSTR(kBASCommandKey)));
+ assert(CFGetTypeID(CFDictionaryGetValue(request, CFSTR(kBASCommandKey))) == CFStringGetTypeID());
+
+ // Look up the command and preauthorize. This has the nice side effect that
+ // the authentication dialog comes up, in the typical case, here, rather than
+ // in the helper tool. This is good because the helper tool is global /and/
+ // single threaded, so if it's waiting for an authentication dialog for user A
+ // it can't handle requests from user B.
+
+ retval = FindCommand(request, commands, &commandIndex);
+
+ #if !defined(BAS_PREAUTHORIZE)
+ #define BAS_PREAUTHORIZE 1
+ #endif
+ #if BAS_PREAUTHORIZE
+ if ( (retval == noErr) && (commands[commandIndex].rightName != NULL) ) {
+ AuthorizationItem item = { commands[commandIndex].rightName, 0, NULL, 0 };
+ AuthorizationRights rights = { 1, &item };
+
+ retval = AuthorizationCopyRights(auth, &rights, kAuthorizationEmptyEnvironment, kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize, NULL);
+ }
+ #endif
+
+ // Create the socket and tell it to not generate SIGPIPE.
+
+ if (retval == noErr) {
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ retval = BASErrnoToOSStatus(errno);
+ }
+ }
+ if (retval == noErr) {
+ static const int kOne = 1;
+
+ if ( setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &kOne, sizeof(kOne)) < 0 ) {
+ retval = BASErrnoToOSStatus(errno);
+ }
+ }
+
+ // Form the socket address, including a path based on the bundle ID.
+
+ if (retval == noErr) {
+ if ( ! CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC)) ) {
+ retval = coreFoundationUnknownErr;
+ }
+ }
+ if (retval == noErr) {
+ int pathLen;
+
+ memset(&addr, 0, sizeof(addr));
+
+ addr.sun_family = AF_UNIX;
+ pathLen = snprintf(addr.sun_path, sizeof(addr.sun_path), kBASSocketPathFormat, bundleIDC);
+ if (pathLen >= sizeof(addr.sun_path)) {
+ retval = paramErr; // length of bundle pushed us over the UNIX domain socket path length limit
+ } else {
+ addr.sun_len = SUN_LEN(&addr);
+ }
+ }
+
+ // Attempt to connect.
+
+ if (retval == noErr) {
+ if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
+ retval = BASErrnoToOSStatus(errno);
+ }
+ }
+
+ // Send the flattened AuthorizationRef to the tool.
+
+ if (retval == noErr) {
+ retval = AuthorizationMakeExternalForm(auth, &extAuth);
+ }
+ if (retval == noErr) {
+ retval = BASErrnoToOSStatus( BASWrite(fd, &extAuth, sizeof(extAuth), NULL) );
+ }
+
+ // Write the request.
+
+ if (retval == noErr) {
+ retval = BASErrnoToOSStatus( BASWriteDictionary(request, fd) );
+ }
+
+ // Read response, including any descriptors.
+
+ if (retval == noErr) {
+ retval = BASErrnoToOSStatus( BASReadDictioanaryTranslatingDescriptors(fd, response) );
+ }
+
+ // Clean up.
+
+ if (fd != -1) {
+ junk = close(fd);
+ assert(junk == 0);
+ }
+ NormaliseOSStatusErrorCode(&retval);
+
+ assert( (retval == noErr) == (*response != NULL) );
+
+ return retval;
+}
+
+extern OSStatus BASGetErrorFromResponse(CFDictionaryRef response)
+ // See comment in header.
+{
+ OSStatus err;
+ CFNumberRef num;
+
+ assert(response != NULL);
+
+ num = (CFNumberRef) CFDictionaryGetValue(response, CFSTR(kBASErrorKey));
+ err = noErr;
+ if ( (num == NULL) || (CFGetTypeID(num) != CFNumberGetTypeID()) ) {
+ err = coreFoundationUnknownErr;
+ }
+ if (err == noErr) {
+ if ( ! CFNumberGetValue(num, kCFNumberSInt32Type, &err) ) {
+ err = coreFoundationUnknownErr;
+ }
+ }
+
+ NormaliseOSStatusErrorCode(&err);
+ return err;
+}
+
+extern BASFailCode BASDiagnoseFailure(
+ AuthorizationRef auth,
+ CFStringRef bundleID
+)
+ // See comment in header.
+{
+ BASFailCode retval = kBASFailUnknown;
+ int err;
+ int pathLen;
+ char bundleIDC [ PATH_MAX ];
+ char toolPath [ PATH_MAX ];
+ char plistPath [ PATH_MAX ];
+
+ struct stat fileStatus;
+ int toolErr;
+ int plistErr;
+ int fd;
+ struct sockaddr_un addr;
+
+ // Pre-conditions
+
+ assert(auth != NULL);
+ assert(bundleID != NULL);
+
+ // Construct paths to the tool and plist.
+
+ if ( CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC)) ) {
+
+ pathLen = snprintf(toolPath, sizeof(toolPath), kBASToolPathFormat, bundleIDC);
+ assert(pathLen < PATH_MAX); // snprintf truncated the string; won't crash us, but we want to know
+
+ pathLen = snprintf(plistPath, sizeof(plistPath), kBASPlistPathFormat, bundleIDC);
+ assert(pathLen < PATH_MAX); // snprintf truncated the string; won't crash us, but we want to know
+
+ // Check if files exist at those paths.
+
+ toolErr = stat(toolPath, &fileStatus);
+ plistErr = stat(plistPath, &fileStatus);
+
+ if ( (toolErr == 0) && (plistErr == 0) ) {
+ // If both items are present, try to connect and see what we get.
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd != -1) {
+ memset(&addr, 0, sizeof(addr));
+
+ addr.sun_family = AF_UNIX;
+ (void) snprintf(addr.sun_path, sizeof(addr.sun_path), kBASSocketPathFormat, bundleIDC);
+ addr.sun_len = SUN_LEN(&addr);
+
+ // Attempt to connect to the socket. If we get ECONNREFUSED, it means no one is
+ // listening.
+
+ if ( (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) && (errno == ECONNREFUSED) ) {
+ retval = kBASFailDisabled;
+ }
+ err = close(fd);
+ assert(err == 0);
+ }
+ } else {
+ if ( (toolErr == 0) || (plistErr == 0) ) {
+ retval = kBASFailPartiallyInstalled;
+ } else {
+ retval = kBASFailNotInstalled;
+ }
+ }
+ }
+
+ return retval;
+}
+
+// kPlistTemplate is a template for our launchd.plist file.
+
+static const char * kPlistTemplate =
+ // The standard plist header.
+
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ "<dict>\n"
+
+ // We install the job disabled, then enable it as the last step.
+
+ " <key>Disabled</key>\n"
+ " <true/>\n"
+
+ // Use the bundle identifier as the job label.
+
+ " <key>Label</key>\n"
+ " <string>%s</string>\n"
+
+ // Use launch on demaind.
+
+ " <key>OnDemand</key>\n"
+ " <true/>\n"
+
+ // There are no program arguments, other that the path to the helper tool itself.
+ //
+ // IMPORTANT
+ // kBASToolPathFormat embeds a %s
+
+ " <key>ProgramArguments</key>\n"
+ " <array>\n"
+ " <string>" kBASToolPathFormat "</string>\n"
+ " </array>\n"
+
+ // The tool is required to check in with launchd.
+
+ " <key>ServiceIPC</key>\n"
+ " <true/>\n"
+
+ // This specifies the UNIX domain socket used to launch the tool, including
+ // the permissions on the socket (438 is 0666).
+ //
+ // IMPORTANT
+ // kBASSocketPathFormat embeds a %s
+
+ " <key>Sockets</key>\n"
+ " <dict>\n"
+ " <key>" kLaunchDSocketDictKey "</key>\n"
+ " <dict>\n"
+ " <key>SockFamily</key>\n"
+ " <string>Unix</string>\n"
+ " <key>SockPathMode</key>\n"
+ " <integer>438</integer>\n"
+ " <key>SockPathName</key>\n"
+ " <string>" kBASSocketPathFormat "</string>\n"
+ " <key>SockType</key>\n"
+ " <string>Stream</string>\n"
+ " </dict>\n"
+ " </dict>\n"
+ "</dict>\n"
+ "</plist>\n"
+ ;
+
+
+// Installation
+// ------------
+// We install by running our "InstallTool" using AuthorizationExecuteWithPrivileges
+// (AEWP) and passing the relevant parameters to it through AEWP.
+//
+// There is an obvious issue with the way we are handling installation as the user
+// is executing some non-privileged code by way of AEWP. The scenario could exist
+// that the code is malicious (or they have other malicious code running at the
+// same time) and it could swap in any other tool that it would want executed as
+// EUID == 0.
+//
+// We decided on this design primarily because the only other option was to run a
+// shell via AEWP and pipe a script to it. That would have given us the nice
+// properties of not having to have a separate installer on disk and the script
+// could be embedded within the executable making it a little more difficult for
+// casual hacking.
+//
+// However, running a shell as root is /not/ a very good paradigm to follow, thus,
+// weighing the cost-benefits from a security perspective impelled us to just use
+// a separate installer tool. The assumption being that, no matter what, if a user
+// has malicious code running on their system the added security of having an
+// embedded script is negligible and not worth pulling in an entire shell
+// environment as root.
+//
+// The obvious disadvantages stem from the first advantage of the former, namely,
+// it's a little more coding and accounting effort (-:
+//
+//
+// What's This About Zombies?
+// --------------------------
+// AuthorizationExecuteWithPrivileges creates a process that runs with privileges.
+// This process is a child of our process. Thus, we need to reap the process
+// (by calling <x-man-page://2/waitpid>). If we don't do this, we create a 'zombie'
+// process (<x-man-page://1/ps> displays its status as "Z") that persists until
+// our process quits (at which point the zombie gets reparented to launchd, and
+// launchd automatically reaps it). Zombies are generally considered poor form.
+// Thus, we want to avoid creating them.
+//
+// Unfortunately, AEWP doesn't return the process ID of the child process
+// <rdar://problem/3090277>, which makes it challenging for us to reap it. We could
+// reap all children (by passing -1 to waitpid) but that's not cool for library code
+// (we could end up reaping a child process that's completely unrelated to this
+// code, perhaps created by some other part of the host application). Thus, we need
+// to find the child process's PID. And the only way to do that is for the child
+// process to tell us.
+//
+// So, in the child process (the install tool) we echo the process ID and in the
+// parent we look for that in the returned text. *sigh* It's pretty ugly, but
+// that's the best I can come up with. We delimit the process ID with some
+// pretty distinctive text to make it clear that we've got the right thing.
+
+#if !defined(NDEBUG)
+
+ static Boolean gBASLogInteractions = false;
+ // Set gBASLogInteractions to have BASFixFailure log its interactions with
+ // the installation tool to stderr.
+
+ static Boolean gBASLogInteractionsInitialised = false;
+ // This indicates whether we've initialised gBASLogInteractions from the
+ // environment variable.
+
+#endif
+
+static OSStatus RunInstallToolAsRoot(
+ AuthorizationRef auth,
+ const char * installToolPath,
+ const char * command,
+ ...
+)
+ // Run the specified install tool as root. The arguments to the tool are
+ // given as a sequence of (char *)s, terminated be a NULL. The tool is
+ // expected to output special tokens to indicate success or failure.
+{
+ OSStatus retval;
+ size_t argCount;
+ size_t argIndex;
+ va_list ap;
+ char ** args;
+ Boolean success;
+ FILE * channel;
+ int junk;
+ pid_t childPID;
+
+ // Pre-conditions
+
+ assert(auth != NULL);
+ assert(installToolPath != NULL);
+ assert(command != NULL);
+
+ channel = NULL;
+ args = NULL;
+ childPID = -1;
+
+ // Count the number of arguments.
+
+ argCount = 0;
+ va_start(ap, command);
+ while ( va_arg(ap, char *) != NULL ) {
+ argCount += 1;
+ }
+ va_end(ap);
+
+ // Allocate an argument array and populate it, checking each argument along the way.
+
+ retval = noErr;
+ args = calloc(argCount + 3, sizeof(char *)); // +3 for installToolPath, command and trailing NULL
+ if (args == NULL) {
+ retval = memFullErr;
+ }
+ if (retval == noErr) {
+ argIndex = 0;
+
+ args[argIndex] = (char *) installToolPath; // Annoyingly, AEWP (and exec) takes a (char * const *)
+ argIndex += 1; // argument, implying that it might modify the individual
+ args[argIndex] = (char *) command; // strings. That means you can't pass a (const char *) to
+ argIndex += 1; // the routine. However, AEWP never modifies its input
+ // arguments, so we just cast away the const.
+ // *sigh* <rdar://problem/3090294>
+ va_start(ap, command);
+ do {
+ args[argIndex] = va_arg(ap, char *);
+ if (args[argIndex] == NULL) {
+ break;
+ }
+ argIndex += 1;
+ } while (true);
+ va_end(ap);
+ }
+
+ // Go go gadget AEWP!
+
+ if (retval == noErr) {
+ #if !defined(NDEBUG)
+ if ( ! gBASLogInteractionsInitialised ) {
+ const char * value;
+
+ value = getenv("BASLogInteractions");
+ gBASLogInteractions = ( ((value != NULL) && (atoi(value) != 0)) );
+
+ gBASLogInteractionsInitialised = true;
+ }
+
+ if (gBASLogInteractions) {
+ argIndex = 0;
+ while (args[argIndex] != NULL) {
+ fprintf(stderr, "args[%zd] = %s\n", argIndex, args[argIndex]);
+ argIndex += 1;
+ }
+ }
+ #endif
+ retval = AuthorizationExecuteWithPrivileges(auth, args[0], kAuthorizationFlagDefaults, &args[1], &channel);
+ }
+
+ // Process the tool's output. We read every line of output from the tool until
+ // we receive either an EOF or the success or failure tokens.
+ //
+ // AEWP provides us with no way to get to the tool's stderr or exit status,
+ // so we rely on the tool to send us this "oK" to indicate successful completion.
+
+ if (retval == noErr) {
+ char thisLine[1024];
+ long tmpLong;
+ int tmpInt;
+
+ // This loops is a little more complex than you might expect. There are
+ // a number of reasons for this:
+ //
+ // o AEWP does not return us the child PID, so we have to scan the tool's
+ // output look for a line that contains that information (surrounded
+ // by special tokens).
+ //
+ // o Because we can't be guaranteed to get the child PID, we can't be
+ // guaranteed to get the child's exit status. Thus, rather than relying
+ // on the exit status, we have the child explicitly print special tokens
+ // on success and failure.
+ //
+ // o Because we're parsing special tokens anyway, we might as well extract
+ // the real error code from the failure token.
+ //
+ // o A change made to launchctl in Mac OS X 10.4.7 <rdar://problem/4389914>
+ // causes it to fork a copy of itself. The forked copy then delays
+ // for 30 seconds before doing some stuff, eventually printing a message
+ // like "Workaround Bonjour: 0". This causes us two problems.
+ //
+ // 1. The second copy of launchd still has our communications channel
+ // (that is, the other end of "channel") as its stdin/stdout.
+ // Thus, we don't get an EOF on channel until that copy quits.
+ // This causes a 30 second delay in installation.
+ //
+ // 2. The second copy of launchd prints its status line (that is,
+ // "Workaround Bonjour: 0") well after the tool prints the success
+ // token.
+ //
+ // I solved these problems by parsing each line for the success or failure
+ // token and ignoring any output after that.
+ //
+ // To minimise the danger of interpreting one of the tool's commands
+ // output as one of our tokens, I've given them a wacky case (for example,
+ // "oK", not "ok" or "OK" or "Ok").
+
+ do {
+ success = (fgets(thisLine, sizeof(thisLine), channel) != NULL);
+ if ( ! success ) {
+ // We hit the end of the output without seeing a success or failure
+ // token. Note good. errState is an ADSP error code, but it says
+ // exactly what I want to say and it's not likely to crop up any
+ // other way.
+ retval = errState;
+ break;
+ }
+
+ // This echo doesn't work properly if the line coming back from the tool
+ // is longer than the line buffer. However, as the echo is only relevant for
+ // debugging, and the detection of the "oK" isn't affected by this problem,
+ // I'm going to leave it as it is.
+
+ #if !defined(NDEBUG)
+ if (gBASLogInteractions) {
+ fprintf(stderr, ">%s", thisLine);
+ }
+ #endif
+
+ // Look for the success token and terminate with no error in that case.
+
+ if (strcmp(thisLine, kBASInstallToolSuccess "\n") == 0) {
+ assert(retval == noErr);
+ break;
+ }
+
+ // Look for the failure token and extract the error result from that.
+
+ if ( sscanf(thisLine, kBASInstallToolFailure "\n", &tmpInt) == 1 ) {
+ retval = BASErrnoToOSStatus( tmpInt );
+ if (retval == noErr) {
+ assert(false);
+ retval = errState;
+ }
+ break;
+ }
+
+ // If we haven't already found a child process ID, look for a line
+ // that contains it (surrounded by special tokens). For details, see
+ // the discussion of zombies above.
+
+ if ( (childPID == -1) && (sscanf(thisLine, kBASAntiZombiePIDToken1 "%ld" kBASAntiZombiePIDToken2 "\n", &tmpLong) == 1) ) {
+ childPID = (pid_t) tmpLong;
+ }
+ } while (true);
+ }
+
+ // If we successfully managed to determine the PID of our child process, reap
+ // that child. Note that we ignore any errors from this step. If an error
+ // occurs, we end up creating a zombie, which isn't too big a deal. We also
+ // junk the status result from the tool, relying exclusively on the presence
+ // of the "oK" in the output.
+
+ #if !defined(NDEBUG)
+ if (gBASLogInteractions) {
+ fprintf(stderr, "childPID=%ld\n", (long) childPID);
+ }
+ #endif
+ if (childPID != -1) {
+ pid_t waitResult;
+ int junkStatus;
+
+ do {
+ waitResult = waitpid(childPID, &junkStatus, 0);
+ } while ( (waitResult < 0) && (errno == EINTR) );
+ }
+
+ // Clean up.
+
+ if (channel != NULL) {
+ junk = fclose(channel);
+ assert(junk == 0);
+ }
+ free(args);
+
+ NormaliseOSStatusErrorCode(&retval);
+ return retval;
+}
+
+static OSStatus BASInstall(
+ AuthorizationRef auth,
+ const char * bundleID,
+ const char * installToolPath,
+ const char * helperToolPath
+)
+ // Do an install from scratch. Get the specified tool from the bundle
+ // and install it in the "/Library/PrivilegedHelperTools" directory,
+ // along with a plist in "/Library/LaunchDaemons".
+{
+ OSStatus retval;
+ int junk;
+ char * plistText;
+ int fd;
+ char plistPath[PATH_MAX];
+
+ // Pre-conditions
+
+ assert(auth != NULL);
+ assert(bundleID != NULL);
+ assert(installToolPath != NULL);
+ assert(helperToolPath != NULL);
+
+ // Prepare for failure
+
+ plistText = NULL;
+ fd = -1;
+ plistPath[0] = 0;
+
+ // Create the property list from the template, substituting the bundle identifier in
+ // three different places. I realise that this isn't very robust (if you change
+ // the template you have to change this code), but it is /very/ easy.
+
+ retval = asprintf(&plistText, kPlistTemplate, bundleID, bundleID, bundleID);
+ if (retval < 0) {
+ retval = memFullErr;
+ } else {
+ retval = noErr;
+ }
+
+ // Write the plist to a temporary file.
+
+ if (retval == noErr) {
+ strlcpy(plistPath, "/tmp/BASTemp-XXXXXXXX.plist", sizeof(plistPath));
+
+ fd = mkstemps(plistPath, strlen( strrchr(plistPath, '.') ) );
+ if (fd < 0) {
+ retval = BASErrnoToOSStatus( errno );
+ }
+ }
+ if (retval == noErr) {
+ retval = BASErrnoToOSStatus( BASWrite(fd, plistText, strlen(plistText), NULL) );
+ }
+
+ // Run the tool as root using AuthorizationExecuteWithPrivileges.
+
+ if (retval == noErr) {
+ retval = RunInstallToolAsRoot(auth, installToolPath, kBASInstallToolInstallCommand, bundleID, helperToolPath, plistPath, NULL);
+ }
+
+ // Clean up.
+
+ free(plistText);
+ if (fd != -1) {
+ junk = close(fd);
+ assert(junk == 0);
+
+ junk = unlink(plistPath);
+ assert(junk == 0);
+ }
+
+ return retval;
+}
+
+static OSStatus GetToolPath(CFStringRef bundleID, CFStringRef toolName, char *toolPath, size_t toolPathSize)
+ // Given a bundle identifier and the name of a tool embedded within that bundle,
+ // get a file system path to the tool.
+{
+ OSStatus err;
+ CFBundleRef bundle;
+ Boolean success;
+ CFURLRef toolURL;
+
+ assert(bundleID != NULL);
+ assert(toolName != NULL);
+ assert(toolPath != NULL);
+ assert(toolPathSize > 0);
+
+ toolURL = NULL;
+
+ err = noErr;
+ bundle = CFBundleGetBundleWithIdentifier(bundleID);
+ if (bundle == NULL) {
+ err = coreFoundationUnknownErr;
+ }
+ if (err == noErr) {
+ toolURL = CFBundleCopyAuxiliaryExecutableURL(bundle, toolName);
+ if (toolURL == NULL) {
+ err = coreFoundationUnknownErr;
+ }
+ }
+ if (err == noErr) {
+ success = CFURLGetFileSystemRepresentation(toolURL, true, (UInt8 *) toolPath, toolPathSize);
+ if ( ! success ) {
+ err = coreFoundationUnknownErr;
+ }
+ }
+
+ if (toolURL != NULL) {
+ CFRelease(toolURL);
+ }
+
+ return err;
+}
+
+extern OSStatus BASFixFailure(
+ AuthorizationRef auth,
+ CFStringRef bundleID,
+ CFStringRef installToolName,
+ CFStringRef helperToolName,
+ BASFailCode failCode
+)
+ // See comment in header.
+{
+ OSStatus retval;
+ Boolean success;
+ char bundleIDC[PATH_MAX];
+ char installToolPath[PATH_MAX];
+ char helperToolPath[PATH_MAX];
+
+ // Pre-conditions
+
+ assert(auth != NULL);
+ assert(bundleID != NULL);
+ assert(installToolName != NULL);
+ assert(helperToolName != NULL);
+
+ // Get the bundle identifier as a UTF-8 C string. Also, get paths for both of
+ // the tools.
+
+ retval = noErr;
+ success = CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC));
+ if ( ! success ) {
+ retval = coreFoundationUnknownErr;
+ }
+ if (retval == noErr) {
+ retval = GetToolPath(bundleID, installToolName, installToolPath, sizeof(installToolPath));
+ }
+ if (retval == noErr) {
+ retval = GetToolPath(bundleID, helperToolName, helperToolPath, sizeof(helperToolPath));
+ }
+
+ // Depending on the failure code, either run the enable command or the install
+ // from scratch command.
+
+ if (retval == noErr) {
+ if (failCode == kBASFailDisabled) {
+ retval = RunInstallToolAsRoot(auth, installToolPath, kBASInstallToolEnableCommand, bundleIDC, NULL);
+ } else {
+ retval = BASInstall(auth, bundleIDC, installToolPath, helperToolPath);
+ }
+ }
+
+ return retval;
+}
Property changes on: branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLib.c
___________________________________________________________________
Name: svn:executable
+ *
Added: branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLib.h
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLib.h (rev 0)
+++ branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLib.h 2008-08-01 14:07:22 UTC (rev 38876)
@@ -0,0 +1,783 @@
+/*
+ File: BetterAuthorizationSampleLib.h
+
+ Contains: Interface to reusable code for privileged helper tools.
+
+ Written by: DTS
+
+ Copyright: Copyright (c) 2007 Apple Inc. All Rights Reserved.
+
+ Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple, Inc.
+ ("Apple") in consideration of your agreement to the following terms, and your
+ use, installation, modification or redistribution of this Apple software
+ constitutes acceptance of these terms. If you do not agree with these terms,
+ please do not use, install, modify or redistribute this Apple software.
+
+ In consideration of your agreement to abide by the following terms, and subject
+ to these terms, Apple grants you a personal, non-exclusive license, under Apple's
+ copyrights in this original Apple software (the "Apple Software"), to use,
+ reproduce, modify and redistribute the Apple Software, with or without
+ modifications, in source and/or binary forms; provided that if you redistribute
+ the Apple Software in its entirety and without modifications, you must retain
+ this notice and the following text and disclaimers in all such redistributions of
+ the Apple Software. Neither the name, trademarks, service marks or logos of
+ Apple, Inc. may be used to endorse or promote products derived from the
+ Apple Software without specific prior written permission from Apple. Except as
+ expressly stated in this notice, no other rights or licenses, express or implied,
+ are granted by Apple herein, including but not limited to any patent rights that
+ may be infringed by your derivative works or by other works in which the Apple
+ Software may be incorporated.
+
+ The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
+ WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
+ WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
+ COMBINATION WITH YOUR PRODUCTS.
+
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
+ OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
+ (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+#ifndef _BetterAuthorizationSampleLIB_H
+#define _BetterAuthorizationSampleLIB_H
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+#include <asl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/////////////////////////////////////////////////////////////////
+
+/*
+ This header has extensive HeaderDoc comments. To see these comments in a more
+ felicitous form, you can generate HTML from the HeaderDoc comments using the
+ following command:
+
+ $ headerdoc2html BetterAuthorizationSampleLib.h
+ $ open BetterAuthorizationSampleLib/index.html
+*/
+
+/*!
+ @header BetterAuthorizationSampleLib
+
+ @abstract Reusable library for creating helper tools that perform privileged
+ operations on behalf of your application.
+
+ @discussion BetterAuthorizationSampleLib allows you to perform privileged operations
+ in a helper tool. In this model, your application runs with standard
+ privileges and, when it needs to do a privileged operation, it makes a
+ request to the helper tool. The helper tool uses Authorization Services
+ to ensure that the user is authorized to perform that operation.
+
+ BetterAuthorizationSampleLib takes care of all of the mechanics of
+ installing the helper tool and communicating with it. Specifically, it
+ has routines that your application can call to:
+
+ 1. send requests to a helper tool (BASExecuteRequestInHelperTool)
+
+ 2. install the helper tool if it's not installed, or fix an installation if
+ it's broken (BASDiagnoseFailure and BASFixFailure)
+
+ BetterAuthorizationSampleLib also helps you implement the helper tool.
+ Specifically, you call the routine BASHelperToolMain in the main entry
+ point for your helper tool, passing it an array of command callbacks (of
+ type BASCommandProc). BASHelperToolMain will take care of all the details
+ of communication with the application and only call your callback to
+ execute the actual command.
+
+ A command consists of request and response CFDictionaries (or, equivalently,
+ NSDictionaries). BetterAuthorizationSampleLib defines three special keys for
+ these dictionaries:
+
+ 1. kBASCommandKey -- In the request dictionary, this is the name of the
+ command. Its value is a string that uniquely identifies the command within
+ your program.
+
+ 2. kBASErrorKey -- In the response dictionary, this is the error result for
+ the request. Its value is an OSStatus-style error code.
+
+ 3. kBASDescriptorArrayKey -- In the response dictionary, if present, this is
+ an array of file descriptors being returned from the helper tool.
+
+ You can use any other key to represent addition parameters (or return values)
+ for the command. The only constraints that BetterAuthorizationSampleLib applies
+ to these extra parameters is that they must be serialisable as a CFPropertyList.
+
+ BetterAuthorizationSampleLib requires that you tell it about the list of commands
+ that you support. Each command is represented by a command specification
+ (BASCommandSpec). The command specification includes the following information:
+
+ 1. The name of the command. This is the same as the kBASCommandKey value in
+ the request dictionary.
+
+ 2. The authorization right associated with the command. BetterAuthorizationSampleLib
+ uses this to ensure that the user is authorized to use the command before
+ it calls your command callback in the privileged helper tool.
+
+ 3. Information to create the command's authorization right specification in the
+ policy database. The is used by the BASSetDefaultRules function.
+
+ Finally, BetterAuthorizationSampleLib includes a number of utilities routines to help
+ wrangle error codes (BASErrnoToOSStatus, BASOSStatusToErrno, and BASGetErrorFromResponse)
+ and file descriptors (BASCloseDescriptorArray).
+*/
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Command Description
+
+/*!
+ @struct BASCommandSpec
+
+ @abstract Describes a privileged operation to BetterAuthorizationSampleLib.
+
+ @discussion Both the application and the tool must tell BetterAuthorizationSampleLib about
+ the operations (that is, commands) that they support. They do this by passing
+ in an array of BASCommandSpec structures. Each element describes one command.
+ The array is terminated by a command whose commandName field is NULL.
+
+ In general the application and tool should use the same array definition.
+ However, there are cases where these might be out of sync. For example, if you
+ have an older version of the application talking to a newer version of the tool,
+ the tool might know about more commands than the application (and thus provide a
+ longer array), and that's OK.
+
+ @field commandName
+ A identifier for this command. This can be any string that is unique within
+ the context of your programs. A NULL value in this field terminates the array.
+
+ The length of the command name must not be greater than 1024 UTF-16 values.
+
+ @field rightName
+ This is the name of the authorization right associated with the
+ command. This can be NULL if you don't want any right associated with the
+ command. If it's not NULL, BetterAuthorizationSampleLib will acquire that right
+ before allowing the command to execute.
+
+ @field rightDefaultRule
+ This is the name of an authorization rule that should be used in
+ the default right specification for the right. To see a full list of these rules,
+ look at the "rules" dictionary within the policy database (currently
+ "/etc/authorization"). Common values include "default" (which requires that the user
+ hold credentials that authenticate them as an admin user) and "allow" (which will let
+ anyone acquire the right).
+
+ This must be NULL if (and only if) rightName is NULL.
+
+ @field rightDescriptionKey
+ This is a key used to form a custom prompt for the right. The value of this
+ string should be a key into a .strings file whose name you supply to
+ BASSetDefaultRules. When BetterAuthorizationSampleLib creates the right specification,
+ it uses this key to get all of the localised prompt strings for the right.
+
+ This must be NULL if rightName is NULL. Otherwise, this may be NULL if you
+ don't want a custom prompt for your right.
+
+ @field userData
+ This field is is for the benefit of the client; BetterAuthorizationSampleLib
+ does not use it in any way.
+*/
+
+struct BASCommandSpec {
+ const char * commandName;
+ const char * rightName;
+ const char * rightDefaultRule;
+ const char * rightDescriptionKey;
+ const void * userData;
+};
+typedef struct BASCommandSpec BASCommandSpec;
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Request/Response Keys
+
+// Standard keys for the request dictionary
+
+/*!
+ @define kBASCommandKey
+
+ @abstract Key for the command string within the request dictionary.
+
+ @discussion Within a request, this key must reference a string that is the name of the
+ command to execute. This must match one of the commands in the
+ BASCommandSpec array.
+
+ The length of a command name must not be greater than 1024 UTF-16 values.
+*/
+
+#define kBASCommandKey "com.apple.dts.BetterAuthorizationSample.command" // CFString
+
+// Standard keys for the response dictionary
+
+/*!
+ @define kBASErrorKey
+
+ @abstract Key for the error result within the response dictionary.
+
+ @discussion Within a response, this key must reference a number that is the error result
+ for the response, interpreted as an OSStatus.
+*/
+
+#define kBASErrorKey "com.apple.dts.BetterAuthorizationSample.error" // CFNumber
+
+/*!
+ @define kBASDescriptorArrayKey
+
+ @abstract Key for a file descriptor array within the response dictionary.
+
+ @discussion Within a response, this key, if present, must reference an array
+ of numbers, which are the file descriptors being returned with
+ the response. The numbers are interpreted as ints.
+*/
+
+#define kBASDescriptorArrayKey "com.apple.dts.BetterAuthorizationSample.descriptors" // CFArray of CFNumber
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Helper Tool Routines
+
+/*!
+ @functiongroup Helper Tool Routines
+*/
+
+/*!
+ @typedef BASCommandProc
+
+ @abstract Command processing callback.
+
+ @discussion When your helper tool calls BASHelperToolMain, it passes in a pointer to an
+ array of callback functions of this type. When BASHelperToolMain receives a
+ valid command, it calls one of these function so that your program-specific
+ code can process the request. BAS guarantees that the effective, save and
+ real user IDs (EUID, SUID, RUID) will all be zero at this point (that is,
+ you're "running as root").
+
+ By the time this callback is called, BASHelperToolMain has already verified that
+ this is a known command. It also acquires the authorization right associated
+ with the command, if any. However, it does nothing to validate the other
+ parameters in the request. These parameters come from a non-privileged source
+ and you should verify them carefully.
+
+ Your implementation should get any input parameters from the request and place
+ any output parameters in the response. It can also put an array of file
+ descriptors into the response using the kBASDescriptorArrayKey key.
+
+ If an error occurs, you should just return an appropriate error code.
+ BASHelperToolMain will ensure that this gets placed in the response.
+
+ You should attempt to fail before adding any file descriptors to the response,
+ or remove them once you know that you're going to fail. If you put file
+ descriptors into the response and then return an error, those descriptors will
+ still be passed back to the client. It's likely the client isn't expecting this.
+
+ Calls to this function will be serialised; that is, once your callback is
+ running, BASHelperToolMain won't call you again until you return. Your callback
+ should avoid blocking for long periods of time. If you block for too long, the
+ BAS watchdog will kill the entire helper tool process.
+
+ This callback runs in a daemon context; you must avoid doing things that require the
+ user's context. For example, launching a GUI application would be bad. See
+ Technote 2083 "Daemons and Agents" for more information about execution contexts.
+
+ @param auth This is a reference to the authorization instance associated with the original
+ application that made the request.
+
+ This will never be NULL.
+
+ @param userData This is the value from the userData field of the corresponding entry in the
+ BASCommandSpec array that you passed to BASHelperToolMain.
+
+ @param request This dictionary contains the request. It will have, at a bare minimum, a
+ kBASCommandKey item whose value matches one of the commands in the
+ BASCommandSpec array you passed to BASHelperToolMain. It may also have
+ other, command-specific parameters.
+
+ This will never be NULL.
+
+ @param response This is a dictionary into which you can place the response. It will start out
+ empty, and you can add any results you please to it.
+
+ If you need to return file descriptors, place them in an array and place that
+ array in the response using the kBASDescriptorArrayKey key.
+
+ There's no need to set the error result in the response. BASHelperToolMain will
+ do that for you. However, if you do set a value for the kBASErrorKey key,
+ that value will take precedence; in this case, the function result is ignored.
+
+ This will never be NULL.
+
+ @param asl A reference to the ASL client handle for logging.
+
+ This may be NULL. However, ASL handles a NULL input, so you don't need to
+ conditionalise your code.
+
+ @param aslMsg A reference to a ASL message template for logging.
+
+ This may be NULL. However, ASL handles a NULL input, so you don't need to
+ conditionalise your code.
+*/
+
+typedef OSStatus (*BASCommandProc)(
+ AuthorizationRef auth,
+ const void * userData,
+ CFDictionaryRef request,
+ CFMutableDictionaryRef response,
+ aslclient asl,
+ aslmsg aslMsg
+);
+
+/*!
+ @function BASHelperToolMain
+
+ @abstract Entry point for a privileged helper tool.
+
+ @discussion You should call this function from the main function of your helper tool. It takes
+ care of all of the details of receiving and processing commands. It will call you
+ back (via one of the commandProcs callbacks) when a valid request arrives.
+
+ This function assumes acts like a replacement for main. Thus, it assumes that
+ it owns various process-wide resources (like SIGALRM and the disposition of
+ SIGPIPE). You should not use those resources, either in your main function or
+ in your callback function. Also, you should not call this function on a thread,
+ or start any other threads in the process. Finally, this function has a habit of
+ exiting the entire process if something goes wrong. You should not expect the
+ function to always return.
+
+ This function does not clean up after itself. When this function returns, you
+ are expected to exit. If the function result is noErr, the command processing
+ loop quit in an expected manner (typically because of an idle timeout). Otherwise
+ it quit because of an error.
+
+ @param commands An array that describes the commands that you implement, and their associated
+ rights. The array is terminated by a command with a NULL name. There must be
+ at least one valid command.
+
+ @param commandProcs
+ An array of callback routines that are called when a valid request arrives. The
+ array is expected to perform the operation associated with the corresponding
+ command and set up the response values, if any. The array is terminated by a
+ NULL pointer.
+
+ IMPORTANT: The array must have exactly the same number of entries as the
+ commands array.
+
+ @result An integer representing EXIT_SUCCESS or EXIT_FAILURE.
+*/
+
+extern int BASHelperToolMain(
+ const BASCommandSpec commands[],
+ const BASCommandProc commandProcs[]
+);
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Application Routines
+
+/*!
+ @functiongroup Application Routines
+*/
+
+/*!
+ @function BASSetDefaultRules
+
+ @abstract Creates default right specifications in the policy database.
+
+ @discussion This routine ensures that the policy database (currently
+ "/etc/authorization") contains right specifications for all of the rights
+ that you use (as specified by the commands array). This has two important
+ consequences:
+
+ 1. It makes the rights that you use visible to the system administrator.
+ All they have to do is run your program once and they can see your default
+ right specifications in the policy database.
+
+ 2. It means that, when the privileged helper tool tries to acquire the right,
+ it will use your specification of the right (as modified by the system
+ administrator) rather than the default right specification.
+
+ You must call this function before calling BASExecuteRequestInHelperTool.
+ Typically you would call it at application startup time, or lazily, immediately
+ before calling BASExecuteRequestInHelperTool.
+
+ @param auth A reference to your program's authorization instance; you typically get this
+ by calling AuthorizationCreate.
+
+ This must not be NULL.
+
+ @param commands An array that describes the commands that you implement, and their associated
+ rights. There must be at least one valid command.
+
+ @param bundleID The bundle identifier for your program.
+
+ This must not be NULL.
+
+ @param descriptionStringTableName
+ The name of the .strings file from which to fetch the localised custom
+ prompts for the rights in the commands array (if any). A NULL value is
+ equivalent to passing "Localizable" (that is, it gets the prompts from
+ "Localizable.strings").
+
+ For example, imagine you have a command for which you require a custom prompt.
+ You should put the custom prompt in a .strings file, let's call it
+ "AuthPrompts.strings". You should then pass "AuthPrompts" to this parameter
+ and put the key that gets the prompt into the rightDescriptionKey of the command.
+*/
+
+extern void BASSetDefaultRules(
+ AuthorizationRef auth,
+ const BASCommandSpec commands[],
+ CFStringRef bundleID,
+ CFStringRef descriptionStringTableName
+);
+
+/*!
+ @function BASExecuteRequestInHelperTool
+
+ @abstract Executes a request in the privileged helper tool, returning the response.
+
+ @discussion This routine synchronously executes a request in the privileged helper tool and
+ returns the response.
+
+ If the function returns an error, the IPC between your application and the helper tool
+ failed. Unfortunately it's not possible to tell whether this failure occurred while
+ sending the request or receiving the response, thus it's not possible to know whether
+ the privileged operation was done or not.
+
+ If the functions returns no error, the IPC between your application and the helper tool
+ was successful. However, the command may still have failed. You must get the error
+ value from the response (typically using BASGetErrorFromResponse) to see if the
+ command succeeded or not.
+
+ On success the response dictionary may contain a value for the kBASDescriptorArrayKey key.
+ If so, that will be a non-empty CFArray of CFNumbers, each of which can be accessed as an int.
+ Each value is a descriptor that is being returned to you from the helper tool. You are
+ responsible for closing these descriptors when you're done with them.
+
+ @param auth A reference to your program's authorization instance; you typically get this
+ by calling AuthorizationCreate.
+
+ This must not be NULL.
+
+ @param commands An array that describes the commands that you implement, and their associated
+ rights. There must be at least one valid command.
+
+ @param bundleID The bundle identifier for your program.
+
+ This must not be NULL.
+
+ @param request A dictionary describing the requested operation. This must, at least, contain
+ a string value for the kBASCommandKey. Furthermore, this string must match
+ one of the commands in the array.
+
+ The dictionary may also contain other values. These are passed to the helper
+ tool unintepreted. All values must be serialisable using the CFPropertyList
+ API.
+
+ This must not be NULL.
+
+ @param response This must not be NULL. On entry, *response must be NULL. On success, *response
+ will not be NULL. On error, *response will be NULL.
+
+ On success, you are responsible for disposing of *response. You are also
+ responsible for closing any descriptors returned in the response.
+
+ @result An OSStatus code (see BASErrnoToOSStatus and BASOSStatusToErrno).
+*/
+
+extern OSStatus BASExecuteRequestInHelperTool(
+ AuthorizationRef auth,
+ const BASCommandSpec commands[],
+ CFStringRef bundleID,
+ CFDictionaryRef request,
+ CFDictionaryRef * response
+);
+
+/*!
+ @enum BASFailCode
+
+ @abstract Indicates why a request failed.
+
+ @discussion If BASExecuteRequestInHelperTool fails with an error (indicating
+ an IPC failure), you can call BASDiagnoseFailure to determine what
+ went wrong. BASDiagnoseFailure will return the value of this
+ type that best describes the failure.
+
+ @constant kBASFailUnknown
+ Indicates that BASDiagnoseFailure could not accurately determine the cause of the
+ failure.
+
+ @constant kBASFailDisabled
+ The request failed because the helper tool is installed but disabled.
+
+ @constant kBASFailPartiallyInstalled
+ The request failed because the helper tool is only partially installed.
+
+ @constant kBASFailNotInstalled
+ The request failed because the helper tool is not installed at all.
+
+ @constant kBASFailNeedsUpdate
+ The request failed because the helper tool is installed but out of date.
+ BASDiagnoseFailure will never return this value. However, if you detect that
+ the helper tool is out of date (typically by sending it a "get version" request)
+ you can pass this value to BASFixFailure to force it to update the tool.
+*/
+
+enum {
+ kBASFailUnknown,
+ kBASFailDisabled,
+ kBASFailPartiallyInstalled,
+ kBASFailNotInstalled,
+ kBASFailNeedsUpdate
+};
+typedef uint32_t BASFailCode;
+
+/*!
+ @function BASDiagnoseFailure
+
+ @abstract Determines the cause of a failed request.
+
+ @discussion If BASExecuteRequestInHelperTool fails with an error (indicating an
+ IPC failure), you can call this routine to determine what went wrong.
+ It returns a BASFailCode value indicating the cause of the failure.
+ You should use this value to tell the user what's going on and what
+ you intend to do about it. Once you get the user's consent, you can
+ call BASFixFailure to fix the problem.
+
+ For example, if this function result is kBASFailDisabled, you could put up the
+ dialog saying:
+
+ My privileged helper tool is disabled. Would you like to enable it?
+ This operation may require you to authorize as an admin user.
+ [Cancel] [[Enable]]
+
+ On the other hand, if this function result is kBASFailNotInstalled, the dialog might be:
+
+ My privileged helper tool is not installed. Would you like to install it?
+ This operation may require you to authorize as an admin user.
+ [Cancel] [[Install]]
+
+ BASDiagnoseFailure will never return kBASFailNeedsUpdate. It's your responsibility
+ to detect version conflicts (a good way to do this is by sending a "get version" request
+ to the helper tool). However, once you've detected a version conflict, you can pass
+ kBASFailNeedsUpdate to BASFixFailure to get it to install the latest version of your
+ helper tool.
+
+ If you call this routine when everything is working properly, you're likely to get
+ a result of kBASFailUnknown.
+
+ @param auth A reference to your program's authorization instance; you typically get this
+ by calling AuthorizationCreate.
+
+ This must not be NULL.
+
+ @param bundleID The bundle identifier for your program.
+
+ This must not be NULL.
+
+ @result A BASFailCode value indicating the cause of the failure. This will never be
+ kBASFailNeedsUpdate.
+*/
+
+extern BASFailCode BASDiagnoseFailure(
+ AuthorizationRef auth,
+ CFStringRef bundleID
+);
+
+/*!
+ @function BASFixFailure
+
+ @abstract Installs, or reinstalls, the privileged helper tool.
+
+ @discussion This routine installs or reinstalls the privileged helper tool. Typically
+ you call this in response to an IPC failure talking to the tool. You first
+ diagnose the failure using BASDiagnoseFailure and then call this routine to
+ fix the failure by installing (or reinstalling) the tool.
+
+ Because the helper tool is privileged, installing it is a privileged
+ operation. This routine will do its work by calling
+ AuthorizationExecuteWithPrivileges, which is likely to prompt the user
+ for an admin name and password.
+
+ @param auth A reference to your program's authorization instance; you typically get this
+ by calling AuthorizationCreate.
+
+ This must not be NULL.
+
+ @param bundleID The bundle identifier for your program.
+
+ This must not be NULL.
+
+ @param installToolName
+ The name of the install tool within your bundle. You should place the tool
+ in the executable directory within the bundle. Specifically, the tool must be
+ available by passing this name to CFBundleCopyAuxiliaryExecutableURL.
+
+ This must not be NULL.
+
+ @param helperToolName
+ The name of the helper tool within your bundle. You should place the tool
+ in the executable directory within the bundle. Specifically, the tool must be
+ available by passing this name to CFBundleCopyAuxiliaryExecutableURL.
+
+ This must not be NULL.
+
+ @param failCode A value indicating the type of failure that's occurred. In most cases you get this
+ value by calling BASDiagnoseFailure.
+
+ @result An OSStatus code (see BASErrnoToOSStatus and BASOSStatusToErrno).
+*/
+
+extern OSStatus BASFixFailure(
+ AuthorizationRef auth,
+ CFStringRef bundleID,
+ CFStringRef installToolName,
+ CFStringRef helperToolName,
+ BASFailCode failCode
+);
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Utility Routines
+
+/*!
+ @functiongroup Utilities
+*/
+
+/*!
+ @function BASErrnoToOSStatus
+
+ @abstract Convert an errno value to an OSStatus value.
+
+ @discussion All errno values have accepted alternatives in the errSecErrnoBase
+ OSStatus range, and this routine does the conversion. For example,
+ ENOENT becomes errSecErrnoBase + ENOENT. Any value that's not
+ recognised just gets passed through unmodified.
+
+ A value of 0 becomes noErr.
+
+ For more information about errSecErrnoBase, see DTS Q&A 1499
+ <http://developer.apple.com/qa/qa2006/qa1499.html>.
+
+ @param errNum The errno value to convert.
+
+ @result An OSStatus code representing the errno equivalent.
+*/
+
+extern OSStatus BASErrnoToOSStatus(int errNum);
+
+/*!
+ @function BASOSStatusToErrno
+
+ @abstract Convert an OSStatus value to an errno value.
+
+ @discussion This function converts some specific OSStatus values (Open Transport and
+ errSecErrnoBase ranges) to their corresponding errno values. It more-or-less
+ undoes the conversion done by BASErrnoToOSStatus, including a pass
+ through for unrecognised values.
+
+ It's worth noting that there are many more defined OSStatus error codes
+ than errno error codes, so you're more likely to encounter a passed
+ through value when going in this direction.
+
+ A value of noErr becomes 0.
+
+ For more information about errSecErrnoBase, see DTS Q&A 1499
+ <http://developer.apple.com/qa/qa2006/qa1499.html>.
+
+ @param errNum The OSStatus value to convert.
+
+ @result An integer code representing the OSStatus equivalent.
+*/
+
+extern int BASOSStatusToErrno(OSStatus errNum);
+
+/*!
+ @function BASGetErrorFromResponse
+
+ @abstract Extracts the error status from a helper tool response.
+
+ @discussion This function extracts the error status from a helper tool response.
+ Specifically, its uses the kBASErrorKey key to get a CFNumber and
+ it gets the resulting value from that number.
+
+ @param response A helper tool response, typically acquired by calling BASExecuteRequestInHelperTool.
+
+ This must not be NULL
+
+ @result An OSStatus code (see BASErrnoToOSStatus and BASOSStatusToErrno).
+*/
+
+extern OSStatus BASGetErrorFromResponse(CFDictionaryRef response);
+
+/*!
+ @function BASCloseDescriptorArray
+
+ @abstract Closes all of the file descriptors referenced by a CFArray.
+
+ @discussion Given a CFArray of CFNumbers, treat each number as a file descriptor
+ and close it.
+
+ The most common reason to use this routine is that you've executed,
+ using BASExecuteRequestInHelperTool, a request that returns a response
+ with embedded file descriptors, and you want to close those descriptors.
+ In that case, you typically call this as:
+
+ BASCloseDescriptorArray( CFDictionaryGetValue(response, CFSTR(kBASDescriptorArrayKey)) );
+
+ @param descArray
+ The array containing the descriptors to close.
+
+ This may be NULL, in which case the routine does nothing.
+*/
+
+extern void BASCloseDescriptorArray(
+ CFArrayRef descArray
+);
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Utility Routines
+
+// The following definitions are exported purely for the convenience of the
+// install tool ("BetterAuthorizationSampleLibInstallTool.c"). You must not
+// use them in your own code.
+
+#if !defined(BAS_PRIVATE)
+ #define BAS_PRIVATE 0
+#endif
+#if BAS_PRIVATE
+
+ // Hard-wired file system paths for the launchd property list file and
+ // the privileged helper tool. In all cases, %s is a placeholder
+ // for the bundle ID (in file system representation).
+
+ #define kBASPlistPathFormat "/Library/LaunchDaemons/%s.plist"
+
+ #define kBASToolDirPath "/Library/PrivilegedHelperTools" // KEEP IN SYNC!
+ #define kBASToolPathFormat "/Library/PrivilegedHelperTools/%s" // KEEP IN SYNC!
+
+ // Commands strings for the install tool.
+
+ #define kBASInstallToolInstallCommand "install"
+ #define kBASInstallToolEnableCommand "enable"
+
+ // Magic values used to bracket the process ID returned by the install tool.
+
+ #define kBASAntiZombiePIDToken1 "cricket<"
+ #define kBASAntiZombiePIDToken2 ">bat"
+
+ // Magic value used to indicate success or failure from the install tool.
+
+ #define kBASInstallToolSuccess "oK"
+ #define kBASInstallToolFailure "FailUrE %d"
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Property changes on: branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLib.h
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLibInstallTool.c
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLibInstallTool.c (rev 0)
+++ branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLibInstallTool.c 2008-08-01 14:07:22 UTC (rev 38876)
@@ -0,0 +1,472 @@
+/*
+ File: BetterAuthorizationSampleLibInstallTool.c
+
+ Contains: Tool to install BetterAuthorizationSampleLib-based privileged helper tools.
+
+ Written by: DTS
+
+ Copyright: Copyright (c) 2007 Apple Inc. All Rights Reserved.
+
+ Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple, Inc.
+ ("Apple") in consideration of your agreement to the following terms, and your
+ use, installation, modification or redistribution of this Apple software
+ constitutes acceptance of these terms. If you do not agree with these terms,
+ please do not use, install, modify or redistribute this Apple software.
+
+ In consideration of your agreement to abide by the following terms, and subject
+ to these terms, Apple grants you a personal, non-exclusive license, under Apple's
+ copyrights in this original Apple software (the "Apple Software"), to use,
+ reproduce, modify and redistribute the Apple Software, with or without
+ modifications, in source and/or binary forms; provided that if you redistribute
+ the Apple Software in its entirety and without modifications, you must retain
+ this notice and the following text and disclaimers in all such redistributions of
+ the Apple Software. Neither the name, trademarks, service marks or logos of
+ Apple, Inc. may be used to endorse or promote products derived from the
+ Apple Software without specific prior written permission from Apple. Except as
+ expressly stated in this notice, no other rights or licenses, express or implied,
+ are granted by Apple herein, including but not limited to any patent rights that
+ may be infringed by your derivative works or by other works in which the Apple
+ Software may be incorporated.
+
+ The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
+ WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
+ WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
+ COMBINATION WITH YOUR PRODUCTS.
+
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
+ OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
+ (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+// Allows access to path information associated with tool and plist installation
+// from BetterAuthorizationSampleLib.h
+#define BAS_PRIVATE 1
+
+#include "BetterAuthorizationSampleLib.h"
+
+extern char **environ;
+
+static int RunLaunchCtl(
+ bool junkStdIO,
+ const char *command,
+ const char *plistPath
+)
+ // Handles all the invocations of launchctl by doing the fork() + execve()
+ // for proper clean-up. Only two commands are really supported by our
+ // implementation; loading and unloading of a job via the plist pointed at
+ // (const char *) plistPath.
+{
+ int err;
+ const char * args[5];
+ pid_t childPID;
+ pid_t waitResult;
+ int status;
+
+ // Pre-conditions.
+ assert(command != NULL);
+ assert(plistPath != NULL);
+
+ // Make sure we get sensible logging even if we never get to the waitpid.
+
+ status = 0;
+
+ // Set up the launchctl arguments. We run launchctl using StartupItemContext
+ // because, in future system software, launchctl may decide on the launchd
+ // to talk to based on your Mach bootstrap namespace rather than your RUID.
+
+ args[0] = "/bin/launchctl";
+ args[1] = command; // "load" or "unload"
+ args[2] = "-w";
+ args[3] = plistPath; // path to plist
+ args[4] = NULL;
+
+ fprintf(stderr, "launchctl %s %s '%s'\n", args[1], args[2], args[3]);
+
+ // Do the standard fork/exec dance.
+
+ childPID = fork();
+ switch (childPID) {
+ case 0:
+ // child
+ err = 0;
+
+ // If we've been told to junk the I/O for launchctl, open
+ // /dev/null and dup that down to stdin, stdout, and stderr.
+
+ if (junkStdIO) {
+ int fd;
+ int err2;
+
+ fd = open("/dev/null", O_RDWR);
+ if (fd < 0) {
+ err = errno;
+ }
+ if (err == 0) {
+ if ( dup2(fd, STDIN_FILENO) < 0 ) {
+ err = errno;
+ }
+ }
+ if (err == 0) {
+ if ( dup2(fd, STDOUT_FILENO) < 0 ) {
+ err = errno;
+ }
+ }
+ if (err == 0) {
+ if ( dup2(fd, STDERR_FILENO) < 0 ) {
+ err = errno;
+ }
+ }
+ err2 = close(fd);
+ if (err2 < 0) {
+ err2 = 0;
+ }
+ if (err == 0) {
+ err = err2;
+ }
+ }
+ if (err == 0) {
+ err = execve(args[0], (char **) args, environ);
+ }
+ if (err < 0) {
+ err = errno;
+ }
+ _exit(EXIT_FAILURE);
+ break;
+ case -1:
+ err = errno;
+ break;
+ default:
+ err = 0;
+ break;
+ }
+
+ // Only the parent gets here. Wait for the child to complete and get its
+ // exit status.
+
+ if (err == 0) {
+ do {
+ waitResult = waitpid(childPID, &status, 0);
+ } while ( (waitResult == -1) && (errno == EINTR) );
+
+ if (waitResult < 0) {
+ err = errno;
+ } else {
+ assert(waitResult == childPID);
+
+ if ( ! WIFEXITED(status) || (WEXITSTATUS(status) != 0) ) {
+ err = EINVAL;
+ }
+ }
+ }
+
+ fprintf(stderr, "launchctl -> %d %ld 0x%x\n", err, (long) childPID, status);
+
+ return err;
+}
+
+static int CopyFileOverwriting(
+ const char *sourcePath,
+ mode_t destMode,
+ const char *destPath
+)
+ // Our own version of a file copy. This routine will either handle
+ // the copy of the tool binary or the plist file associated with
+ // that binary. As the function name suggests, it writes over any
+ // existing file pointed to by (const char *) destPath.
+{
+ int err;
+ int junk;
+ int sourceFD;
+ int destFD;
+ char buf[65536];
+
+ // Pre-conditions.
+ assert(sourcePath != NULL);
+ assert(destPath != NULL);
+
+ (void) unlink(destPath);
+
+ destFD = -1;
+
+ err = 0;
+ sourceFD = open(sourcePath, O_RDONLY);
+ if (sourceFD < 0) {
+ err = errno;
+ }
+
+ if (err == 0) {
+ destFD = open(destPath, O_CREAT | O_EXCL | O_WRONLY, destMode);
+ if (destFD < 0) {
+ err = errno;
+ }
+ }
+
+ if (err == 0) {
+ ssize_t bytesReadThisTime;
+ ssize_t bytesWrittenThisTime;
+ ssize_t bytesWritten;
+
+ do {
+ bytesReadThisTime = read(sourceFD, buf, sizeof(buf));
+ if (bytesReadThisTime < 0) {
+ err = errno;
+ }
+
+ bytesWritten = 0;
+ while ( (err == 0) && (bytesWritten < bytesReadThisTime) ) {
+ bytesWrittenThisTime = write(destFD, &buf[bytesWritten], bytesReadThisTime - bytesWritten);
+ if (bytesWrittenThisTime < 0) {
+ err = errno;
+ } else {
+ bytesWritten += bytesWrittenThisTime;
+ }
+ }
+
+ } while ( (err == 0) && (bytesReadThisTime != 0) );
+ }
+
+ // Clean up.
+
+ if (sourceFD != -1) {
+ junk = close(sourceFD);
+ assert(junk == 0);
+ }
+ if (destFD != -1) {
+ junk = close(destFD);
+ assert(junk == 0);
+ }
+
+ fprintf(stderr, "copy '%s' %#o '%s' -> %d\n", sourcePath, (int) destMode, destPath, err);
+
+ return err;
+}
+
+static int InstallCommand(
+ const char * bundleID,
+ const char * toolSourcePath,
+ const char * plistSourcePath
+)
+ // Heavy lifting function for handling all the necessary steps to install a
+ // helper tool in the correct location, with the correct permissions,
+ // and call launchctl in order to load it as a current job.
+{
+ int err;
+ char toolDestPath[PATH_MAX];
+ char plistDestPath[PATH_MAX];
+ struct stat sb;
+ static const mode_t kDirectoryMode = ACCESSPERMS & ~(S_IWGRP | S_IWOTH);
+ static const mode_t kExecutableMode = ACCESSPERMS & ~(S_IWGRP | S_IWOTH);
+ static const mode_t kFileMode = DEFFILEMODE & ~(S_IWGRP | S_IWOTH);
+
+ // Pre-conditions.
+ assert(bundleID != NULL);
+ assert(toolSourcePath != NULL);
+ assert(plistSourcePath != NULL);
+
+ (void) snprintf(toolDestPath, sizeof(toolDestPath), kBASToolPathFormat, bundleID);
+ (void) snprintf(plistDestPath, sizeof(plistDestPath), kBASPlistPathFormat, bundleID);
+
+ // Stop the helper tool if it's currently running.
+
+ (void) RunLaunchCtl(true, "unload", plistDestPath);
+
+ // Create the PrivilegedHelperTools directory. The owner will be "root" because
+ // we're running as root (our EUID is 0). The group will be "admin" because
+ // it's inherited from "/Library". The permissions will be rwxr-xr-x because
+ // of kDirectoryMode combined with our umask.
+
+ err = mkdir(kBASToolDirPath, kDirectoryMode);
+ if (err < 0) {
+ err = errno;
+ }
+ fprintf(stderr, "mkdir '%s' %#o -> %d\n", kBASToolDirPath, kDirectoryMode, err);
+ if ( (err == 0) || (err == EEXIST) ) {
+ err = stat(kBASToolDirPath, &sb);
+ if (err < 0) {
+ err = errno;
+ }
+ }
+
+ // /Library/PrivilegedHelperTools may have come from a number of places:
+ //
+ // A. We may have just created it. In this case it will be
+ // root:admin rwxr-xr-x.
+ //
+ // B. It may have been correctly created by someone else. By definition,
+ // that makes it root:wheel rwxr-xr-x.
+ //
+ // C. It may have been created (or moved here) incorrectly (or maliciously)
+ // by someone else. In that case it will be u:g xxxxxxxxx, where u is
+ // not root, or root:g xxxxwxxwx (that is, root-owned by writeable by
+ // someone other than root).
+ //
+ // In case A, we want to correct the group. In case B, we want to do
+ // nothing. In case C, we want to fail.
+
+ if (err == 0) {
+ if ( (sb.st_uid == 0) && (sb.st_gid == 0) ) {
+ // case B -- do nothing
+ } else if ( (sb.st_uid == 0) && (sb.st_gid != 0) && ((sb.st_mode & ALLPERMS) == kDirectoryMode) ) {
+ // case A -- fix the group ID
+ //
+ // This is safe because /Library is sticky and the file is owned
+ // by root, which means that only root can move it. Also, we
+ // don't have to worry about malicious files existing within the
+ // directory because its only writeable by root.
+
+ err = chown(kBASToolDirPath, -1, 0);
+ if (err < 0) {
+ err = errno;
+ }
+ fprintf(stderr, "chown -1:0 '%s' -> %d\n", kBASToolDirPath, err);
+ } else {
+ fprintf(stderr, "bogus perms on '%s' %d:%d %o\n", kBASToolDirPath, (int) sb.st_uid, (int) sb.st_gid, (int) sb.st_mode);
+ err = EPERM;
+ }
+ }
+
+ // Then create the known good copy. The ownership and permissions
+ // will be set appropriately, as described in the comments for mkdir.
+ // We don't have to worry about atomicity because this tool won't be
+ // looked at until our plist is installed.
+
+ if (err == 0) {
+ err = CopyFileOverwriting(toolSourcePath, kExecutableMode, toolDestPath);
+ }
+
+ // For the plist, our caller has created the file in /tmp and we just copy it
+ // into the correct location. This ensures that the file is complete
+ // and valid before anyone starts looking at it and will also overwrite
+ // any existing file with this new version.
+ //
+ // Since we have to read/write in the file byte by byte to make sure that
+ // the file is complete we are rolling our own 'copy'. This clearly is
+ // ignoring atomicity since we do not roll back to the state of 'what was
+ // previously there' if there is an error; rather, whatever has been
+ // written up to that point of granular failure /is/ the state of the
+ // plist file.
+
+ if (err == 0) {
+ err = CopyFileOverwriting(plistSourcePath, kFileMode, plistDestPath);
+ }
+
+ // Use launchctl to load our job. The plist file starts out disabled,
+ // so we pass "-w" to enable it permanently.
+
+ if (err == 0) {
+ err = RunLaunchCtl(false, "load", plistDestPath);
+ }
+
+ return err;
+}
+
+static int EnableCommand(
+ const char *bundleID
+)
+ // Utility function to call through to RunLaunchCtl in order to load a job
+ // given by the path contructed from the (const char *) bundleID.
+{
+ int err;
+ char plistPath[PATH_MAX];
+
+ // Pre-condition.
+ assert(bundleID != NULL);
+
+ (void) snprintf(plistPath, sizeof(plistPath), kBASPlistPathFormat, bundleID);
+ err = RunLaunchCtl(false, "load", plistPath);
+
+ return err;
+}
+
+int main(int argc, char **argv)
+{
+ int err;
+
+ // Print our PID so that the app can avoid creating zombies.
+
+ fprintf(stdout, kBASAntiZombiePIDToken1 "%ld" kBASAntiZombiePIDToken2 "\n", (long) getpid());
+ fflush(stdout);
+
+ // On the client side, AEWP only gives a handle to stdout, so we dup stdout
+ // downto stderr for the rest of this tool. This ensures that all our output
+ // makes it to the client.
+
+ err = dup2(STDOUT_FILENO, STDERR_FILENO);
+ if (err < 0) {
+ err = errno;
+ } else {
+ err = 0;
+ }
+
+ // Set up the standard umask. The goal here is to be robust in the
+ // face of common environmental changes, not to resist a malicious attack.
+ // Also sync the RUID to the 0 because launchctl keys off the RUID (at least
+ // on 10.4.x).
+
+ if (err == 0) {
+ (void) umask(S_IWGRP | S_IWOTH);
+
+ err = setuid(0);
+ if (err < 0) {
+ fprintf(stderr, "setuid\n");
+ err = EINVAL;
+ }
+ }
+
+ if ( (err == 0) && (argc < 2) ) {
+ fprintf(stderr, "usage\n");
+ err = EINVAL;
+ }
+
+ // The first argument is the command. Switch off that and extract the
+ // remaining arguments and pass them to our command routines.
+
+ if (err == 0) {
+ if ( strcmp(argv[1], kBASInstallToolInstallCommand) == 0 ) {
+ if (argc == 5) {
+ err = InstallCommand(argv[2], argv[3], argv[4]);
+ } else {
+ fprintf(stderr, "usage3\n");
+ err = EINVAL;
+ }
+ } else if ( strcmp(argv[1], kBASInstallToolEnableCommand) == 0 ) {
+ if (argc == 3) {
+ err = EnableCommand(argv[2]);
+ } else {
+ fprintf(stderr, "usage4\n");
+ err = EINVAL;
+ }
+ } else {
+ fprintf(stderr, "usage2\n");
+ err = EINVAL;
+ }
+ }
+
+ // Write "oK" to stdout and quit. The presence of the "oK" on the last
+ // line of output is used by the calling code to detect success.
+
+ if (err == 0) {
+ fprintf(stderr, kBASInstallToolSuccess "\n");
+ } else {
+ fprintf(stderr, kBASInstallToolFailure "\n", err);
+ }
+
+ return (err == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
Property changes on: branches/gsoc08-framework/MacPorts_Framework/BetterAuthorizationSampleLibInstallTool.c
___________________________________________________________________
Name: svn:executable
+ *
Added: branches/gsoc08-framework/MacPorts_Framework/MPHelperCommon.c
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/MPHelperCommon.c (rev 0)
+++ branches/gsoc08-framework/MacPorts_Framework/MPHelperCommon.c 2008-08-01 14:07:22 UTC (rev 38876)
@@ -0,0 +1,36 @@
+/*
+ * MPHelperCommon.c
+ * MacPorts.Framework
+ *
+ * Created by George Armah on 7/31/08.
+ * Copyright 2008 Lafayette College. All rights reserved.
+ *
+ */
+
+#include "MPHelperCommon.h"
+
+
+/*
+ IMPORTANT
+ ---------
+ This array must be exactly parallel to the kMPHelperCommandProcs array
+ in "MPHelperTool.m".
+ */
+
+const BASCommandSpec kMPHelperCommandSet[] = {
+
+ { kMPHelperEvaluateTclCommand, //commandName
+ kMPHelperEvaluateTclRightsName, //rightName
+ "default", //rightDefaultRule -- by default, you have to have admin credentials
+ NULL, //rightDescriptionKey
+ NULL // userData ... I might use this to pass the NSError object later on
+ },
+
+ { NULL, //the array is null terminated
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ }
+
+};
Added: branches/gsoc08-framework/MacPorts_Framework/MPHelperCommon.h
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/MPHelperCommon.h (rev 0)
+++ branches/gsoc08-framework/MacPorts_Framework/MPHelperCommon.h 2008-08-01 14:07:22 UTC (rev 38876)
@@ -0,0 +1,42 @@
+/*
+ * MPHelperCommon.h
+ * MacPorts.Framework
+ *
+ * Created by George Armah on 7/31/08.
+ * Copyright 2008 Lafayette College. All rights reserved.
+ *
+ */
+
+#ifndef _MPHELPERCOMMON_H
+#define _MPHELPERCOMMON_H
+
+#include "BetterAuthorizationSampleLib.h"
+
+//We need only one command for this Tool
+
+#define kMPHelperEvaluateTclCommand "EvaluateTcl"
+
+ // authorization right name
+
+ #define kMPHelperEvaluateTclRightsName "com.MacPorts.MacPortsFramework.EvaluateTcl"
+
+ // request keys
+ // Should I put the NSError object in the request dictionary ?
+ // I'll try that for now and see how it goes
+
+ //String to be Evlauted
+ #define kTclStringToBeEvaluated "TclString" //CFString
+
+
+ //response keys
+ #define kTclStringEvaluationResult "TclStringEvaluationResult" //CFString
+
+ //Actually hold off doing errors for now
+ //NSError object we are passing
+ #define kNSErrorString "NSErrorString" //Am I allowed to pass in an NSError object?
+ //Lets make it a string for now
+
+
+extern const BASCommandSpec kMPHelperCommandSet[];
+
+#endif
\ No newline at end of file
Property changes on: branches/gsoc08-framework/MacPorts_Framework/MPHelperCommon.h
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: branches/gsoc08-framework/MacPorts_Framework/MPHelperTool.m
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/MPHelperTool.m 2008-08-01 12:15:55 UTC (rev 38875)
+++ branches/gsoc08-framework/MacPorts_Framework/MPHelperTool.m 2008-08-01 14:07:22 UTC (rev 38876)
@@ -6,193 +6,104 @@
// Copyright 2008 Lafayette College. All rights reserved.
//
+//#include <netinet/in.h>
+#include <stdio.h>
+//#include <sys/socket.h>
+#include <unistd.h>
+
+#include <CoreServices/CoreServices.h>
+#include "BetterAuthorizationSampleLib.h"
+#include "MPHelperCommon.h"
+
#import <Cocoa/Cocoa.h>
-#import <Foundation/Foundation.h>
#import <Security/Security.h>
#import "MPInterpreter.h"
-#include <stdlib.h>
-#include <sys/stat.h>
+static OSStatus DoEvaluateTclString (
+ AuthorizationRef auth,
+ const void * userData,
+ CFDictionaryRef request,
+ CFMutableDictionaryRef response,
+ aslclient asl,
+ aslmsg aslMsg
+)
-// /////////////////////////////////////////////////////////////////////////////
-// exitCleanly
-//
-// Exits the program with the correct status and releases the autorelease pool.
-void exitCleanly(int code, NSAutoreleasePool *pool)
{
- [pool release];
- exit(code);
+ OSStatus retval = noErr;
+
+ //Pre conditions
+ assert(auth != NULL);
+ //userData may be NULL
+ assert(request != NULL);
+ assert(response != NULL);
+ //asl may be null
+ //aslMsg may be null
+
+ //Get the NSString that was passed in the request dictionary
+ NSString * tclCmd;
+ CFStringRef cTclCmd = (CFStringRef)CFDictionaryGetValue(request, CFSTR(kTclStringToBeEvaluated));
+
+ if (cTclCmd != NULL) {
+ tclCmd = (NSString *) cTclCmd;
+ }
+ else {
+ //something went wrong ... do some error handling
+ retval = coreFoundationUnknownErr;
+ }
+
+ MPInterpreter * interp = [MPInterpreter sharedInterpreter];
+ NSError * evalError;
+ NSString * result = [interp evaluateStringAsString:tclCmd
+ error:&evalError];
+ CFStringRef cResult = (CFStringRef) result;
+
+ if( result != nil && evalError != nil) {
+ CFDictionaryAddValue(response, CFSTR(kTclStringEvaluationResult), cResult);
+ }
+ else{
+ //Try setting the user data pointer to the error
+ retval = coreFoundationUnknownErr;
+ }
+
+ return retval;
}
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Tool Infrastructure
-//This code is adapted from :
-// http://forums.macrumors.com/showthread.php?t=508394
+/*
+ IMPORTANT
+ ---------
+ This array must be exactly parallel to the kMPHelperCommandSet array
+ in "MPHelperCommon.c".
+ */
-//I will first try implementing this without any Authorization services.
-//I just want a helper tool whose user id is set to 0 and can self repair itself
-//I will be using message passing for IPC rather than piping or anything else
-//like that. Check on IRC if there are any dangers in doing that.
+static const BASCommandProc kMPHelperCommandProcs[] = {
+ DoEvaluateTclString,
+ NULL
+};
-//Usage
-// ./MPHelperTool --self-repair srOptions --rec-count recOptions interpCmd
-// So argv is of size 6 ... no more no less
+//Should I just do stuff in main and use the above method to
+//just retrieve the string to be evaluated as a tcl command?
-// srOptions is a C string with value of "yes" or "no" to tell us whether or not
-// to run self repair
-
-//recOptions is a number telling us how many times we have been called recursively
-
-//interpCmd is a the
-
int main(int argc, char const * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
- setuid(0);
- NSLog(@"UID is %i", geteuid());
- //Check for right number of args
- if (argc != 6) {
- exitCleanly(TCL_ERROR , pool);
- }
+ // Go directly into BetterAuthorizationSampleLib code.
- //The second thing to check for is recOptions ... This method should be called
- //recursively at most once. Its a bit weird but I think from the code above,
- //this tool repairs itself AND THEN proceeds to execute the required command all in
- //one process. This means the number of recursive calls should not exceed one.
- //If we fail to self repair at the first try, we should just exit.
-
-
- int recOptions = [[NSString stringWithCString:argv[4] encoding:NSUTF8StringEncoding] intValue];
- if (recOptions > 1) {
- exitCleanly(TCL_ERROR, pool);
- }
- else {
- ++recOptions;
- }
+ // IMPORTANT
+ // BASHelperToolMain doesn't clean up after itself, so once it returns
+ // we must quit.
+ BASHelperToolMain(kMPHelperCommandSet, kMPHelperCommandProcs);
- MPInterpreter * interp = [MPInterpreter sharedInterpreter];
- NSString * interpCmd = [NSString stringWithCString:argv[5] encoding:NSUTF8StringEncoding];
- NSString * _path_to_self = [[NSBundle mainBundle] pathForResource:@"MPHelperTool"
- ofType:nil];
- //OSStatus status;
- BOOL authenticatedMode = YES;
- //AuthorizationRef auth;
- //AuthorizationExternalForm extAuth;
+ [pool release];
- //This memory pointer should be valid till _path_to_self is freed or released
- //in the autorelease pool .. in other words its save for our purposes
- //and we dont' have to worry about releasing memory
- const char * path_to_self = [_path_to_self cStringUsingEncoding:NSUTF8StringEncoding];
-
- if (!strcmp(argv[1], "--self-repair") && !strcmp(argv[2], "yes") )
- {
- NSLog(@"MacPortsFramework MPHelperTool main Self-repair. Starting");
- // We have started ourself in self-repair mode. This means that this executable is not owned by root and/or the setuid bit is not set. We need to recover that...
- struct stat st;
- int fd_tool;
-
-
- //We don't need this code for now
- /* Recover the passed in AuthorizationRef.*/
-
-
- /* Open tool exclusively, so noone can change it while we bless it */
- fd_tool = open(path_to_self, O_NONBLOCK|O_RDONLY|O_EXLOCK, 0);
-
- if (fd_tool == -1)
- {
- NSLog(@"MacPortsFramework MPHelperTool main Self-Repair. Exclusive open while repairing tool failed: %d.",errno);
- exitCleanly(-1,pool);
- }
-
- if (fstat(fd_tool, &st))
- {
- NSLog(@"MacPortsFramework MPHelperTool main Self-Repair. fstat failed");
- exitCleanly(-1,pool);
- }
-
- if (st.st_uid != 0)
- {
- fchown(fd_tool, 0, st.st_gid);
- }
-
-
- /* Disable group and world writability and make setuid root. */
- fchmod(fd_tool, (st.st_mode & (~(S_IWGRP|S_IWOTH))) | S_ISUID);
-
- close(fd_tool);
-
- NSLog(@"MacPortsFramework MPHelperTool main Self-repair. Complete");
- authenticatedMode = YES;
-
-
- /*/Hopefully this works
- int result = [interp execute:_path_to_self
- withArgs:[NSArray arrayWithObjects:SELF_REPAIR, @"no", REC_COUNT,
- [NSString stringWithFormat:@"%d", recOptions], interpCmd, nil]];
-
- exitCleanly(result, pool);*/
-
-
- }
- else
- {
- //To Do
- //Add code here to receive Authorization reference from somwhere
-
- authenticatedMode = YES;
- }
-
- /* If we are not running as root we need to self-repair. But we don't want to do it more than once
- which means */
- if (authenticatedMode && geteuid() != 0)
- {
- NSLog(@"MacPortsFramework MPHelperTool main Normal-Mode. Not running as root! Starting self-repair mode.");
-
- //We run again in self repair mode. I am assuming that this new "forked" process
- // will be able to repair the binary and execute the command successfully ...
- //if it fails I guess i should return something to that effect?
- int result = [interp execute:_path_to_self
- withArgs:[NSArray arrayWithObjects:SELF_REPAIR, @"yes", REC_COUNT,
- [NSString stringWithFormat:@"%d", recOptions], interpCmd, nil]];
-
-
- //Is the above method guaranteed to always complete before the
- //program execution gets here?
- exitCleanly(result, pool);
-
-
- }
-
- //Now we can finally execute the method ... whew
- if (interpCmd != nil) {
- NSError * evalError;
- NSLog(@"Executin Tcl command %@", interpCmd);
-
- NSString * result = [interp evaluateStringAsString:interpCmd
- error:&evalError];
- if(result == nil && evalError) {
- NSLog(@"Command %@ exited with Error %@", interpCmd, evalError);
- exitCleanly(TCL_ERROR,pool);
- }
- else {
- NSLog(@"Command %@ returned %@", interpCmd, result);
- exitCleanly(TCL_OK, pool);
- }
- /*while(*argv != NULL) {
- NSLog(@"Passed parameter is %@", [NSString stringWithCString:*argv]);
- ++argv;
- }*/
- }
-
- [pool release];
-
-
return 0;
}
Modified: branches/gsoc08-framework/MacPorts_Framework/MPInterpreter.h
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/MPInterpreter.h 2008-08-01 12:15:55 UTC (rev 38875)
+++ branches/gsoc08-framework/MacPorts_Framework/MPInterpreter.h 2008-08-01 14:07:22 UTC (rev 38876)
@@ -47,6 +47,7 @@
//Defining some flags for MPHelperTool
#define SELF_REPAIR @"--self-repair"
#define REC_COUNT @"--rec-count"
+#define MP_HELPER @"MPHelperTool"
#define MPPackage @"macports"
#define MPPackageVersion @"1.0"
@@ -65,10 +66,12 @@
@interface MPInterpreter : NSObject {
Tcl_Interp* _interpreter;
+ NSString * helperToolInterpCommand;
}
+
/*!
@brief Return singleton shared MPInterpreter instance
*/
@@ -170,5 +173,6 @@
- (NSString *)getVariableAsString:(NSString *)variable;
-
+//For testing helper tool
+-(NSString *)evaluateStringWithMPHelperTool:(NSString *)statement;
@end
Modified: branches/gsoc08-framework/MacPorts_Framework/MPInterpreter.m
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/MPInterpreter.m 2008-08-01 12:15:55 UTC (rev 38875)
+++ branches/gsoc08-framework/MacPorts_Framework/MPInterpreter.m 2008-08-01 14:07:22 UTC (rev 38876)
@@ -34,7 +34,17 @@
*/
#import "MPInterpreter.h"
+#include "BetterAuthorizationSampleLib.h"
+#include "MPHelperCommon.h"
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Globals
+
+static AuthorizationRef mpAuth;
+
+#pragma mark -
+
@implementation MPInterpreter
#pragma mark Notifications Code
@@ -138,7 +148,56 @@
}
#pragma mark -
+#pragma mark Authorization Code
+//Internal method for setting up Authorizatin Reference
+//Called during initialization of interpreter object
+//This is a bit weird: Since MPInterpreter is compiled twice First for the helper
+//tool and then for the Framework, we want to call this method only when it is
+//being compiled for the Framework ... hence we check that the main bundle name is
+//MacPorts.Framework before initializing authorization
+-(void) initializeAuthorization {
+ //I'll probably regret doing this later on
+ //but whatever ....
+ char * username = "armahg";
+ char * password = "200485";
+
+ OSStatus junk;
+
+ //Create Environment specific to this machine for tesitng purposes
+ AuthorizationItem envItems[2];
+ envItems[0].name = kAuthorizationEnvironmentPassword;
+ envItems[0].value = password;
+ envItems[0].valueLength = strlen(password);
+ envItems[0].flags = 0;
+
+ envItems[1].name = kAuthorizationEnvironmentUsername;
+ envItems[1].value = username;
+ envItems[1].valueLength = strlen(username);
+ envItems[1].flags = 0;
+
+ AuthorizationItemSet env = { 2 , envItems };
+
+ AuthorizationFlags envFlags;
+ envFlags = kAuthorizationFlagDefaults |
+ kAuthorizationFlagExtendRights |
+ kAuthorizationFlagPreAuthorize;
+
+
+ junk = AuthorizationCreate(NULL, &env, envFlags, &mpAuth);
+ assert(junk == noErr);
+ //assert (junk == noErr) == (mpAuth != NULL);
+
+ NSString * bundleID = [[NSBundle bundleForClass:[self class]] bundleIdentifier];
+
+ BASSetDefaultRules(mpAuth,
+ kMPHelperCommandSet,
+ (CFStringRef) bundleID,
+ NULL);
+
+}
+
+#pragma mark -
#pragma mark MPInterpreter Code
- (id) init {
@@ -185,6 +244,12 @@
Tcl_DeleteInterp(_interpreter);
}
+ //Initialize helperToolInterpCommand
+ helperToolInterpCommand = @"";
+
+ //Initialize Authorization stuff should probably check for errors ....
+ [self initializeAuthorization];
+
}
return self;
}
@@ -193,6 +258,7 @@
return _interpreter;
}
+
+ (MPInterpreter*)sharedInterpreter {
return [self sharedInterpreterWithPkgPath:MP_DEFAULT_PKG_PATH];
}
@@ -351,5 +417,55 @@
return [NSString stringWithUTF8String:Tcl_GetVar(_interpreter, [variable UTF8String], 0)];
}
-
+- (NSString *) evaluateStringWithMPHelperTool:(NSString *) statement {
+ OSStatus err;
+ BASFailCode failCode;
+ NSString * bundleID;
+ NSDictionary * request;
+ CFDictionaryRef response;
+
+ response = NULL;
+
+ request = [NSDictionary dictionaryWithObjectsAndKeys:
+ @kMPHelperEvaluateTclCommand, @kBASCommandKey,
+ statement, @kTclStringToBeEvaluated, nil];
+ assert(request != NULL);
+
+ bundleID = [[NSBundle bundleForClass:[self class]] bundleIdentifier];
+ NSLog(@" %@ ", bundleID);
+ assert(bundleID != NULL);
+
+
+ err = BASExecuteRequestInHelperTool(mpAuth,
+ kMPHelperCommandSet,
+ (CFStringRef) bundleID,
+ (CFDictionaryRef) request,
+ &response);
+
+ //Try to recover
+ if ( (err != noErr) && (err != userCanceledErr) ) {
+ failCode = BASDiagnoseFailure(mpAuth, (CFStringRef) bundleID);
+
+ err = BASFixFailure(mpAuth,
+ (CFStringRef) bundleID,
+ CFSTR("MPHelperInstallTool"),
+ CFSTR("MPHelperTool"),
+ failCode);
+
+ if (err == noErr) {
+ err = BASExecuteRequestInHelperTool(mpAuth,
+ kMPHelperCommandSet,
+ (CFStringRef) bundleID,
+ (CFDictionaryRef) request,
+ &response);
+ }
+ }
+ else {
+ err = userCanceledErr;
+ }
+
+ CFStringRef result = CFDictionaryGetValue(response, CFSTR(kTclStringEvaluationResult));
+
+ return (NSString *) result;
+}
@end
Modified: branches/gsoc08-framework/MacPorts_Framework/MPInterpreterTest.h
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/MPInterpreterTest.h 2008-08-01 12:15:55 UTC (rev 38875)
+++ branches/gsoc08-framework/MacPorts_Framework/MPInterpreterTest.h 2008-08-01 14:07:22 UTC (rev 38876)
@@ -43,7 +43,7 @@
- (void)testInitialization;
- (void)testGetVariableAsArray;
-//- (void)testMPHelperTool;
+- (void)testMPHelperTool;
//- (void)testMutableDictionaryFromTclListAsString;
//- (void)testEvaluateStringAsString;
Modified: branches/gsoc08-framework/MacPorts_Framework/MPInterpreterTest.m
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/MPInterpreterTest.m 2008-08-01 12:15:55 UTC (rev 38875)
+++ branches/gsoc08-framework/MacPorts_Framework/MPInterpreterTest.m 2008-08-01 14:07:22 UTC (rev 38876)
@@ -58,14 +58,11 @@
}
-/*
+
- (void)testMPHelperTool {
- //Interesting ... we'll see who MPInterpreter belongs to
- //at the time of execution MPHelperTool ... or MacPorts.Framework
- [interp execute:[[NSBundle bundleForClass:MPInterpreter] pathForResource:@"MPHelperTool" ofType:nil]
- withArgs:[NSArray arrayWithObjects:@"--sel-repair", @"no", @"--rec-count",
- @"0", @"mportselfupdate", nil]];
+ //Here goes nothing
+ [interp evaluateStringWithMPHelperTool:@"mportsync"];
}
/*
Modified: branches/gsoc08-framework/MacPorts_Framework/MPMacPortsTest.h
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/MPMacPortsTest.h 2008-08-01 12:15:55 UTC (rev 38875)
+++ branches/gsoc08-framework/MacPorts_Framework/MPMacPortsTest.h 2008-08-01 14:07:22 UTC (rev 38876)
@@ -49,9 +49,9 @@
-(void) testPathToPortIndex;
//-(void) testDepends;
//-(void) testSearch;
--(void) testSync;
+//-(void) testSync;
-(void) testVersion;
//-(void) testInstall;
--(void) testMPHelperTool;
+//-(void) testMPHelperTool;
@end
Modified: branches/gsoc08-framework/MacPorts_Framework/MPMacPortsTest.m
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/MPMacPortsTest.m 2008-08-01 12:15:55 UTC (rev 38875)
+++ branches/gsoc08-framework/MacPorts_Framework/MPMacPortsTest.m 2008-08-01 14:07:22 UTC (rev 38876)
@@ -74,7 +74,7 @@
STAssertNotNil(searchResults, @"This dictionary should have at least %d key value pairs", [searchResults count]);
}
-
+/*
-(void) testSync {
NSError * syncError = nil;
[testPort sync:&syncError];
@@ -89,7 +89,7 @@
}
-/*
+
-(void) testSelfupdate {
//The only way to test this that I know of is to listen for the posted notifications
//and take actions as appropriate
@@ -104,22 +104,6 @@
}
--(void) testMPHelperTool {
- NSLog(@"Testing MPHelperTool");
- NSTask *task = [[NSTask alloc] init];
- [task setLaunchPath:[[NSBundle bundleForClass:[MPMacPorts class]]
- pathForResource:@"MPHelperTool"
- ofType:nil]];
-
- NSArray * args = [NSArray arrayWithObjects:SELF_REPAIR, @"no",
- REC_COUNT, @"0", @"return [macports::version]", nil];
- [task setArguments:args];
-
- [task launch];
-
- [task release];
-
-}
/*
Modified: branches/gsoc08-framework/MacPorts_Framework/MacPorts.Framework.xcodeproj/project.pbxproj
===================================================================
--- branches/gsoc08-framework/MacPorts_Framework/MacPorts.Framework.xcodeproj/project.pbxproj 2008-08-01 12:15:55 UTC (rev 38875)
+++ branches/gsoc08-framework/MacPorts_Framework/MacPorts.Framework.xcodeproj/project.pbxproj 2008-08-01 14:07:22 UTC (rev 38876)
@@ -44,19 +44,27 @@
6E49F37B0DFFAB0B0030C3AF /* MPInterpreterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 489DD92E0C94674B00595506 /* MPInterpreterTest.m */; };
6E49F37F0DFFAFF80030C3AF /* MacPorts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* MacPorts.framework */; };
6EA294590E080DEB00902D12 /* MPMacPortsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E88D1CB0DF4B90B00684E9F /* MPMacPortsTest.m */; };
+ 6EB6F7C40E432A840057962C /* MPHelperTool in CopyFiles */ = {isa = PBXBuildFile; fileRef = 6ED12A4A0E3E552F0026773D /* MPHelperTool */; };
+ 6EC260730E426FC80013BC48 /* BetterAuthorizationSampleLib.c in Sources */ = {isa = PBXBuildFile; fileRef = 6EC260700E426FC80013BC48 /* BetterAuthorizationSampleLib.c */; };
+ 6EC260740E426FC80013BC48 /* BetterAuthorizationSampleLib.h in Headers */ = {isa = PBXBuildFile; fileRef = 6EC260710E426FC80013BC48 /* BetterAuthorizationSampleLib.h */; };
+ 6EC260760E426FC80013BC48 /* BetterAuthorizationSampleLib.c in Sources */ = {isa = PBXBuildFile; fileRef = 6EC260700E426FC80013BC48 /* BetterAuthorizationSampleLib.c */; };
+ 6EC260770E426FC80013BC48 /* BetterAuthorizationSampleLib.h in Headers */ = {isa = PBXBuildFile; fileRef = 6EC260710E426FC80013BC48 /* BetterAuthorizationSampleLib.h */; };
+ 6EC260970E4272D20013BC48 /* MPHelperCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = 6EC260950E4272D20013BC48 /* MPHelperCommon.h */; };
+ 6EC260980E4272D20013BC48 /* MPHelperCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = 6EC260960E4272D20013BC48 /* MPHelperCommon.c */; };
+ 6EC260990E4272D20013BC48 /* MPHelperCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = 6EC260950E4272D20013BC48 /* MPHelperCommon.h */; };
+ 6EC2609A0E4272D20013BC48 /* MPHelperCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = 6EC260960E4272D20013BC48 /* MPHelperCommon.c */; };
+ 6EC2609B0E4273920013BC48 /* BetterAuthorizationSampleLibInstallTool.c in Sources */ = {isa = PBXBuildFile; fileRef = 6EC260720E426FC80013BC48 /* BetterAuthorizationSampleLibInstallTool.c */; };
+ 6EC2609E0E42950C0013BC48 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC2609D0E42950C0013BC48 /* CoreFoundation.framework */; };
+ 6EC2614F0E42959B0013BC48 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6ED12AA60E3E7E900026773D /* Cocoa.framework */; };
6ED12A4F0E3E55660026773D /* MPHelperTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ED12A4E0E3E55660026773D /* MPHelperTool.m */; };
6ED12A550E3E55DF0026773D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6ED12A540E3E55DF0026773D /* Security.framework */; };
6ED12A560E3E55DF0026773D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6ED12A540E3E55DF0026773D /* Security.framework */; };
- 6ED12A5A0E3E56420026773D /* MPHelperTool in Resources */ = {isa = PBXBuildFile; fileRef = 6ED12A4A0E3E552F0026773D /* MPHelperTool */; };
- 6ED12AA30E3E7E7C0026773D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6ED12AA20E3E7E7C0026773D /* Foundation.framework */; };
- 6ED12AA70E3E7E900026773D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6ED12AA60E3E7E900026773D /* Cocoa.framework */; };
6ED12AC90E3E94310026773D /* MPInterpreter.m in Sources */ = {isa = PBXBuildFile; fileRef = 48906AFC0C4230B700A3ED8A /* MPInterpreter.m */; };
6ED12AD20E3E9AC30026773D /* MPInterpreter.h in Headers */ = {isa = PBXBuildFile; fileRef = 48906AFB0C4230B700A3ED8A /* MPInterpreter.h */; };
6ED12AF10E3E9E210026773D /* Tcl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EA0F56E0DFEB55E00C15082 /* Tcl.framework */; };
6ED12AFB0E3E9F980026773D /* MPNotifications.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E270D070E158CED00BAE687 /* MPNotifications.h */; };
6ED12AFC0E3E9FA60026773D /* MPNotifications.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E270D080E158CED00BAE687 /* MPNotifications.m */; };
6ED12B060E3EA9E30026773D /* init.tcl in Resources */ = {isa = PBXBuildFile; fileRef = 48E9939E0C82CEB000219DDF /* init.tcl */; };
- 6ED12C9E0E40C3320026773D /* template.c in Resources */ = {isa = PBXBuildFile; fileRef = 6ED12C9D0E40C3320026773D /* template.c */; };
8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; };
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; };
/* End PBXBuildFile section */
@@ -69,15 +77,35 @@
remoteGlobalIDString = 8DC2EF4F0486A6940098B216;
remoteInfo = MacPorts;
};
+ 6EC2608B0E4270110013BC48 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 6EC260860E426FF10013BC48;
+ remoteInfo = MPHelperInstallTool;
+ };
6ED12A520E3E55A50026773D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = 6ED12A490E3E552F0026773D /* MPHelperTool */;
+ remoteGlobalIDString = 6ED12A490E3E552F0026773D;
remoteInfo = MPHelperTool;
};
/* End PBXContainerItemProxy section */
+/* Begin PBXCopyFilesBuildPhase section */
+ 6EB6F7B00E4326F40057962C /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 6;
+ files = (
+ 6EB6F7C40E432A840057962C /* MPHelperTool in CopyFiles */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
/* Begin PBXFileReference section */
0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
089C1667FE841158C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -114,13 +142,17 @@
6E88D1CB0DF4B90B00684E9F /* MPMacPortsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMacPortsTest.m; sourceTree = "<group>"; };
6EA0F56E0DFEB55E00C15082 /* Tcl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Tcl.framework; path = System/Library/Frameworks/Tcl.framework; sourceTree = SDKROOT; };
6EAFD8B70DEC614E00E97270 /* dummycommit.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dummycommit.test; sourceTree = "<group>"; };
+ 6EC260700E426FC80013BC48 /* BetterAuthorizationSampleLib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = BetterAuthorizationSampleLib.c; sourceTree = "<group>"; };
+ 6EC260710E426FC80013BC48 /* BetterAuthorizationSampleLib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BetterAuthorizationSampleLib.h; sourceTree = "<group>"; };
+ 6EC260720E426FC80013BC48 /* BetterAuthorizationSampleLibInstallTool.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = BetterAuthorizationSampleLibInstallTool.c; sourceTree = "<group>"; };
+ 6EC260870E426FF10013BC48 /* MPHelperInstallTool */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = MPHelperInstallTool; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6EC260950E4272D20013BC48 /* MPHelperCommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPHelperCommon.h; sourceTree = "<group>"; };
+ 6EC260960E4272D20013BC48 /* MPHelperCommon.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = MPHelperCommon.c; sourceTree = "<group>"; };
+ 6EC2609D0E42950C0013BC48 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
6ED12A4A0E3E552F0026773D /* MPHelperTool */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = MPHelperTool; sourceTree = BUILT_PRODUCTS_DIR; };
6ED12A4E0E3E55660026773D /* MPHelperTool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPHelperTool.m; sourceTree = "<group>"; };
6ED12A540E3E55DF0026773D /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = "<absolute>"; };
- 6ED12AA20E3E7E7C0026773D /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6ED12AA60E3E7E900026773D /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
- 6ED12C2D0E405E660026773D /* finkLauncher.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = finkLauncher.c; sourceTree = "<group>"; };
- 6ED12C9D0E40C3320026773D /* template.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = template.c; sourceTree = "<group>"; };
8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8DC2EF5B0486A6940098B216 /* MacPorts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MacPorts.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@@ -134,14 +166,21 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 6EC260850E426FF10013BC48 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
6ED12A480E3E552F0026773D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6ED12A550E3E55DF0026773D /* Security.framework in Frameworks */,
- 6ED12AA30E3E7E7C0026773D /* Foundation.framework in Frameworks */,
- 6ED12AA70E3E7E900026773D /* Cocoa.framework in Frameworks */,
6ED12AF10E3E9E210026773D /* Tcl.framework in Frameworks */,
+ 6EC2609E0E42950C0013BC48 /* CoreFoundation.framework in Frameworks */,
+ 6EC2614F0E42959B0013BC48 /* Cocoa.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -164,6 +203,7 @@
8DC2EF5B0486A6940098B216 /* MacPorts.framework */,
489DD8F40C94365F00595506 /* Test.octest */,
6ED12A4A0E3E552F0026773D /* MPHelperTool */,
+ 6EC260870E426FF10013BC48 /* MPHelperInstallTool */,
);
name = Products;
sourceTree = "<group>";
@@ -181,8 +221,8 @@
0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */,
034768DFFF38A50411DB9C8B /* Products */,
6EA0F56E0DFEB55E00C15082 /* Tcl.framework */,
- 6ED12AA20E3E7E7C0026773D /* Foundation.framework */,
6ED12AA60E3E7E900026773D /* Cocoa.framework */,
+ 6EC2609D0E42950C0013BC48 /* CoreFoundation.framework */,
);
name = "MacPorts Foundation";
sourceTree = "<group>";
@@ -290,9 +330,12 @@
6ED12A460E3E54A80026773D /* MPHelperTool */ = {
isa = PBXGroup;
children = (
+ 6EC260700E426FC80013BC48 /* BetterAuthorizationSampleLib.c */,
+ 6EC260710E426FC80013BC48 /* BetterAuthorizationSampleLib.h */,
+ 6EC260720E426FC80013BC48 /* BetterAuthorizationSampleLibInstallTool.c */,
6ED12A4E0E3E55660026773D /* MPHelperTool.m */,
- 6ED12C2D0E405E660026773D /* finkLauncher.c */,
- 6ED12C9D0E40C3320026773D /* template.c */,
+ 6EC260950E4272D20013BC48 /* MPHelperCommon.h */,
+ 6EC260960E4272D20013BC48 /* MPHelperCommon.c */,
);
name = MPHelperTool;
sourceTree = "<group>";
@@ -306,6 +349,8 @@
files = (
6ED12AFB0E3E9F980026773D /* MPNotifications.h in Headers */,
6ED12AD20E3E9AC30026773D /* MPInterpreter.h in Headers */,
+ 6EC260740E426FC80013BC48 /* BetterAuthorizationSampleLib.h in Headers */,
+ 6EC260970E4272D20013BC48 /* MPHelperCommon.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -322,6 +367,8 @@
48A866AA0CD364F700B521BC /* MPReceipt.h in Headers */,
481D04A20CDAAAFD00D4A550 /* MPMutableDictionary.h in Headers */,
6E270D090E158CED00BAE687 /* MPNotifications.h in Headers */,
+ 6EC260770E426FC80013BC48 /* BetterAuthorizationSampleLib.h in Headers */,
+ 6EC260990E4272D20013BC48 /* MPHelperCommon.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -347,6 +394,22 @@
productReference = 489DD8F40C94365F00595506 /* Test.octest */;
productType = "com.apple.product-type.bundle";
};
+ 6EC260860E426FF10013BC48 /* MPHelperInstallTool */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 6EC260940E4270420013BC48 /* Build configuration list for PBXNativeTarget "MPHelperInstallTool" */;
+ buildPhases = (
+ 6EC260840E426FF10013BC48 /* Sources */,
+ 6EC260850E426FF10013BC48 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = MPHelperInstallTool;
+ productName = MPHelperInstallTool;
+ productReference = 6EC260870E426FF10013BC48 /* MPHelperInstallTool */;
+ productType = "com.apple.product-type.tool";
+ };
6ED12A490E3E552F0026773D /* MPHelperTool */ = {
isa = PBXNativeTarget;
buildConfigurationList = 6ED12A500E3E55660026773D /* Build configuration list for PBXNativeTarget "MPHelperTool" */;
@@ -374,11 +437,13 @@
8DC2EF540486A6940098B216 /* Sources */,
8DC2EF560486A6940098B216 /* Frameworks */,
6E49F4F40E00DD520030C3AF /* ShellScript */,
+ 6EB6F7B00E4326F40057962C /* CopyFiles */,
);
buildRules = (
);
dependencies = (
6ED12A530E3E55A50026773D /* PBXTargetDependency */,
+ 6EC2608C0E4270110013BC48 /* PBXTargetDependency */,
);
name = MacPorts;
productInstallPath = "$(HOME)/Library/Frameworks";
@@ -402,6 +467,7 @@
projectDirPath = "";
projectRoot = "";
targets = (
+ 6EC260860E426FF10013BC48 /* MPHelperInstallTool */,
6ED12A490E3E552F0026773D /* MPHelperTool */,
8DC2EF4F0486A6940098B216 /* MacPorts */,
489DD8F30C94365F00595506 /* Test */,
@@ -423,7 +489,6 @@
buildActionMask = 2147483647;
files = (
6ED12B060E3EA9E30026773D /* init.tcl in Resources */,
- 6ED12C9E0E40C3320026773D /* template.c in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -431,7 +496,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 6ED12A5A0E3E56420026773D /* MPHelperTool in Resources */,
8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */,
48E9939F0C82CEB000219DDF /* init.tcl in Resources */,
6E44A00D0E2DAD66007DE8EC /* ToDo.txt in Resources */,
@@ -492,6 +556,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 6EC260840E426FF10013BC48 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6EC2609B0E4273920013BC48 /* BetterAuthorizationSampleLibInstallTool.c in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
6ED12A470E3E552F0026773D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -499,6 +571,8 @@
6ED12AC90E3E94310026773D /* MPInterpreter.m in Sources */,
6ED12AFC0E3E9FA60026773D /* MPNotifications.m in Sources */,
6ED12A4F0E3E55660026773D /* MPHelperTool.m in Sources */,
+ 6EC260730E426FC80013BC48 /* BetterAuthorizationSampleLib.c in Sources */,
+ 6EC260980E4272D20013BC48 /* MPHelperCommon.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -514,6 +588,8 @@
481D04A30CDAAAFD00D4A550 /* MPMutableDictionary.m in Sources */,
4825ECC40CE61468006B0385 /* MPRegistry.m in Sources */,
6E270D0A0E158CED00BAE687 /* MPNotifications.m in Sources */,
+ 6EC260760E426FC80013BC48 /* BetterAuthorizationSampleLib.c in Sources */,
+ 6EC2609A0E4272D20013BC48 /* MPHelperCommon.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -525,6 +601,11 @@
target = 8DC2EF4F0486A6940098B216 /* MacPorts */;
targetProxy = 6E1AE8460E232D0700F6D7BC /* PBXContainerItemProxy */;
};
+ 6EC2608C0E4270110013BC48 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 6EC260860E426FF10013BC48 /* MPHelperInstallTool */;
+ targetProxy = 6EC2608B0E4270110013BC48 /* PBXContainerItemProxy */;
+ };
6ED12A530E3E55A50026773D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 6ED12A490E3E552F0026773D /* MPHelperTool */;
@@ -689,6 +770,36 @@
};
name = Release;
};
+ 6EC260890E426FF30013BC48 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ COPY_PHASE_STRIP = NO;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_FIX_AND_CONTINUE = YES;
+ GCC_MODEL_TUNING = G5;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ INSTALL_PATH = /usr/local/bin;
+ PREBINDING = NO;
+ PRODUCT_NAME = MPHelperInstallTool;
+ };
+ name = Debug;
+ };
+ 6EC2608A0E426FF30013BC48 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ COPY_PHASE_STRIP = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_ENABLE_FIX_AND_CONTINUE = NO;
+ GCC_MODEL_TUNING = G5;
+ INSTALL_PATH = /usr/local/bin;
+ PREBINDING = NO;
+ PRODUCT_NAME = MPHelperInstallTool;
+ ZERO_LINK = NO;
+ };
+ name = Release;
+ };
6ED12A4C0E3E55300026773D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -770,6 +881,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 6EC260940E4270420013BC48 /* Build configuration list for PBXNativeTarget "MPHelperInstallTool" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6EC260890E426FF30013BC48 /* Debug */,
+ 6EC2608A0E426FF30013BC48 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
6ED12A500E3E55660026773D /* Build configuration list for PBXNativeTarget "MPHelperTool" */ = {
isa = XCConfigurationList;
buildConfigurations = (
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/macports-changes/attachments/20080801/d7354d97/attachment-0001.html
More information about the macports-changes
mailing list