2 File: BetterAuthorizationSampleLib.c
4 Contains: Implementation of reusable code for privileged helper tools.
8 Copyright: Copyright (c) 2007 Apple Inc. All Rights Reserved.
10 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple, Inc.
11 ("Apple") in consideration of your agreement to the following terms, and your
12 use, installation, modification or redistribution of this Apple software
13 constitutes acceptance of these terms. If you do not agree with these terms,
14 please do not use, install, modify or redistribute this Apple software.
16 In consideration of your agreement to abide by the following terms, and subject
17 to these terms, Apple grants you a personal, non-exclusive license, under Apple's
18 copyrights in this original Apple software (the "Apple Software"), to use,
19 reproduce, modify and redistribute the Apple Software, with or without
20 modifications, in source and/or binary forms; provided that if you redistribute
21 the Apple Software in its entirety and without modifications, you must retain
22 this notice and the following text and disclaimers in all such redistributions of
23 the Apple Software. Neither the name, trademarks, service marks or logos of
24 Apple, Inc. may be used to endorse or promote products derived from the
25 Apple Software without specific prior written permission from Apple. Except as
26 expressly stated in this notice, no other rights or licenses, express or implied,
27 are granted by Apple herein, including but not limited to any patent rights that
28 may be infringed by your derivative works or by other works in which the Apple
29 Software may be incorporated.
31 The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
32 WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
33 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
34 PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
35 COMBINATION WITH YOUR PRODUCTS.
37 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
38 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
39 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40 ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
41 OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
42 (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
43 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47 // Define BAS_PRIVATE so that we pick up our private definitions from
48 // "BetterAuthorizationSampleLib.h".
52 #include "BetterAuthorizationSampleLib.h"
57 #include <sys/event.h>
60 #include <sys/socket.h>
62 // At runtime BAS only requires CoreFoundation. However, at build time we need
63 // CoreServices for the various OSStatus error codes in "MacErrors.h". Thus, by default,
64 // we include CoreServices at build time. However, you can flip this switch to check
65 // that you're not accidentally using any other CoreServices things.
68 #include <CoreServices/CoreServices.h>
70 #warning Do not ship this way!
71 #include <CoreFoundation/CoreFoundation.h>
72 #include "/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/MacErrors.h"
75 //////////////////////////////////////////////////////////////////////////////////
76 #pragma mark ***** Constants
79 kIdleTimeoutInSeconds = 120, // if we get no requests in 2 minutes, we quit
80 kWatchdogTimeoutInSeconds = 65 // any given request must be completely in 65 seconds
84 // These values must be greater than 60 seconds. If a job runs for less than 60
85 // seconds, launchd will consider it to have failed.
87 // kBASMaxNumberOfKBytes has two uses:
89 // 1. When receiving a dictionary, it is used to limit the size of the incoming
90 // data. This ensures that a non-privileged client can't exhaust the
91 // address space of a privileged helper tool.
93 // 2. Because it's less than 4 GB, this limit ensures that the dictionary size
94 // can be sent as an architecture-neutral uint32_t.
96 #define kBASMaxNumberOfKBytes (1024 * 1024)
98 // A hard-wired file system path for the UNIX domain socket; %s is the placeholder
99 // for the bundle ID (in file system representation).
101 #define kBASSocketPathFormat "/var/run/%s.socket"
103 // The key used to get our describe our socket in the launchd property list file.
105 #define kLaunchDSocketDictKey "MasterSocket"
107 /////////////////////////////////////////////////////////////////
108 #pragma mark ***** Common Code
110 extern int BASOSStatusToErrno(OSStatus errNum)
111 // See comment in header.
115 #define CASE(ident) \
116 case k ## ident ## Err: \
124 retval = ESRCH; // no ENORSRC on Mac OS X, so use ESRCH
139 retval = ECANCELED; // note spelling difference
147 if ( (errNum <= kEPERMErr) && (errNum >= kENOMSGErr) ) {
148 retval = (-3200 - errNum) + 1; // OT based error
149 } else if ( (errNum >= errSecErrnoBase) && (errNum <= (errSecErrnoBase + ELAST)) ) {
150 retval = (int) errNum - errSecErrnoBase; // POSIX based error
152 retval = (int) errNum; // just return the value unmodified
159 extern OSStatus BASErrnoToOSStatus(int errNum)
160 // See comment in header.
166 } else if ( (errNum >= EPERM) && (errNum <= ELAST) ) {
167 retval = (OSStatus) errNum + errSecErrnoBase;
169 retval = (int) errNum; // just return the value unmodified
175 static Boolean BASIsBinaryPropertyListData(const void * plistBuffer, size_t plistSize)
176 // Make sure that whatever is passed into the buffer that will
177 // eventually become a plist (and then sequentially a dictionary)
178 // is NOT in binary format.
180 static const char kBASBinaryPlistWatermark[6] = "bplist";
182 assert(plistBuffer != NULL);
184 return (plistSize >= sizeof(kBASBinaryPlistWatermark))
185 && (memcmp(plistBuffer, kBASBinaryPlistWatermark, sizeof(kBASBinaryPlistWatermark)) == 0);
188 static void NormaliseOSStatusErrorCode(OSStatus *errPtr)
189 // Normalise the cancelled error code to reduce the number of checks that our clients
190 // have to do. I made this a function in case I ever want to expand this to handle
191 // more than just this one case.
193 assert(errPtr != NULL);
195 if ( (*errPtr == errAuthorizationCanceled) || (*errPtr == (errSecErrnoBase + ECANCELED)) ) {
196 *errPtr = userCanceledErr;
200 static int BASRead(int fd, void *buf, size_t bufSize, size_t *bytesRead)
201 // A wrapper around <x-man-page://2/read> that keeps reading until either
202 // bufSize bytes are read or until EOF is encountered, in which case you get
205 // If bytesRead is not NULL, *bytesRead will be set to the number
206 // of bytes successfully read. On success, this will always be equal to
207 // bufSize. On error, it indicates how much was read before the error
208 // occurred (which could be zero).
213 ssize_t bytesThisTime;
220 assert(bufSize <= kBASMaxNumberOfKBytes);
221 // bytesRead may be NULL
225 cursor = (char *) buf;
226 while ( (err == 0) && (bytesLeft != 0) ) {
227 bytesThisTime = read(fd, cursor, bytesLeft);
228 if (bytesThisTime > 0) {
229 cursor += bytesThisTime;
230 bytesLeft -= bytesThisTime;
231 } else if (bytesThisTime == 0) {
234 assert(bytesThisTime == -1);
239 err = 0; // let's loop again
243 if (bytesRead != NULL) {
244 *bytesRead = bufSize - bytesLeft;
250 static int BASWrite(int fd, const void *buf, size_t bufSize, size_t *bytesWritten)
251 // A wrapper around <x-man-page://2/write> that keeps writing until either
252 // all the data is written or an error occurs, in which case
255 // If bytesWritten is not NULL, *bytesWritten will be set to the number
256 // of bytes successfully written. On success, this will always be equal to
257 // bufSize. On error, it indicates how much was written before the error
258 // occurred (which could be zero).
263 ssize_t bytesThisTime;
270 assert(bufSize <= kBASMaxNumberOfKBytes);
271 // bytesWritten may be NULL
273 // SIGPIPE occurs when you write to pipe or socket
274 // whose other end has been closed. The default action
275 // for SIGPIPE is to terminate the process. That's
276 // probably not what you wanted. So, in the debug build,
277 // we check that you've set the signal action to SIG_IGN
278 // (ignore). Of course, you could be building a program
279 // that needs SIGPIPE to work in some special way, in
280 // which case you should define BAS_WRITE_CHECK_SIGPIPE
281 // to 0 to bypass this check.
283 #if !defined(BAS_WRITE_CHECK_SIGPIPE)
284 #define BAS_WRITE_CHECK_SIGPIPE 1
286 #if !defined(NDEBUG) && BAS_WRITE_CHECK_SIGPIPE
290 struct sigaction currentSignalState;
294 junk = fstat(fd, &sb);
297 if ( S_ISFIFO(sb.st_mode) || S_ISSOCK(sb.st_mode) ) {
298 junk = sigaction(SIGPIPE, NULL, ¤tSignalState);
301 valLen = sizeof(val);
302 junk = getsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &val, &valLen);
304 assert(valLen == sizeof(val));
306 // If you hit this assertion, you need to either disable SIGPIPE in
307 // your process or on the specific socket you're writing to. The
308 // standard code for the former is:
310 // (void) signal(SIGPIPE, SIG_IGN);
312 // You typically add this code to your main function.
314 // The standard code for the latter is:
316 // static const int kOne = 1;
317 // err = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &kOne, sizeof(kOne));
319 // You typically do this just after creating the socket.
321 assert( (currentSignalState.sa_handler == SIG_IGN) || (val == 1) );
328 cursor = (char *) buf;
329 while ( (err == 0) && (bytesLeft != 0) ) {
330 bytesThisTime = write(fd, cursor, bytesLeft);
331 if (bytesThisTime > 0) {
332 cursor += bytesThisTime;
333 bytesLeft -= bytesThisTime;
334 } else if (bytesThisTime == 0) {
338 assert(bytesThisTime == -1);
343 err = 0; // let's loop again
347 if (bytesWritten != NULL) {
348 *bytesWritten = bufSize - bytesLeft;
354 static int BASReadDictionary(int fdIn, CFDictionaryRef *dictPtr)
355 // Create a CFDictionary by reading the XML data from fdIn.
356 // It first reads the size of the XML data, then allocates a
357 // buffer for that data, then reads the data in, and finally
358 // unflattens the data into a CFDictionary.
360 // On success, the caller is responsible for releasing *dictPtr.
362 // See also the companion routine, BASWriteDictionary, below.
368 CFPropertyListRef dict;
373 assert( dictPtr != NULL);
374 assert(*dictPtr == NULL);
380 // Read the data size and allocate a buffer. Always read the length as a big-endian
381 // uint32_t, so that the app and the helper tool can be different architectures.
383 err = BASRead(fdIn, &dictSize, sizeof(dictSize), NULL);
385 dictSize = OSSwapBigToHostInt32(dictSize);
387 // According to the C language spec malloc(0) may return NULL (although the Mac OS X
388 // malloc doesn't ever do this), so we specifically check for and error out in
391 } else if (dictSize > kBASMaxNumberOfKBytes) {
392 // Abitrary limit to prevent potentially hostile client overwhelming us with data.
397 dictBuffer = malloc( (size_t) dictSize);
398 if (dictBuffer == NULL) {
403 // Read the data and unflatten.
406 err = BASRead(fdIn, dictBuffer, dictSize, NULL);
408 if ( (err == 0) && BASIsBinaryPropertyListData(dictBuffer, dictSize) ) {
409 err = BASOSStatusToErrno( coreFoundationUnknownErr );
412 dictData = CFDataCreateWithBytesNoCopy(NULL, dictBuffer, dictSize, kCFAllocatorNull);
413 if (dictData == NULL) {
414 err = BASOSStatusToErrno( coreFoundationUnknownErr );
418 dict = CFPropertyListCreateFromXMLData(NULL, dictData, kCFPropertyListImmutable, NULL);
420 err = BASOSStatusToErrno( coreFoundationUnknownErr );
423 if ( (err == 0) && (CFGetTypeID(dict) != CFDictionaryGetTypeID()) ) {
424 err = EINVAL; // only CFDictionaries need apply
436 *dictPtr = (CFDictionaryRef) dict;
438 if (dictData != NULL) {
442 assert( (err == 0) == (*dictPtr != NULL) );
447 static int BASWriteDictionary(CFDictionaryRef dict, int fdOut)
448 // Write a dictionary to a file descriptor by flattening
449 // it into XML. Send the size of the XML before sending
450 // the data so that BASReadDictionary knows how much to
453 // See also the companion routine, BASReadDictionary, above.
461 assert(dict != NULL);
466 // Get the dictionary as XML data.
468 dictData = CFPropertyListCreateXMLData(NULL, dict);
469 if (dictData == NULL) {
470 err = BASOSStatusToErrno( coreFoundationUnknownErr );
473 // Send the length, then send the data. Always send the length as a big-endian
474 // uint32_t, so that the app and the helper tool can be different architectures.
476 // The MoreAuthSample version of this code erroneously assumed that CFDataGetBytePtr
477 // can fail and thus allocated an extra buffer to copy the data into. In reality,
478 // CFDataGetBytePtr can't fail, so this version of the code doesn't do the unnecessary
481 if ( (err == 0) && (CFDataGetLength(dictData) > kBASMaxNumberOfKBytes) ) {
485 dictSize = OSSwapHostToBigInt32( CFDataGetLength(dictData) );
486 err = BASWrite(fdOut, &dictSize, sizeof(dictSize), NULL);
489 err = BASWrite(fdOut, CFDataGetBytePtr(dictData), CFDataGetLength(dictData), NULL);
492 if (dictData != NULL) {
499 // When we pass a descriptor, we have to pass at least one byte
500 // of data along with it, otherwise the recvmsg call will not
501 // block if the descriptor hasn't been written to the other end
502 // of the socket yet.
504 static const char kDummyData = 'D';
506 // Due to a kernel bug in Mac OS X 10.4.x and earlier <rdar://problem/4650646>,
507 // you will run into problems if you write data to a socket while a process is
508 // trying to receive a descriptor from that socket. A common symptom of this
509 // problem is that, if you write two descriptors back-to-back, the second one
512 // To avoid this problem, we explicitly ACK all descriptor transfers.
513 // After writing a descriptor, the sender reads an ACK byte from the socket.
514 // After reading a descriptor, the receiver sends an ACK byte (kACKData)
515 // to unblock the sender.
517 static const char kACKData = 'A';
519 static int BASReadDescriptor(int fd, int *fdRead)
520 // Read a descriptor from fd and place it in *fdRead.
522 // On success, the caller is responsible for closing *fdRead.
524 // See the associated BASWriteDescriptor, below.
535 ssize_t bytesReceived;
540 assert( fdRead != NULL);
541 assert(*fdRead == -1);
543 iov.iov_base = (char *) &dummyData;
544 iov.iov_len = sizeof(dummyData);
550 msg.msg_control = (caddr_t) &control;
551 msg.msg_controllen = sizeof(control);
552 msg.msg_flags = MSG_WAITALL;
555 bytesReceived = recvmsg(fd, &msg, 0);
556 if (bytesReceived == sizeof(dummyData)) {
557 if ( (dummyData != kDummyData)
558 || (msg.msg_flags != 0)
559 || (msg.msg_control == NULL)
560 || (msg.msg_controllen != sizeof(control))
561 || (control.hdr.cmsg_len != sizeof(control))
562 || (control.hdr.cmsg_level != SOL_SOCKET)
563 || (control.hdr.cmsg_type != SCM_RIGHTS)
564 || (control.fd < 0) ) {
567 *fdRead = control.fd;
570 } else if (bytesReceived == 0) {
573 assert(bytesReceived == -1);
578 } while (err == EINTR);
580 // Send the ACK. If that fails, we have to act like we never got the
581 // descriptor in our to maintain our post condition.
584 err = BASWrite(fd, &kACKData, sizeof(kACKData), NULL);
586 junk = close(*fdRead);
592 assert( (err == 0) == (*fdRead >= 0) );
597 static int BASWriteDescriptor(int fd, int fdToWrite)
598 // Write the descriptor fdToWrite to fd.
600 // See the associated BASReadDescriptor, above.
615 assert(fdToWrite >= 0);
617 control.hdr.cmsg_len = sizeof(control);
618 control.hdr.cmsg_level = SOL_SOCKET;
619 control.hdr.cmsg_type = SCM_RIGHTS;
620 control.fd = fdToWrite;
622 iov.iov_base = (char *) &kDummyData;
623 iov.iov_len = sizeof(kDummyData);
629 msg.msg_control = (caddr_t) &control;
630 msg.msg_controllen = control.hdr.cmsg_len;
633 bytesSent = sendmsg(fd, &msg, 0);
634 if (bytesSent == sizeof(kDummyData)) {
637 assert(bytesSent == -1);
642 } while (err == EINTR);
644 // After writing the descriptor, try to read an ACK back from the
645 // recipient. If that fails, or we get the wrong ACK, we've failed.
648 err = BASRead(fd, &ack, sizeof(ack), NULL);
649 if ( (err == 0) && (ack != kACKData) ) {
657 extern void BASCloseDescriptorArray(
660 // See comment in header.
666 // I decided to allow descArray to be NULL because it makes it
667 // easier to call this routine using the code.
669 // BASCloseDescriptorArray((CFArrayRef) CFDictionaryGetValue(response, CFSTR(kBASDescriptorArrayKey)));
671 if (descArray != NULL) {
672 if (CFGetTypeID(descArray) == CFArrayGetTypeID()) {
673 descCount = CFArrayGetCount(descArray);
675 for (descIndex = 0; descIndex < descCount; descIndex++) {
676 CFNumberRef thisDescNum;
679 thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(descArray, descIndex);
680 if ( (thisDescNum == NULL)
681 || (CFGetTypeID(thisDescNum) != CFNumberGetTypeID())
682 || ! CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc) ) {
685 assert(thisDesc >= 0);
686 junk = close(thisDesc);
696 static int BASReadDictioanaryTranslatingDescriptors(int fd, CFDictionaryRef *dictPtr)
697 // Reads a dictionary and its associated descriptors (if any) from fd,
698 // putting the dictionary (modified to include the translated descriptor
699 // numbers) in *dictPtr.
701 // On success, the caller is responsible for releasing *dictPtr and for
702 // closing any descriptors it references (BASCloseDescriptorArray makes
703 // the second part easy).
707 CFDictionaryRef dict;
708 CFArrayRef incomingDescs;
713 assert( dictPtr != NULL);
714 assert(*dictPtr == NULL);
718 // Read the dictionary.
720 err = BASReadDictionary(fd, &dict);
722 // Now read the descriptors, if any.
725 incomingDescs = (CFArrayRef) CFDictionaryGetValue(dict, CFSTR(kBASDescriptorArrayKey));
726 if (incomingDescs == NULL) {
727 // No descriptors. Not much to do. Just use dict as the response,
728 // NULLing it out so that we don't release it at the end.
733 CFMutableArrayRef translatedDescs;
734 CFMutableDictionaryRef mutableDict;
738 // We have descriptors, so there's lots of stuff to do. Have to
739 // receive each of the descriptors assemble them into the
740 // translatedDesc array, then create a mutable dictionary based
741 // on response (mutableDict) and replace the
742 // kBASDescriptorArrayKey with translatedDesc.
744 translatedDescs = NULL;
747 // Start by checking incomingDescs.
749 if ( CFGetTypeID(incomingDescs) != CFArrayGetTypeID() ) {
753 // Create our output data.
756 translatedDescs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
757 if (translatedDescs == NULL) {
758 err = coreFoundationUnknownErr;
762 mutableDict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
763 if (mutableDict == NULL) {
764 err = BASOSStatusToErrno( coreFoundationUnknownErr );
768 // Now read each incoming descriptor, appending the results
769 // to translatedDescs as we go. By keeping our working results
770 // in translatedDescs, we make sure that we can clean up if
774 descCount = CFArrayGetCount(incomingDescs);
776 // We don't actually depend on the descriptor values in the
777 // response (that is, the elements of incomingDescs), because
778 // they only make sense it the context of the sending process.
779 // All we really care about is the number of elements, which
780 // tells us how many times to go through this loop. However,
781 // just to be paranoid, in the debug build I check that the
782 // incoming array is well formed.
785 for (descIndex = 0; descIndex < descCount; descIndex++) {
787 CFNumberRef thisDescNum;
789 thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(incomingDescs, descIndex);
790 assert(thisDescNum != NULL);
791 assert(CFGetTypeID(thisDescNum) == CFNumberGetTypeID());
792 assert(CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc));
793 assert(thisDesc >= 0);
797 // Here's the real work. For descCount times, read a descriptor
798 // from fd, wrap it in a CFNumber, and append it to translatedDescs.
799 // Note that we have to be very careful not to leak a descriptor
800 // if we get an error here.
802 for (descIndex = 0; descIndex < descCount; descIndex++) {
804 CFNumberRef thisDescNum;
809 err = BASReadDescriptor(fd, &thisDesc);
811 thisDescNum = CFNumberCreate(NULL, kCFNumberIntType, &thisDesc);
812 if (thisDescNum == NULL) {
813 err = BASOSStatusToErrno( coreFoundationUnknownErr );
817 CFArrayAppendValue(translatedDescs, thisDescNum);
818 // The descriptor is now stashed in translatedDescs,
819 // so this iteration of the loop is no longer responsible
824 if (thisDescNum != NULL) {
825 CFRelease(thisDescNum);
827 if (thisDesc != -1) {
828 junk = close(thisDesc);
838 // Clean up and establish output parameters.
841 CFDictionarySetValue(mutableDict, CFSTR(kBASDescriptorArrayKey), translatedDescs);
842 *dictPtr = mutableDict;
844 BASCloseDescriptorArray(translatedDescs);
845 if (mutableDict != NULL) {
846 CFRelease(mutableDict);
849 if (translatedDescs != NULL) {
850 CFRelease(translatedDescs);
859 assert( (err == 0) == (*dictPtr != NULL) );
864 static int BASWriteDictionaryAndDescriptors(CFDictionaryRef dict, int fd)
865 // Writes a dictionary and its associated descriptors to fd.
868 CFArrayRef descArray;
874 assert(dict != NULL);
877 // Write the dictionary.
879 err = BASWriteDictionary(dict, fd);
881 // Process any descriptors. The descriptors are indicated by
882 // a special key in the dictionary. If that key is present,
883 // it's a CFArray of CFNumbers that present the descriptors to be
887 descArray = (CFArrayRef) CFDictionaryGetValue(dict, CFSTR(kBASDescriptorArrayKey));
889 // We only do the following if the special key is present.
891 if (descArray != NULL) {
893 // If it's not an array, that's bad.
895 if ( CFGetTypeID(descArray) != CFArrayGetTypeID() ) {
899 // Loop over the array, getting each descriptor and writing it.
902 descCount = CFArrayGetCount(descArray);
904 for (descIndex = 0; descIndex < descCount; descIndex++) {
905 CFNumberRef thisDescNum;
908 thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(descArray, descIndex);
909 if ( (thisDescNum == NULL)
910 || (CFGetTypeID(thisDescNum) != CFNumberGetTypeID())
911 || ! CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc) ) {
915 err = BASWriteDescriptor(fd, thisDesc);
929 static OSStatus FindCommand(
930 CFDictionaryRef request,
931 const BASCommandSpec commands[],
932 size_t * commandIndexPtr
934 // FindCommand is a simple utility routine for checking that the
935 // command name within a request is valid (that is, matches one of the command
936 // names in the BASCommandSpec array).
938 // On success, *commandIndexPtr will be the index of the requested command
939 // in the commands array. On error, the value in *commandIndexPtr is undefined.
941 OSStatus retval = noErr;
942 CFStringRef commandStr;
944 UInt32 commandSize = 0;
949 assert(request != NULL);
950 assert(commands != NULL);
951 assert(commands[0].commandName != NULL); // there must be at least one command
952 assert(commandIndexPtr != NULL);
956 // Get the command as a C string. To prevent untrusted command string from
957 // trying to run us out of memory, we limit its length to 1024 UTF-16 values.
959 commandStr = CFDictionaryGetValue(request, CFSTR(kBASCommandKey));
960 if ( (commandStr == NULL) || (CFGetTypeID(commandStr) != CFStringGetTypeID()) ) {
963 commandSize = CFStringGetLength(commandStr);
964 if ( (retval == noErr) && (commandSize > 1024) ) {
967 if (retval == noErr) {
970 bufSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(commandStr), kCFStringEncodingUTF8) + 1;
971 command = malloc(bufSize);
973 if (command == NULL) {
975 } else if ( ! CFStringGetCString(commandStr, command, bufSize, kCFStringEncodingUTF8) ) {
976 retval = coreFoundationUnknownErr;
980 // Search the commands array for that command.
982 if (retval == noErr) {
984 if ( strcmp(commands[index].commandName, command) == 0 ) {
985 *commandIndexPtr = index;
989 if (commands[index].commandName == NULL) {
990 retval = BASErrnoToOSStatus(ENOENT);
1001 /////////////////////////////////////////////////////////////////
1002 #pragma mark ***** Tool Code
1007 BetterAuthorizationSampleLib's privileged helper tool server is single threaded. Thus,
1008 it's possible for a broken or malicious client to stop progress within the helper
1009 tool simply by sending the tool half a request. The single thread of execution
1010 within the tool will wait forever for the rest of the request and, while it's
1011 waiting, it won't be able to service other requests. Clearly this is not good.
1013 I contemplated a number of solutions to this problem, but eventually settled
1014 on a very simple solution. When it starts processing a request, the tool
1015 starts a watchdog timer. If the timer expires, the tool dies. The single
1016 request that the tool is blocked on will fail (because our end of the per-connection
1017 socket for that request closed when we died) and subsequent requests will
1018 relaunch the tool on demand, courtesy of launchd.
1020 I use SIGALRM to implement this functionality. As stated in our header, the
1021 BetterAuthorizationSampleLib code claims this signal and our clients are required not
1022 to use it. Also, the default disposition for SIGALRM is to quit the process,
1023 which is exactly what I want.
1026 static void EnableWatchdog(void)
1027 // Start the watchdog timer. If you don't call DisableWatchdog before the
1028 // timer expires, the process will die with a SIGALRM.
1030 (void) alarm(kWatchdogTimeoutInSeconds);
1033 static void DisableWatchdog(void)
1034 // Disable the watchdog timer.
1039 #if ! defined(NDEBUG)
1041 static bool CommandArraySizeMatchesCommandProcArraySize(
1042 const BASCommandSpec commands[],
1043 const BASCommandProc commandProcs[]
1046 size_t commandCount;
1050 while ( commands[commandCount].commandName != NULL ) {
1055 while ( commandProcs[procCount] != NULL ) {
1059 return (commandCount == procCount);
1065 On-The-'Wire' Protocol
1066 ----------------------
1067 The on-the-'wire' protocol for a BetterAuthorizationSampleLib connection (from the
1068 perspective of the client) is:
1072 send AuthorizationExternalForm (32 byte blob)
1073 send request dictionary length (4 bytes, uint32_t, big endian)
1074 send request dictionary (N bytes, flattened CFPropertyList)
1076 read response dictionary length (4 bytes, uint32_t, big endian)
1077 read response dictionary (N bytes, flattened CFPropertyList)
1078 for each descriptor in dictionary
1079 read 1 byte ('D') with attached descriptor
1085 static int HandleConnection(
1088 const BASCommandSpec commands[],
1089 const BASCommandProc commandProcs[],
1092 // This routine handles a single connection from a client. This connection, in
1093 // turn, represents a single command (request/response pair). commands is the
1094 // list of valid commands. commandProc is a callback to call to actually
1095 // execute a command. Finally, fd is the file descriptor from which the request
1096 // should be read, and to which the response should be sent.
1101 AuthorizationExternalForm extAuth;
1102 AuthorizationRef auth = NULL;
1103 CFDictionaryRef request = NULL;
1104 size_t commandIndex;
1105 CFMutableDictionaryRef response = NULL;
1106 OSStatus commandProcStatus;
1111 // aslMsg may be NULL
1112 assert(commands != NULL);
1113 assert(commands[0].commandName != NULL); // there must be at least one command
1114 assert(commandProcs != NULL);
1115 assert( CommandArraySizeMatchesCommandProcArraySize(commands, commandProcs) );
1118 // Read in the external authorization reference.
1119 retval = BASRead(fd, &extAuth, sizeof(extAuth), NULL);
1121 // Internalize external authorization reference.
1123 retval = BASOSStatusToErrno( AuthorizationCreateFromExternalForm(&extAuth, &auth) );
1126 // Read in CFDictionaryRef request (the command and its arguments).
1128 retval = BASReadDictionary(fd, &request);
1131 // Create a mutable response dictionary before calling the client.
1133 response = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1134 if (response == NULL) {
1135 retval = BASOSStatusToErrno( coreFoundationUnknownErr );
1139 // Errors that occur within this block are considered command errors, that is, they're
1140 // reported to the client in the kBASErrorKey value of the response dictionary
1141 // (that is, BASExecuteRequestInHelperTool returns noErr and valid response dictionary with
1142 // an error value in the kBASErrorKey entry of the dictionary). In contrast, other errors
1143 // are considered IPC errors and generally result in a the client getting an error status
1144 // back from BASExecuteRequestInHelperTool.
1146 // Notably a request with an unrecognised command string will return an error code
1147 // in the response, as opposed to an IPC error. This means that a client can check
1148 // whether a tool supports a particular command without triggering an IPC teardown.
1151 // Get the command name from the request dictionary and check to see whether or
1152 // not the command is valid by comparing with the BASCommandSpec array. Also,
1153 // if the command is valid, return the associated right (if any).
1155 commandProcStatus = FindCommand(request, commands, &commandIndex);
1157 // Acquire the associated right for the command. If rightName is NULL, the
1158 // commandProc is required to do its own authorization.
1160 if ( (commandProcStatus == noErr) && (commands[commandIndex].rightName != NULL) ) {
1161 AuthorizationItem item = { commands[commandIndex].rightName, 0, NULL, 0 };
1162 AuthorizationRights rights = { 1, &item };
1164 commandProcStatus = AuthorizationCopyRights(
1167 kAuthorizationEmptyEnvironment,
1168 kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed,
1173 // Call callback to execute command based on the request.
1175 if (commandProcStatus == noErr) {
1176 commandProcStatus = commandProcs[commandIndex](auth, commands[commandIndex].userData, request, response, asl, aslMsg);
1178 if (commandProcStatus == noErr) {
1179 junkInt = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Command callback succeeded");
1180 assert(junkInt == 0);
1182 junkInt = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Command callback failed: %ld", (long) commandProcStatus);
1183 assert(junkInt == 0);
1187 // If the command didn't insert its own error value, we use its function
1188 // result as the error value.
1190 if ( ! CFDictionaryContainsKey(response, CFSTR(kBASErrorKey)) ) {
1193 numRef = CFNumberCreate(NULL, kCFNumberSInt32Type, &commandProcStatus);
1194 if (numRef == NULL) {
1195 retval = BASOSStatusToErrno( coreFoundationUnknownErr );
1197 CFDictionaryAddValue(response, CFSTR(kBASErrorKey), numRef);
1203 // Write response back to the client.
1205 retval = BASWriteDictionaryAndDescriptors(response, fd);
1210 if (response != NULL) {
1211 // If there are any descriptors in response, we've now passed them off to the client,
1212 // so we can (and must) close our references to them.
1213 BASCloseDescriptorArray( CFDictionaryGetValue(response, CFSTR(kBASDescriptorArrayKey)) );
1214 CFRelease(response);
1216 if (request != NULL) {
1220 junk = AuthorizationFree(auth, kAuthorizationFlagDefaults);
1221 assert(junk == noErr);
1227 #if !defined(NDEBUG)
1229 static void WaitForDebugger(aslclient asl, aslmsg aslMsg)
1230 // You can force a debug version of the tool to stop and wait on
1231 // launch using the following Terminal command:
1233 // $ sudo launchctl stop com.example.BetterAuthorizationSample
1234 // $ sudo launchctl setenv BASWaitForDebugger 1
1240 // aslMsg may be NULL
1242 value = getenv("BASWaitForDebugger");
1243 if ( ((value != NULL) && (atoi(value) != 0)) ) {
1244 err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Waiting for debugger");
1252 static int CheckInWithLaunchd(aslclient asl, aslmsg aslMsg, const char **errStrPtr)
1253 // Checks in with launchd and gets back our listening socket.
1254 // Returns the socket as the function result (or -1 on error).
1255 // Also, on error, set *errStrPtr to a error string suitable
1256 // for logging with ASL. If the message contains a %m, which
1257 // causes ASL to log errno, errno will be set appropriately.
1260 launch_data_t checkinRequest = NULL;
1261 launch_data_t checkinResponse = NULL;
1262 launch_data_t socketsDict;
1263 launch_data_t fdArray;
1264 launch_data_t fdData;
1270 // aslMsg may be NULL
1271 assert( errStrPtr != NULL);
1272 assert(*errStrPtr == NULL);
1274 // Check in with launchd. Create a checkin request, then run it, then
1275 // check if we got an error.
1277 checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN);
1278 if (checkinRequest == NULL) {
1279 *errStrPtr = "Could not create checkin request: %m";
1282 checkinResponse = launch_msg(checkinRequest);
1283 if (checkinResponse == NULL) {
1284 *errStrPtr = "Error checking in: %m";
1287 if (launch_data_get_type(checkinResponse) == LAUNCH_DATA_ERRNO) {
1288 errno = launch_data_get_errno(checkinResponse); // set errno so %m picks it up
1289 *errStrPtr = "Checkin failed: %m";
1293 // Retrieve the dictionary of sockets entries from the job. This corresponds to the
1294 // value of the "Sockets" key in our plist file.
1296 socketsDict = launch_data_dict_lookup(checkinResponse, LAUNCH_JOBKEY_SOCKETS);
1297 if (socketsDict == NULL) {
1298 *errStrPtr = "Could not get socket dictionary from checkin response: %m";
1301 if (launch_data_get_type(socketsDict) != LAUNCH_DATA_DICTIONARY) {
1302 *errStrPtr = "Could not get socket dictionary from checkin response: Type mismatch";
1305 if (launch_data_dict_get_count(socketsDict) > 1) {
1306 err = asl_log(asl, aslMsg, ASL_LEVEL_WARNING, "Some sockets in dictionary will be ignored");
1310 // Get the dictionary value from the key "MasterSocket", as defined in the launchd
1311 // property list file.
1313 fdArray = launch_data_dict_lookup(socketsDict, kLaunchDSocketDictKey);
1314 if (fdArray == NULL) {
1315 *errStrPtr = "Could not get file descriptor array: %m";
1318 if (launch_data_get_type(fdArray) != LAUNCH_DATA_ARRAY) {
1319 *errStrPtr = "Could not get file descriptor array: Type mismatch";
1322 if (launch_data_array_get_count(fdArray) > 1) {
1323 err = asl_log(asl, aslMsg, ASL_LEVEL_WARNING, "Some sockets in array will be ignored");
1327 // Get the socket file descriptor from the array.
1329 fdData = launch_data_array_get_index(fdArray, 0);
1330 if (fdData == NULL) {
1331 *errStrPtr = "Could not get file descriptor array entry: %m";
1334 if (launch_data_get_type(fdData) != LAUNCH_DATA_FD) {
1335 *errStrPtr = "Could not get file descriptor array entry: Type mismatch";
1338 fd = launch_data_get_fd(fdData);
1341 // The following was used to debug a problem with launchd <rdar://problem/5410487>.
1342 // I'm going to leave it in, disabled, until that problem is resolved.
1345 err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Listening descriptor is %d", fd);
1350 if (checkinResponse != NULL) {
1351 launch_data_free(checkinResponse);
1353 if (checkinRequest != NULL) {
1354 launch_data_free(checkinRequest);
1360 static int SetNonBlocking(int fd, Boolean nonBlocking)
1361 // Sets the non-blocking state of fd.
1373 flags = fcntl(fd, F_GETFL);
1378 // If the current state of O_NONBLOCK doesn't match the required
1379 // state, toggle that flag and set it back.
1381 if ( (err == 0) && (((flags & O_NONBLOCK) != 0) != nonBlocking) ) {
1382 flags ^= O_NONBLOCK;
1383 err = fcntl(fd, F_SETFL, flags);
1392 extern int BASHelperToolMain(
1393 const BASCommandSpec commands[],
1394 const BASCommandProc commandProcs[]
1396 // See comment in header.
1398 const char * errStr = NULL;
1400 aslclient asl = NULL;
1401 aslmsg aslMsg = NULL;
1405 struct kevent initEvent;
1409 assert(commands != NULL);
1410 assert(commands[0].commandName != NULL); // there must be at least one command
1411 assert(commandProcs != NULL);
1412 assert( CommandArraySizeMatchesCommandProcArraySize(commands, commandProcs) );
1414 // Create a new ASL client object, and a template message for any messages that
1415 // we log. We don't care if these fail because ASL will do the right thing
1416 // if you pass it NULL (that is, nothing).
1418 asl = asl_open(NULL, "HelperTools", ASL_OPT_STDERR);
1419 assert(asl != NULL);
1421 aslMsg = asl_new(ASL_TYPE_MSG);
1422 assert(aslMsg != NULL);
1424 err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Starting up");
1427 #if !defined(NDEBUG)
1428 WaitForDebugger(asl, aslMsg);
1431 // Set up the signal handlers we are interested in.
1433 // o SIGTERM -- launchd sends us this when it wants us to quit. We don't
1434 // actually need to set up a handler because the default behaviour (process
1435 // termination) is fine.
1437 // o SIGALRM -- No need to set it up because the default behaviour (process
1438 // termination) is fine. See the "Watchdog Timer" comment (above) for details.
1440 // o SIGPIPE -- We don't want to quit when write to a dead socket, so we
1441 // ignore this signal.
1443 pipeSet = signal(SIGPIPE, SIG_IGN);
1444 if (pipeSet == SIG_ERR) {
1445 errStr = "Could not ignore SIGPIPE: %m";
1449 // Check in with launchd and get our listening socket.
1451 listener = CheckInWithLaunchd(asl, aslMsg, &errStr);
1453 assert(errStr != NULL);
1457 // Create a kqueue and wrap the listening socket in it.
1461 errStr = "Could not create kqueue: %m";
1465 EV_SET(&initEvent, listener, EVFILT_READ, EV_ADD, 0, 0, NULL);
1466 err = kevent(kq, &initEvent, 1, NULL, 0, NULL);
1468 errStr = "Could not add listening socket to kqueue: %m";
1472 // Force the listening socket to non-blocking mode. Without this, our timeout
1473 // handling won't work properly. Specifically, we could get stuck in an accept
1474 // if a connection request appears and then disappears. Eventually the watchdog
1475 // would clean up, but that's not a great solution.
1477 err = SetNonBlocking(listener, true);
1479 errno = err; // for %m
1480 errStr = "Could not check/set socket flags: %m";
1484 // Loop servicing connection requests one at a time.
1488 struct kevent thisEvent;
1490 int thisConnectionError;
1491 struct sockaddr_storage clientAddr; // we don't need this info, but accept won't let us ignore it
1492 socklen_t clientAddrLen = sizeof(clientAddr);
1493 static const struct timespec kIdleTimeout = { kIdleTimeoutInSeconds , 0 };
1495 // Wait on the kqueue for a connection request.
1497 eventCount = kevent(kq, NULL, 0, &thisEvent, 1, &kIdleTimeout);
1498 if (eventCount == 0) {
1499 // We've hit our idle timer. Just break out of the connection loop.
1501 } else if (eventCount == -1) {
1502 // We got some sort of error from kevent; quit with an error.
1503 errStr = "Unexpected error while listening for connections: %m";
1507 // From this point on, we're running on the watchdog timer. If we get
1508 // stuck anywhere, the watchdog will fire eventually and we'll quit.
1512 // The accept should never get stuck because this is a non-blocking
1515 thisConnection = accept(thisEvent.ident, (struct sockaddr *) &clientAddr, &clientAddrLen);
1516 if (thisConnection == -1) {
1517 if (errno == EWOULDBLOCK) {
1518 // If the incoming connection just disappeared (perhaps the client
1519 // died before we accepted the connection), don't log that as an error
1521 err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Connection disappeared before we could accept it: %m");
1524 // Other errors mean that we're in a very weird state; we respond by
1525 // failing out with an error.
1526 errStr = "Unexpected error while accepting a connection: %m";
1531 // Because the accept can fail in a non-fatal fashion, thisConnection can be
1532 // -1 here. In that case, we just skip the next step.
1534 if (thisConnection != -1) {
1535 err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Request started");
1538 // thisConnection inherits its non-blocking setting from listener, but
1539 // we want it to be blocking from here on in, so we switch the status.
1540 // We're now relying on the watchdog to kill us if we get stuck.
1542 thisConnectionError = BASErrnoToOSStatus( SetNonBlocking(thisConnection, false) );
1544 // Entering heavy liftiing. We have a separate routine to actually
1545 // read the request from the connection, call the client, and send
1548 if (thisConnectionError == noErr) {
1549 thisConnectionError = HandleConnection(asl, aslMsg, commands, commandProcs, thisConnection);
1552 err = close(thisConnection);
1555 if (thisConnectionError == 0) {
1556 err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Request finished");
1558 errno = thisConnectionError; // so it can be picked up by %m
1559 err = asl_log(asl, aslMsg, ASL_LEVEL_ERR, "Request failed: %m");
1568 // At this point, errStr is either NULL, in which case we're quitting because
1569 // of our idle timer, or non-NULL, in which case we're dying with an error.
1571 // We expect the caller to immediately quit once we return. Thus, we
1572 // don't bother cleaning up any resources we have allocated here, including
1573 // asl, aslMsg, and kq.
1575 if (errStr != NULL) {
1576 err = asl_log(asl, aslMsg, ASL_LEVEL_ERR, errStr);
1579 err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Shutting down");
1581 return (errStr == NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
1584 /////////////////////////////////////////////////////////////////
1585 #pragma mark ***** App Code
1587 extern void BASSetDefaultRules(
1588 AuthorizationRef auth,
1589 const BASCommandSpec commands[],
1590 CFStringRef bundleID,
1591 CFStringRef descriptionStringTableName
1593 // See comment in header.
1597 size_t commandIndex;
1601 assert(auth != NULL);
1602 assert(commands != NULL);
1603 assert(commands[0].commandName != NULL); // there must be at least one command
1604 assert(bundleID != NULL);
1605 // descriptionStringTableName may be NULL
1607 bundle = CFBundleGetBundleWithIdentifier(bundleID);
1608 assert(bundle != NULL);
1610 // For each command, set up the default authorization right specification, as
1611 // indicated by the command specification.
1614 while (commands[commandIndex].commandName != NULL) {
1615 // Some no-obvious assertions:
1617 // If you have a right name, you must supply a default rule.
1618 // If you have no right name, you can't supply a default rule.
1620 assert( (commands[commandIndex].rightName == NULL) == (commands[commandIndex].rightDefaultRule == NULL) );
1622 // If you have no right name, you can't supply a right description.
1623 // OTOH, if you have a right name, you may supply a NULL right description
1624 // (in which case you get no custom prompt).
1626 assert( (commands[commandIndex].rightName != NULL) || (commands[commandIndex].rightDescriptionKey == NULL) );
1628 // If there's a right name but no current right specification, set up the
1629 // right specification.
1631 if (commands[commandIndex].rightName != NULL) {
1632 err = AuthorizationRightGet(commands[commandIndex].rightName, (CFDictionaryRef*) NULL);
1633 if (err == errAuthorizationDenied) {
1634 CFStringRef thisDescription;
1635 CFStringRef thisRule;
1637 // The right is not already defined. Set up a definition based on
1638 // the fields in the command specification.
1640 thisRule = CFStringCreateWithCString(
1641 kCFAllocatorDefault,
1642 commands[commandIndex].rightDefaultRule,
1643 kCFStringEncodingUTF8
1645 assert(thisRule != NULL);
1647 thisDescription = NULL;
1648 if (commands[commandIndex].rightDescriptionKey != NULL) {
1649 thisDescription = CFStringCreateWithCString (
1650 kCFAllocatorDefault,
1651 commands[commandIndex].rightDescriptionKey,
1652 kCFStringEncodingUTF8
1654 assert(thisDescription != NULL);
1657 err = AuthorizationRightSet(
1659 commands[commandIndex].rightName, // rightName
1660 thisRule, // rightDefinition
1661 thisDescription, // descriptionKey
1663 descriptionStringTableName // localeTableName
1664 ); // NULL indicates "Localizable.strings"
1665 assert(err == noErr);
1667 if (thisDescription != NULL) {
1668 CFRelease(thisDescription);
1670 if (thisRule != NULL) {
1671 CFRelease(thisRule);
1674 // A right already exists (err == noErr) or any other error occurs, we
1675 // assume that it has been set up in advance by the system administrator or
1676 // this is the second time we've run. Either way, there's nothing more for
1684 extern OSStatus BASExecuteRequestInHelperTool(
1685 AuthorizationRef auth,
1686 const BASCommandSpec commands[],
1687 CFStringRef bundleID,
1688 CFDictionaryRef request,
1689 CFDictionaryRef * response
1691 // See comment in header.
1693 OSStatus retval = noErr;
1695 size_t commandIndex;
1696 char bundleIDC[PATH_MAX];
1698 struct sockaddr_un addr;
1699 AuthorizationExternalForm extAuth;
1703 assert(auth != NULL);
1704 assert(commands != NULL);
1705 assert(commands[0].commandName != NULL); // there must be at least one command
1706 assert(bundleID != NULL);
1707 assert(request != NULL);
1708 assert( response != NULL);
1709 assert(*response == NULL);
1713 assert(CFDictionaryContainsKey(request, CFSTR(kBASCommandKey)));
1714 assert(CFGetTypeID(CFDictionaryGetValue(request, CFSTR(kBASCommandKey))) == CFStringGetTypeID());
1716 // Look up the command and preauthorize. This has the nice side effect that
1717 // the authentication dialog comes up, in the typical case, here, rather than
1718 // in the helper tool. This is good because the helper tool is global /and/
1719 // single threaded, so if it's waiting for an authentication dialog for user A
1720 // it can't handle requests from user B.
1722 retval = FindCommand(request, commands, &commandIndex);
1724 #if !defined(BAS_PREAUTHORIZE)
1725 #define BAS_PREAUTHORIZE 1
1727 #if BAS_PREAUTHORIZE
1728 if ( (retval == noErr) && (commands[commandIndex].rightName != NULL) ) {
1729 AuthorizationItem item = { commands[commandIndex].rightName, 0, NULL, 0 };
1730 AuthorizationRights rights = { 1, &item };
1732 retval = AuthorizationCopyRights(auth, &rights, kAuthorizationEmptyEnvironment, kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize, NULL);
1736 // Create the socket and tell it to not generate SIGPIPE.
1738 if (retval == noErr) {
1739 fd = socket(AF_UNIX, SOCK_STREAM, 0);
1741 retval = BASErrnoToOSStatus(errno);
1744 if (retval == noErr) {
1745 static const int kOne = 1;
1747 if ( setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &kOne, sizeof(kOne)) < 0 ) {
1748 retval = BASErrnoToOSStatus(errno);
1752 // Form the socket address, including a path based on the bundle ID.
1754 if (retval == noErr) {
1755 if ( ! CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC)) ) {
1756 retval = coreFoundationUnknownErr;
1759 if (retval == noErr) {
1762 memset(&addr, 0, sizeof(addr));
1764 addr.sun_family = AF_UNIX;
1765 pathLen = snprintf(addr.sun_path, sizeof(addr.sun_path), kBASSocketPathFormat, bundleIDC);
1766 if (pathLen >= sizeof(addr.sun_path)) {
1767 retval = paramErr; // length of bundle pushed us over the UNIX domain socket path length limit
1769 addr.sun_len = SUN_LEN(&addr);
1773 // Attempt to connect.
1775 if (retval == noErr) {
1776 if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
1777 retval = BASErrnoToOSStatus(errno);
1781 // Send the flattened AuthorizationRef to the tool.
1783 if (retval == noErr) {
1784 retval = AuthorizationMakeExternalForm(auth, &extAuth);
1786 if (retval == noErr) {
1787 retval = BASErrnoToOSStatus( BASWrite(fd, &extAuth, sizeof(extAuth), NULL) );
1790 // Write the request.
1792 if (retval == noErr) {
1793 retval = BASErrnoToOSStatus( BASWriteDictionary(request, fd) );
1796 // Read response, including any descriptors.
1798 if (retval == noErr) {
1799 retval = BASErrnoToOSStatus( BASReadDictioanaryTranslatingDescriptors(fd, response) );
1808 NormaliseOSStatusErrorCode(&retval);
1810 assert( (retval == noErr) == (*response != NULL) );
1815 extern OSStatus BASGetErrorFromResponse(CFDictionaryRef response)
1816 // See comment in header.
1821 assert(response != NULL);
1823 num = (CFNumberRef) CFDictionaryGetValue(response, CFSTR(kBASErrorKey));
1825 if ( (num == NULL) || (CFGetTypeID(num) != CFNumberGetTypeID()) ) {
1826 err = coreFoundationUnknownErr;
1829 if ( ! CFNumberGetValue(num, kCFNumberSInt32Type, &err) ) {
1830 err = coreFoundationUnknownErr;
1834 NormaliseOSStatusErrorCode(&err);
1838 extern BASFailCode BASDiagnoseFailure(
1839 AuthorizationRef auth,
1840 CFStringRef bundleID
1842 // See comment in header.
1844 BASFailCode retval = kBASFailUnknown;
1847 char bundleIDC [ PATH_MAX ];
1848 char toolPath [ PATH_MAX ];
1849 char plistPath [ PATH_MAX ];
1851 struct stat fileStatus;
1855 struct sockaddr_un addr;
1859 assert(auth != NULL);
1860 assert(bundleID != NULL);
1862 // Construct paths to the tool and plist.
1864 if ( CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC)) ) {
1866 pathLen = snprintf(toolPath, sizeof(toolPath), kBASToolPathFormat, bundleIDC);
1867 assert(pathLen < PATH_MAX); // snprintf truncated the string; won't crash us, but we want to know
1869 pathLen = snprintf(plistPath, sizeof(plistPath), kBASPlistPathFormat, bundleIDC);
1870 assert(pathLen < PATH_MAX); // snprintf truncated the string; won't crash us, but we want to know
1872 // Check if files exist at those paths.
1874 toolErr = stat(toolPath, &fileStatus);
1875 plistErr = stat(plistPath, &fileStatus);
1877 if ( (toolErr == 0) && (plistErr == 0) ) {
1878 // If both items are present, try to connect and see what we get.
1880 fd = socket(AF_UNIX, SOCK_STREAM, 0);
1882 memset(&addr, 0, sizeof(addr));
1884 addr.sun_family = AF_UNIX;
1885 (void) snprintf(addr.sun_path, sizeof(addr.sun_path), kBASSocketPathFormat, bundleIDC);
1886 addr.sun_len = SUN_LEN(&addr);
1888 // Attempt to connect to the socket. If we get ECONNREFUSED, it means no one is
1891 if ( (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) && (errno == ECONNREFUSED) ) {
1892 retval = kBASFailDisabled;
1898 if ( (toolErr == 0) || (plistErr == 0) ) {
1899 retval = kBASFailPartiallyInstalled;
1901 retval = kBASFailNotInstalled;
1909 // kPlistTemplate is a template for our launchd.plist file.
1911 static const char * kPlistTemplate =
1912 // The standard plist header.
1914 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1915 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
1916 "<plist version=\"1.0\">\n"
1919 // We install the job disabled, then enable it as the last step.
1921 " <key>Disabled</key>\n"
1924 // Use the bundle identifier as the job label.
1926 " <key>Label</key>\n"
1927 " <string>%s</string>\n"
1929 // Use launch on demaind.
1931 " <key>OnDemand</key>\n"
1934 // There are no program arguments, other that the path to the helper tool itself.
1937 // kBASToolPathFormat embeds a %s
1939 " <key>ProgramArguments</key>\n"
1941 " <string>" kBASToolPathFormat "</string>\n"
1944 // The tool is required to check in with launchd.
1946 " <key>ServiceIPC</key>\n"
1949 // This specifies the UNIX domain socket used to launch the tool, including
1950 // the permissions on the socket (438 is 0666).
1953 // kBASSocketPathFormat embeds a %s
1955 " <key>Sockets</key>\n"
1957 " <key>" kLaunchDSocketDictKey "</key>\n"
1959 " <key>SockFamily</key>\n"
1960 " <string>Unix</string>\n"
1961 " <key>SockPathMode</key>\n"
1962 " <integer>438</integer>\n"
1963 " <key>SockPathName</key>\n"
1964 " <string>" kBASSocketPathFormat "</string>\n"
1965 " <key>SockType</key>\n"
1966 " <string>Stream</string>\n"
1976 // We install by running our "InstallTool" using AuthorizationExecuteWithPrivileges
1977 // (AEWP) and passing the relevant parameters to it through AEWP.
1979 // There is an obvious issue with the way we are handling installation as the user
1980 // is executing some non-privileged code by way of AEWP. The scenario could exist
1981 // that the code is malicious (or they have other malicious code running at the
1982 // same time) and it could swap in any other tool that it would want executed as
1985 // We decided on this design primarily because the only other option was to run a
1986 // shell via AEWP and pipe a script to it. That would have given us the nice
1987 // properties of not having to have a separate installer on disk and the script
1988 // could be embedded within the executable making it a little more difficult for
1991 // However, running a shell as root is /not/ a very good paradigm to follow, thus,
1992 // weighing the cost-benefits from a security perspective impelled us to just use
1993 // a separate installer tool. The assumption being that, no matter what, if a user
1994 // has malicious code running on their system the added security of having an
1995 // embedded script is negligible and not worth pulling in an entire shell
1996 // environment as root.
1998 // The obvious disadvantages stem from the first advantage of the former, namely,
1999 // it's a little more coding and accounting effort (-:
2002 // What's This About Zombies?
2003 // --------------------------
2004 // AuthorizationExecuteWithPrivileges creates a process that runs with privileges.
2005 // This process is a child of our process. Thus, we need to reap the process
2006 // (by calling <x-man-page://2/waitpid>). If we don't do this, we create a 'zombie'
2007 // process (<x-man-page://1/ps> displays its status as "Z") that persists until
2008 // our process quits (at which point the zombie gets reparented to launchd, and
2009 // launchd automatically reaps it). Zombies are generally considered poor form.
2010 // Thus, we want to avoid creating them.
2012 // Unfortunately, AEWP doesn't return the process ID of the child process
2013 // <rdar://problem/3090277>, which makes it challenging for us to reap it. We could
2014 // reap all children (by passing -1 to waitpid) but that's not cool for library code
2015 // (we could end up reaping a child process that's completely unrelated to this
2016 // code, perhaps created by some other part of the host application). Thus, we need
2017 // to find the child process's PID. And the only way to do that is for the child
2018 // process to tell us.
2020 // So, in the child process (the install tool) we echo the process ID and in the
2021 // parent we look for that in the returned text. *sigh* It's pretty ugly, but
2022 // that's the best I can come up with. We delimit the process ID with some
2023 // pretty distinctive text to make it clear that we've got the right thing.
2025 #if !defined(NDEBUG)
2027 static Boolean gBASLogInteractions = false;
2028 // Set gBASLogInteractions to have BASFixFailure log its interactions with
2029 // the installation tool to stderr.
2031 static Boolean gBASLogInteractionsInitialised = false;
2032 // This indicates whether we've initialised gBASLogInteractions from the
2033 // environment variable.
2037 static OSStatus RunInstallToolAsRoot(
2038 AuthorizationRef auth,
2039 const char * installToolPath,
2040 const char * command,
2043 // Run the specified install tool as root. The arguments to the tool are
2044 // given as a sequence of (char *)s, terminated be a NULL. The tool is
2045 // expected to output special tokens to indicate success or failure.
2059 assert(auth != NULL);
2060 assert(installToolPath != NULL);
2061 assert(command != NULL);
2067 // Count the number of arguments.
2070 va_start(ap, command);
2071 while ( va_arg(ap, char *) != NULL ) {
2076 // Allocate an argument array and populate it, checking each argument along the way.
2079 args = calloc(argCount + 3, sizeof(char *)); // +3 for installToolPath, command and trailing NULL
2081 retval = memFullErr;
2083 if (retval == noErr) {
2086 args[argIndex] = (char *) installToolPath; // Annoyingly, AEWP (and exec) takes a (char * const *)
2087 argIndex += 1; // argument, implying that it might modify the individual
2088 args[argIndex] = (char *) command; // strings. That means you can't pass a (const char *) to
2089 argIndex += 1; // the routine. However, AEWP never modifies its input
2090 // arguments, so we just cast away the const.
2091 // *sigh* <rdar://problem/3090294>
2092 va_start(ap, command);
2094 args[argIndex] = va_arg(ap, char *);
2095 if (args[argIndex] == NULL) {
2103 // Go go gadget AEWP!
2105 if (retval == noErr) {
2106 #if !defined(NDEBUG)
2107 if ( ! gBASLogInteractionsInitialised ) {
2110 value = getenv("BASLogInteractions");
2111 gBASLogInteractions = ( ((value != NULL) && (atoi(value) != 0)) );
2113 gBASLogInteractionsInitialised = true;
2116 if (gBASLogInteractions) {
2118 while (args[argIndex] != NULL) {
2119 fprintf(stderr, "args[%zd] = %s\n", argIndex, args[argIndex]);
2124 retval = AuthorizationExecuteWithPrivileges(auth, args[0], kAuthorizationFlagDefaults, &args[1], &channel);
2127 // Process the tool's output. We read every line of output from the tool until
2128 // we receive either an EOF or the success or failure tokens.
2130 // AEWP provides us with no way to get to the tool's stderr or exit status,
2131 // so we rely on the tool to send us this "oK" to indicate successful completion.
2133 if (retval == noErr) {
2134 char thisLine[1024];
2138 // This loops is a little more complex than you might expect. There are
2139 // a number of reasons for this:
2141 // o AEWP does not return us the child PID, so we have to scan the tool's
2142 // output look for a line that contains that information (surrounded
2143 // by special tokens).
2145 // o Because we can't be guaranteed to get the child PID, we can't be
2146 // guaranteed to get the child's exit status. Thus, rather than relying
2147 // on the exit status, we have the child explicitly print special tokens
2148 // on success and failure.
2150 // o Because we're parsing special tokens anyway, we might as well extract
2151 // the real error code from the failure token.
2153 // o A change made to launchctl in Mac OS X 10.4.7 <rdar://problem/4389914>
2154 // causes it to fork a copy of itself. The forked copy then delays
2155 // for 30 seconds before doing some stuff, eventually printing a message
2156 // like "Workaround Bonjour: 0". This causes us two problems.
2158 // 1. The second copy of launchd still has our communications channel
2159 // (that is, the other end of "channel") as its stdin/stdout.
2160 // Thus, we don't get an EOF on channel until that copy quits.
2161 // This causes a 30 second delay in installation.
2163 // 2. The second copy of launchd prints its status line (that is,
2164 // "Workaround Bonjour: 0") well after the tool prints the success
2167 // I solved these problems by parsing each line for the success or failure
2168 // token and ignoring any output after that.
2170 // To minimise the danger of interpreting one of the tool's commands
2171 // output as one of our tokens, I've given them a wacky case (for example,
2172 // "oK", not "ok" or "OK" or "Ok").
2175 success = (fgets(thisLine, sizeof(thisLine), channel) != NULL);
2177 // We hit the end of the output without seeing a success or failure
2178 // token. Note good. errState is an ADSP error code, but it says
2179 // exactly what I want to say and it's not likely to crop up any
2185 // This echo doesn't work properly if the line coming back from the tool
2186 // is longer than the line buffer. However, as the echo is only relevant for
2187 // debugging, and the detection of the "oK" isn't affected by this problem,
2188 // I'm going to leave it as it is.
2190 #if !defined(NDEBUG)
2191 if (gBASLogInteractions) {
2192 fprintf(stderr, ">%s", thisLine);
2196 // Look for the success token and terminate with no error in that case.
2198 if (strcmp(thisLine, kBASInstallToolSuccess "\n") == 0) {
2199 assert(retval == noErr);
2203 // Look for the failure token and extract the error result from that.
2205 if ( sscanf(thisLine, kBASInstallToolFailure "\n", &tmpInt) == 1 ) {
2206 retval = BASErrnoToOSStatus( tmpInt );
2207 if (retval == noErr) {
2214 // If we haven't already found a child process ID, look for a line
2215 // that contains it (surrounded by special tokens). For details, see
2216 // the discussion of zombies above.
2218 if ( (childPID == -1) && (sscanf(thisLine, kBASAntiZombiePIDToken1 "%ld" kBASAntiZombiePIDToken2 "\n", &tmpLong) == 1) ) {
2219 childPID = (pid_t) tmpLong;
2224 // If we successfully managed to determine the PID of our child process, reap
2225 // that child. Note that we ignore any errors from this step. If an error
2226 // occurs, we end up creating a zombie, which isn't too big a deal. We also
2227 // junk the status result from the tool, relying exclusively on the presence
2228 // of the "oK" in the output.
2230 #if !defined(NDEBUG)
2231 if (gBASLogInteractions) {
2232 fprintf(stderr, "childPID=%ld\n", (long) childPID);
2235 if (childPID != -1) {
2240 waitResult = waitpid(childPID, &junkStatus, 0);
2241 } while ( (waitResult < 0) && (errno == EINTR) );
2246 if (channel != NULL) {
2247 junk = fclose(channel);
2252 NormaliseOSStatusErrorCode(&retval);
2256 static OSStatus BASInstall(
2257 AuthorizationRef auth,
2258 const char * bundleID,
2259 const char * installToolPath,
2260 const char * helperToolPath
2262 // Do an install from scratch. Get the specified tool from the bundle
2263 // and install it in the "/Library/PrivilegedHelperTools" directory,
2264 // along with a plist in "/Library/LaunchDaemons".
2270 char plistPath[PATH_MAX];
2274 assert(auth != NULL);
2275 assert(bundleID != NULL);
2276 assert(installToolPath != NULL);
2277 assert(helperToolPath != NULL);
2279 // Prepare for failure
2285 // Create the property list from the template, substituting the bundle identifier in
2286 // three different places. I realise that this isn't very robust (if you change
2287 // the template you have to change this code), but it is /very/ easy.
2289 retval = asprintf(&plistText, kPlistTemplate, bundleID, bundleID, bundleID);
2291 retval = memFullErr;
2296 // Write the plist to a temporary file.
2298 if (retval == noErr) {
2299 strlcpy(plistPath, "/tmp/BASTemp-XXXXXXXX.plist", sizeof(plistPath));
2301 fd = mkstemps(plistPath, strlen( strrchr(plistPath, '.') ) );
2303 retval = BASErrnoToOSStatus( errno );
2306 if (retval == noErr) {
2307 retval = BASErrnoToOSStatus( BASWrite(fd, plistText, strlen(plistText), NULL) );
2310 // Run the tool as root using AuthorizationExecuteWithPrivileges.
2312 if (retval == noErr) {
2313 retval = RunInstallToolAsRoot(auth, installToolPath, kBASInstallToolInstallCommand, bundleID, helperToolPath, plistPath, NULL);
2323 junk = unlink(plistPath);
2330 static OSStatus GetToolPath(CFStringRef bundleID, CFStringRef toolName, char *toolPath, size_t toolPathSize)
2331 // Given a bundle identifier and the name of a tool embedded within that bundle,
2332 // get a file system path to the tool.
2339 assert(bundleID != NULL);
2340 assert(toolName != NULL);
2341 assert(toolPath != NULL);
2342 assert(toolPathSize > 0);
2347 bundle = CFBundleGetBundleWithIdentifier(bundleID);
2348 if (bundle == NULL) {
2349 err = coreFoundationUnknownErr;
2352 toolURL = CFBundleCopyAuxiliaryExecutableURL(bundle, toolName);
2353 if (toolURL == NULL) {
2354 err = coreFoundationUnknownErr;
2358 success = CFURLGetFileSystemRepresentation(toolURL, true, (UInt8 *) toolPath, toolPathSize);
2360 err = coreFoundationUnknownErr;
2364 if (toolURL != NULL) {
2371 extern OSStatus BASFixFailure(
2372 AuthorizationRef auth,
2373 CFStringRef bundleID,
2374 CFStringRef installToolName,
2375 CFStringRef helperToolName,
2376 BASFailCode failCode
2378 // See comment in header.
2382 char bundleIDC[PATH_MAX];
2383 char installToolPath[PATH_MAX];
2384 char helperToolPath[PATH_MAX];
2388 assert(auth != NULL);
2389 assert(bundleID != NULL);
2390 assert(installToolName != NULL);
2391 assert(helperToolName != NULL);
2393 // Get the bundle identifier as a UTF-8 C string. Also, get paths for both of
2397 success = CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC));
2399 retval = coreFoundationUnknownErr;
2401 if (retval == noErr) {
2402 retval = GetToolPath(bundleID, installToolName, installToolPath, sizeof(installToolPath));
2404 if (retval == noErr) {
2405 retval = GetToolPath(bundleID, helperToolName, helperToolPath, sizeof(helperToolPath));
2408 // Depending on the failure code, either run the enable command or the install
2409 // from scratch command.
2411 if (retval == noErr) {
2412 if (failCode == kBASFailDisabled) {
2413 retval = RunInstallToolAsRoot(auth, installToolPath, kBASInstallToolEnableCommand, bundleIDC, NULL);
2415 retval = BASInstall(auth, bundleIDC, installToolPath, helperToolPath);