OSDN Git Service

Update cross tools prefix
[uclinux-h8/uClinux-dist.git] / openswan / osxApp / BetterAuthorizationSampleLib.c
1 /*
2         File:       BetterAuthorizationSampleLib.c
3
4     Contains:   Implementation of reusable code for privileged helper tools.
5
6     Written by: DTS
7
8     Copyright:  Copyright (c) 2007 Apple Inc. All Rights Reserved.
9
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.
15
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.
30
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.
36
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.
44
45 */
46
47 // Define BAS_PRIVATE so that we pick up our private definitions from 
48 // "BetterAuthorizationSampleLib.h".
49
50 #define BAS_PRIVATE 1
51
52 #include "BetterAuthorizationSampleLib.h"
53
54 #include <launch.h>
55 #include <unistd.h>
56 #include <fcntl.h>
57 #include <sys/event.h>
58 #include <sys/stat.h>
59 #include <sys/un.h>
60 #include <sys/socket.h>
61
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.
66
67 #if 1
68     #include <CoreServices/CoreServices.h>
69 #else
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"
73 #endif
74
75 //////////////////////////////////////////////////////////////////////////////////
76 #pragma mark ***** Constants
77
78 enum {
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
81 };
82
83 // IMPORTANT:
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.
86
87 // kBASMaxNumberOfKBytes has two uses:
88 //
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.
92 //
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.
95
96 #define kBASMaxNumberOfKBytes                   (1024 * 1024)
97
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).
100
101 #define kBASSocketPathFormat                    "/var/run/%s.socket"
102
103 // The key used to get our describe our socket in the launchd property list file.
104
105 #define kLaunchDSocketDictKey           "MasterSocket"
106
107 /////////////////////////////////////////////////////////////////
108 #pragma mark ***** Common Code
109
110 extern int BASOSStatusToErrno(OSStatus errNum)
111     // See comment in header.
112 {
113         int retval;
114     
115     #define CASE(ident)         \
116         case k ## ident ## Err: \
117             retval = ident;     \
118             break
119     switch (errNum) {
120                 case noErr:
121                         retval = 0;
122                         break;
123         case kENORSRCErr:
124             retval = ESRCH;                 // no ENORSRC on Mac OS X, so use ESRCH
125             break;
126         case memFullErr:
127             retval = ENOMEM;
128             break;
129         CASE(EDEADLK);
130         CASE(EAGAIN);
131                 case kEOPNOTSUPPErr:
132                         retval = ENOTSUP;
133                         break;
134         CASE(EPROTO);
135         CASE(ETIME);
136         CASE(ENOSR);
137         CASE(EBADMSG);
138         case kECANCELErr:
139             retval = ECANCELED;             // note spelling difference
140             break;
141         CASE(ENOSTR);
142         CASE(ENODATA);
143         CASE(EINPROGRESS);
144         CASE(ESRCH);
145         CASE(ENOMSG);
146         default:
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
151             } else {
152                                 retval = (int) errNum;                                          // just return the value unmodified
153                         }
154     }
155     #undef CASE
156     return retval;
157 }
158
159 extern OSStatus BASErrnoToOSStatus(int errNum)
160     // See comment in header.
161 {
162         OSStatus retval;
163         
164         if ( errNum == 0 ) {
165                 retval = noErr;
166         } else if ( (errNum >= EPERM) && (errNum <= ELAST) ) {
167                 retval = (OSStatus) errNum + errSecErrnoBase;
168         } else {
169                 retval = (int) errNum;      // just return the value unmodified
170         }
171     
172     return retval;
173 }
174
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.
179 {
180     static const char kBASBinaryPlistWatermark[6] = "bplist";
181     
182     assert(plistBuffer != NULL);
183         
184         return (plistSize >= sizeof(kBASBinaryPlistWatermark)) 
185         && (memcmp(plistBuffer, kBASBinaryPlistWatermark, sizeof(kBASBinaryPlistWatermark)) == 0);
186 }
187
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.
192 {
193     assert(errPtr != NULL);
194     
195     if ( (*errPtr == errAuthorizationCanceled) || (*errPtr == (errSecErrnoBase + ECANCELED)) ) {
196         *errPtr = userCanceledErr;
197     }
198 }
199
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 
203     // EPIPE.
204         //
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).
209 {
210         int     err;
211         char *  cursor;
212         size_t  bytesLeft;
213         ssize_t bytesThisTime;
214
215     // Pre-conditions
216
217         assert(fd >= 0);
218         assert(buf != NULL);
219     // bufSize may be 0
220         assert(bufSize <= kBASMaxNumberOfKBytes);
221     // bytesRead may be NULL
222         
223         err = 0;
224         bytesLeft = bufSize;
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) {
232                         err = EPIPE;
233                 } else {
234                         assert(bytesThisTime == -1);
235                         
236                         err = errno;
237                         assert(err != 0);
238                         if (err == EINTR) {
239                                 err = 0;                // let's loop again
240                         }
241                 }
242         }
243         if (bytesRead != NULL) {
244                 *bytesRead = bufSize - bytesLeft;
245         }
246         
247         return err;
248 }
249
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 
253         // you get EPIPE.
254         //
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).
259 {
260         int     err;
261         char *  cursor;
262         size_t  bytesLeft;
263         ssize_t bytesThisTime;
264         
265     // Pre-conditions
266
267         assert(fd >= 0);
268         assert(buf != NULL);
269     // bufSize may be 0
270         assert(bufSize <= kBASMaxNumberOfKBytes);
271         // bytesWritten may be NULL
272         
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.
282         
283         #if !defined(BAS_WRITE_CHECK_SIGPIPE)
284                 #define BAS_WRITE_CHECK_SIGPIPE 1
285         #endif
286         #if !defined(NDEBUG) && BAS_WRITE_CHECK_SIGPIPE
287                 {
288                         int                                     junk;
289                         struct stat                     sb;
290                         struct sigaction        currentSignalState;
291                         int                                     val;
292                         socklen_t                       valLen;
293                         
294                         junk = fstat(fd, &sb);
295                         assert(junk == 0);
296                         
297                         if ( S_ISFIFO(sb.st_mode) || S_ISSOCK(sb.st_mode) ) {
298                                 junk = sigaction(SIGPIPE, NULL, &currentSignalState);
299                                 assert(junk == 0);
300                                 
301                                 valLen = sizeof(val);
302                                 junk = getsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &val, &valLen);
303                                 assert(junk == 0);
304                                 assert(valLen == sizeof(val));
305
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:
309                                 //
310                                 // (void) signal(SIGPIPE, SIG_IGN);
311                                 //
312                                 // You typically add this code to your main function.
313                                 //
314                                 // The standard code for the latter is:
315                                 //
316                                 // static const int kOne = 1;
317                                 // err = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &kOne, sizeof(kOne));
318                                 //
319                                 // You typically do this just after creating the socket.
320
321                                 assert( (currentSignalState.sa_handler == SIG_IGN) || (val == 1) );
322                         }
323                 }
324         #endif
325
326         err = 0;
327         bytesLeft = bufSize;
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) {
335                         assert(false);
336                         err = EPIPE;
337                 } else {
338                         assert(bytesThisTime == -1);
339                         
340                         err = errno;
341                         assert(err != 0);
342                         if (err == EINTR) {
343                                 err = 0;                // let's loop again
344                         }
345                 }
346         }
347         if (bytesWritten != NULL) {
348                 *bytesWritten = bufSize - bytesLeft;
349         }
350         
351         return err;
352 }
353
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.
359     //
360     // On success, the caller is responsible for releasing *dictPtr.
361         //
362         // See also the companion routine, BASWriteDictionary, below.
363 {
364         int                 err = 0;
365         uint32_t                        dictSize;
366         void *                          dictBuffer;
367         CFDataRef                       dictData;
368         CFPropertyListRef       dict;
369
370     // Pre-conditions
371
372         assert(fdIn >= 0);
373         assert( dictPtr != NULL);
374         assert(*dictPtr == NULL);
375         
376         dictBuffer = NULL;
377         dictData   = NULL;
378         dict       = NULL;
379
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.
382         
383         err = BASRead(fdIn, &dictSize, sizeof(dictSize), NULL);
384         if (err == 0) {
385         dictSize = OSSwapBigToHostInt32(dictSize);
386                 if (dictSize == 0) {
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 
389                         // that case.
390                         err = EINVAL;
391                 } else if (dictSize > kBASMaxNumberOfKBytes) {
392                         // Abitrary limit to prevent potentially hostile client overwhelming us with data.
393                         err = EINVAL;
394                 }
395         }
396         if (err == 0) {
397                 dictBuffer = malloc( (size_t) dictSize);
398                 if (dictBuffer == NULL) {
399                         err = ENOMEM;
400                 }
401         }
402         
403         // Read the data and unflatten.
404         
405         if (err == 0) {
406                 err = BASRead(fdIn, dictBuffer, dictSize, NULL);
407         }
408         if ( (err == 0) && BASIsBinaryPropertyListData(dictBuffer, dictSize) ) {
409         err = BASOSStatusToErrno( coreFoundationUnknownErr );
410         }
411         if (err == 0) {
412                 dictData = CFDataCreateWithBytesNoCopy(NULL, dictBuffer, dictSize, kCFAllocatorNull);
413                 if (dictData == NULL) {
414                         err = BASOSStatusToErrno( coreFoundationUnknownErr );
415                 }
416         }
417         if (err == 0) {
418                 dict = CFPropertyListCreateFromXMLData(NULL, dictData, kCFPropertyListImmutable, NULL);
419                 if (dict == NULL) {
420                         err = BASOSStatusToErrno( coreFoundationUnknownErr );
421                 }
422         }
423         if ( (err == 0) && (CFGetTypeID(dict) != CFDictionaryGetTypeID()) ) {
424                 err = EINVAL;           // only CFDictionaries need apply
425         }
426         // CFShow(dict);
427         
428         // Clean up.
429         
430         if (err != 0) {
431                 if (dict != NULL) {
432                         CFRelease(dict);
433                 }
434                 dict = NULL;
435         }
436         *dictPtr = (CFDictionaryRef) dict;
437         free(dictBuffer);
438         if (dictData != NULL) {
439                 CFRelease(dictData);
440         }
441         
442         assert( (err == 0) == (*dictPtr != NULL) );
443         
444         return err;
445 }
446
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 
451         // read.
452         //
453         // See also the companion routine, BASReadDictionary, above.
454 {
455         int                 err = 0;
456         CFDataRef                       dictData;
457         uint32_t                        dictSize;
458
459     // Pre-conditions
460
461         assert(dict != NULL);
462         assert(fdOut >= 0);
463         
464         dictData   = NULL;
465         
466     // Get the dictionary as XML data.
467     
468         dictData = CFPropertyListCreateXMLData(NULL, dict);
469         if (dictData == NULL) {
470                 err = BASOSStatusToErrno( coreFoundationUnknownErr );
471         }
472     
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.
475     //
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 
479     // allocation.
480     
481     if ( (err == 0) && (CFDataGetLength(dictData) > kBASMaxNumberOfKBytes) ) {
482         err = EINVAL;
483     }
484     if (err == 0) {
485                 dictSize = OSSwapHostToBigInt32( CFDataGetLength(dictData) );
486         err = BASWrite(fdOut, &dictSize, sizeof(dictSize), NULL);
487     }
488         if (err == 0) {
489                 err = BASWrite(fdOut, CFDataGetBytePtr(dictData), CFDataGetLength(dictData), NULL);
490         }
491
492         if (dictData != NULL) {
493                 CFRelease(dictData);
494         }
495                 
496         return err;
497 }
498
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.
503
504 static const char kDummyData = 'D';
505
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 
510 // just disappears.
511 //
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.
516
517 static const char kACKData   = 'A';
518
519 static int BASReadDescriptor(int fd, int *fdRead)
520     // Read a descriptor from fd and place it in *fdRead.
521     //
522     // On success, the caller is responsible for closing *fdRead.
523     //
524     // See the associated BASWriteDescriptor, below.
525 {
526         int                             err;
527         int                             junk;
528         struct msghdr           msg;
529         struct iovec            iov;
530         struct {
531                 struct cmsghdr  hdr;
532                 int             fd;
533         }                                       control;
534         char                            dummyData;
535         ssize_t                         bytesReceived;
536
537     // Pre-conditions
538
539         assert(fd >= 0);
540         assert( fdRead != NULL);
541         assert(*fdRead == -1);
542
543         iov.iov_base = (char *) &dummyData;
544         iov.iov_len  = sizeof(dummyData);
545         
546     msg.msg_name       = NULL;
547     msg.msg_namelen    = 0;
548     msg.msg_iov        = &iov;
549     msg.msg_iovlen     = 1;
550     msg.msg_control    = (caddr_t) &control;
551     msg.msg_controllen = sizeof(control);
552     msg.msg_flags          = MSG_WAITALL;
553     
554     do {
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) ) {
565                         err = EINVAL;
566                 } else {
567                         *fdRead = control.fd;
568                         err = 0;
569                 }
570             } else if (bytesReceived == 0) {
571                 err = EPIPE;
572             } else {
573                 assert(bytesReceived == -1);
574
575                 err = errno;
576                 assert(err != 0);
577             }
578         } while (err == EINTR);
579     
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.
582     
583     if (err == 0) {
584         err = BASWrite(fd, &kACKData, sizeof(kACKData), NULL);
585         if (err != 0) {
586             junk = close(*fdRead);
587             assert(junk == 0);
588             *fdRead = -1;
589         }
590     }
591
592         assert( (err == 0) == (*fdRead >= 0) );
593         
594         return err;
595 }
596
597 static int BASWriteDescriptor(int fd, int fdToWrite)
598     // Write the descriptor fdToWrite to fd.
599     //
600     // See the associated BASReadDescriptor, above.
601 {
602         int                             err;
603         struct msghdr           msg;
604         struct iovec            iov;
605         struct {
606                 struct cmsghdr  hdr;
607                 int             fd;
608         }                                       control;
609         ssize_t                         bytesSent;
610     char                ack;
611
612     // Pre-conditions
613
614         assert(fd >= 0);
615         assert(fdToWrite >= 0);
616
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;
621
622         iov.iov_base = (char *) &kDummyData;
623         iov.iov_len  = sizeof(kDummyData);
624         
625     msg.msg_name       = NULL;
626     msg.msg_namelen    = 0;
627     msg.msg_iov        = &iov;
628     msg.msg_iovlen     = 1;
629     msg.msg_control    = (caddr_t) &control;
630     msg.msg_controllen = control.hdr.cmsg_len;
631     msg.msg_flags          = 0;
632     do {
633             bytesSent = sendmsg(fd, &msg, 0);
634             if (bytesSent == sizeof(kDummyData)) {
635                 err = 0;
636             } else {
637                 assert(bytesSent == -1);
638
639                 err = errno;
640                 assert(err != 0);
641             }
642         } while (err == EINTR);
643
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.
646     
647     if (err == 0) {
648         err = BASRead(fd, &ack, sizeof(ack), NULL);
649         if ( (err == 0) && (ack != kACKData) ) {
650             err = EINVAL;
651         }
652     }
653
654     return err;
655 }
656
657 extern void BASCloseDescriptorArray(
658         CFArrayRef                                      descArray
659 )
660     // See comment in header.
661 {       
662         int                                                     junk;
663         CFIndex                                         descCount;
664         CFIndex                                         descIndex;
665         
666         // I decided to allow descArray to be NULL because it makes it 
667         // easier to call this routine using the code.
668         //
669         // BASCloseDescriptorArray((CFArrayRef) CFDictionaryGetValue(response, CFSTR(kBASDescriptorArrayKey)));
670         
671         if (descArray != NULL) {
672                 if (CFGetTypeID(descArray) == CFArrayGetTypeID()) {
673                         descCount = CFArrayGetCount(descArray);
674
675                         for (descIndex = 0; descIndex < descCount; descIndex++) {
676                                 CFNumberRef thisDescNum;
677                                 int             thisDesc;
678                 
679                                 thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(descArray, descIndex);
680                                 if (   (thisDescNum == NULL) 
681                                         || (CFGetTypeID(thisDescNum) != CFNumberGetTypeID()) 
682                                         || ! CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc) ) {
683                                         assert(false);
684                                 } else {
685                                         assert(thisDesc >= 0);
686                                         junk = close(thisDesc);
687                                         assert(junk == 0);
688                                 }
689                         }
690                 } else {
691                         assert(false);
692                 }
693         }
694 }
695
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.
700     //
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).
704 {
705         int                             err;
706         int                             junk;
707         CFDictionaryRef         dict;
708         CFArrayRef                      incomingDescs;
709         
710     // Pre-conditions
711
712         assert(fd >= 0);
713         assert( dictPtr != NULL);
714         assert(*dictPtr == NULL);
715         
716         dict = NULL;
717         
718         // Read the dictionary.
719         
720         err = BASReadDictionary(fd, &dict);
721         
722         // Now read the descriptors, if any.
723         
724         if (err == 0) {
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.
729                         
730                         *dictPtr = dict;
731                         dict = NULL;
732                 } else {
733                         CFMutableArrayRef               translatedDescs;
734                         CFMutableDictionaryRef  mutableDict;
735                         CFIndex                                 descCount;
736                         CFIndex                                 descIndex;
737                         
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.
743                         
744                         translatedDescs  = NULL;
745                         mutableDict      = NULL;
746
747                         // Start by checking incomingDescs.
748                                         
749                         if ( CFGetTypeID(incomingDescs) != CFArrayGetTypeID() ) {
750                                 err = EINVAL;
751                         }
752                         
753                         // Create our output data.
754                         
755                         if (err == 0) {
756                 translatedDescs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
757                 if (translatedDescs == NULL) {
758                     err = coreFoundationUnknownErr;
759                 }
760                         }
761                         if (err == 0) {
762                                 mutableDict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
763                                 if (mutableDict == NULL) {
764                                         err = BASOSStatusToErrno( coreFoundationUnknownErr );
765                                 }
766                         }
767
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 
771                         // we fail.
772                         
773                         if (err == 0) {
774                                 descCount = CFArrayGetCount(incomingDescs);
775                                 
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.
783
784                                 #if !defined(NDEBUG)
785                                         for (descIndex = 0; descIndex < descCount; descIndex++) {
786                                                 int             thisDesc;
787                                                 CFNumberRef thisDescNum;
788                                                 
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);
794                                         }
795                                 #endif
796                                 
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.
801                                 
802                                 for (descIndex = 0; descIndex < descCount; descIndex++) {
803                                         int             thisDesc;
804                                         CFNumberRef thisDescNum;
805                                         
806                                         thisDesc = -1;
807                                         thisDescNum = NULL;
808                                         
809                                         err = BASReadDescriptor(fd, &thisDesc);
810                                         if (err == 0) {
811                                                 thisDescNum = CFNumberCreate(NULL, kCFNumberIntType, &thisDesc);
812                                                 if (thisDescNum == NULL) {
813                                                         err = BASOSStatusToErrno( coreFoundationUnknownErr );
814                                                 }
815                                         }
816                                         if (err == 0) {
817                                                 CFArrayAppendValue(translatedDescs, thisDescNum);
818                                                 // The descriptor is now stashed in translatedDescs, 
819                                                 // so this iteration of the loop is no longer responsible 
820                                                 // for closing it.
821                                                 thisDesc = -1;          
822                                         }
823                                         
824                     if (thisDescNum != NULL) {
825                         CFRelease(thisDescNum);
826                     }
827                                         if (thisDesc != -1) {
828                                                 junk = close(thisDesc);
829                                                 assert(junk == 0);
830                                         }
831                                         
832                                         if (err != 0) {
833                                                 break;
834                                         }
835                                 }
836                         }
837
838                         // Clean up and establish output parameters.
839                         
840                         if (err == 0) {
841                                 CFDictionarySetValue(mutableDict, CFSTR(kBASDescriptorArrayKey), translatedDescs);
842                                 *dictPtr = mutableDict;
843                         } else {
844                                 BASCloseDescriptorArray(translatedDescs);
845                 if (mutableDict != NULL) {
846                     CFRelease(mutableDict);
847                 }
848                         }
849             if (translatedDescs != NULL) {
850                 CFRelease(translatedDescs);
851             }
852                 }
853         }
854         
855     if (dict != NULL) {
856         CFRelease(dict);
857     }
858         
859         assert( (err == 0) == (*dictPtr != NULL) );
860         
861         return err;
862 }
863
864 static int BASWriteDictionaryAndDescriptors(CFDictionaryRef dict, int fd)
865         // Writes a dictionary and its associated descriptors to fd.
866 {
867         int                     err;
868         CFArrayRef              descArray;
869         CFIndex                 descCount;
870         CFIndex                 descIndex;
871         
872     // Pre-conditions
873
874     assert(dict != NULL);
875     assert(fd >= 0);
876     
877         // Write the dictionary.
878         
879         err = BASWriteDictionary(dict, fd);
880         
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 
884         // passed.
885         
886         if (err == 0) {
887                 descArray = (CFArrayRef) CFDictionaryGetValue(dict, CFSTR(kBASDescriptorArrayKey));
888                 
889                 // We only do the following if the special key is present.
890                 
891                 if (descArray != NULL) {
892                 
893                         // If it's not an array, that's bad.
894                         
895                         if ( CFGetTypeID(descArray) != CFArrayGetTypeID() ) {
896                                 err = EINVAL;
897                         }
898                         
899                         // Loop over the array, getting each descriptor and writing it.
900                         
901                         if (err == 0) {
902                                 descCount = CFArrayGetCount(descArray);
903                                 
904                                 for (descIndex = 0; descIndex < descCount; descIndex++) {
905                                         CFNumberRef thisDescNum;
906                                         int             thisDesc;
907                                         
908                                         thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(descArray, descIndex);
909                                         if (   (thisDescNum == NULL) 
910                                                 || (CFGetTypeID(thisDescNum) != CFNumberGetTypeID()) 
911                                                 || ! CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc) ) {
912                                                 err = EINVAL;
913                                         }
914                                         if (err == 0) {
915                                                 err = BASWriteDescriptor(fd, thisDesc);
916                                         }
917
918                                         if (err != 0) {
919                                                 break;
920                                         }
921                                 }
922                         }
923                 }
924         }
925
926         return err;
927 }
928
929 static OSStatus FindCommand(
930         CFDictionaryRef             request,
931         const BASCommandSpec            commands[],
932     size_t *                    commandIndexPtr
933 )
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).
937     // 
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.
940 {
941         OSStatus                                        retval = noErr;
942     CFStringRef                 commandStr;
943     char *                      command;
944         UInt32                                          commandSize = 0;
945         size_t                                          index = 0;
946         
947         // Pre-conditions
948         
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);
953     
954     command = NULL;
955
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.
958     
959     commandStr = CFDictionaryGetValue(request, CFSTR(kBASCommandKey));
960     if ( (commandStr == NULL) || (CFGetTypeID(commandStr) != CFStringGetTypeID()) ) {
961         retval = paramErr;
962     }
963         commandSize = CFStringGetLength(commandStr);
964         if ( (retval == noErr) && (commandSize > 1024) ) {
965                 retval = paramErr;
966         }
967     if (retval == noErr) {
968         size_t      bufSize;
969         
970         bufSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(commandStr), kCFStringEncodingUTF8) + 1;
971         command = malloc(bufSize);
972         
973         if (command == NULL) {
974             retval = memFullErr;
975         } else if ( ! CFStringGetCString(commandStr, command, bufSize, kCFStringEncodingUTF8) ) {
976             retval = coreFoundationUnknownErr;
977         }
978     }
979     
980     // Search the commands array for that command.
981     
982     if (retval == noErr) {
983         do {
984             if ( strcmp(commands[index].commandName, command) == 0 ) {
985                 *commandIndexPtr = index;
986                 break;
987             }
988             index += 1;
989             if (commands[index].commandName == NULL) {
990                 retval = BASErrnoToOSStatus(ENOENT);
991                 break;
992             }
993         } while (true);
994     }
995
996     free(command);
997
998         return retval;
999 }
1000
1001 /////////////////////////////////////////////////////////////////
1002 #pragma mark ***** Tool Code
1003
1004 /*
1005     Watchdog Timer
1006     --------------
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.
1012     
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.
1019     
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.
1024 */
1025
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.
1029 {
1030     (void) alarm(kWatchdogTimeoutInSeconds);
1031 }
1032
1033 static void DisableWatchdog(void)
1034     // Disable the watchdog timer.
1035 {
1036     (void) alarm(0);
1037 }
1038
1039 #if ! defined(NDEBUG)
1040     
1041     static bool CommandArraySizeMatchesCommandProcArraySize(
1042         const BASCommandSpec            commands[], 
1043         const BASCommandProc            commandProcs[]
1044     )
1045     {
1046         size_t  commandCount;
1047         size_t  procCount;
1048         
1049         commandCount = 0;
1050         while ( commands[commandCount].commandName != NULL ) {
1051             commandCount += 1;
1052         }
1053         
1054         procCount = 0;
1055         while ( commandProcs[procCount] != NULL ) {
1056             procCount += 1;
1057         }
1058         
1059         return (commandCount == procCount);
1060     }
1061     
1062 #endif
1063
1064 /*
1065     On-The-'Wire' Protocol
1066     ----------------------
1067     The on-the-'wire' protocol for a BetterAuthorizationSampleLib connection (from the 
1068     perspective of the client) is:
1069     
1070     connect
1071     
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)
1075
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
1080         write 1 byte ('A')
1081
1082     close
1083 */
1084
1085 static int HandleConnection(
1086     aslclient                   asl,
1087     aslmsg                      aslMsg,
1088         const BASCommandSpec            commands[], 
1089         const BASCommandProc            commandProcs[],
1090     int                         fd
1091 )
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.
1097 {
1098     int                         retval;
1099     OSStatus                    junk;
1100     int                         junkInt;
1101     AuthorizationExternalForm   extAuth;
1102     AuthorizationRef                    auth            = NULL;
1103     CFDictionaryRef                             request         = NULL;
1104     size_t                      commandIndex;
1105     CFMutableDictionaryRef              response        = NULL;
1106     OSStatus                    commandProcStatus;
1107     
1108     // Pre-conditions
1109
1110     // asl may be NULL
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) );
1116     assert(fd >= 0);
1117     
1118     // Read in the external authorization reference.
1119     retval = BASRead(fd, &extAuth, sizeof(extAuth), NULL);
1120     
1121     // Internalize external authorization reference.
1122     if (retval == 0) {
1123         retval = BASOSStatusToErrno( AuthorizationCreateFromExternalForm(&extAuth, &auth) );
1124     }
1125     
1126     // Read in CFDictionaryRef request (the command and its arguments).
1127     if (retval == 0) {
1128         retval = BASReadDictionary(fd, &request);
1129     }
1130     
1131     // Create a mutable response dictionary before calling the client.
1132     if (retval == 0) {
1133         response = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1134         if (response == NULL) {
1135             retval = BASOSStatusToErrno( coreFoundationUnknownErr );
1136         }
1137     }
1138
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.
1145     //
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.
1149     
1150     if (retval == 0) {        
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).
1154
1155         commandProcStatus = FindCommand(request, commands, &commandIndex);
1156         
1157         // Acquire the associated right for the command.  If rightName is NULL, the 
1158                 // commandProc is required to do its own authorization.
1159         
1160         if ( (commandProcStatus == noErr) && (commands[commandIndex].rightName != NULL) ) {
1161             AuthorizationItem   item   = { commands[commandIndex].rightName, 0, NULL, 0 };
1162             AuthorizationRights rights = { 1, &item };
1163             
1164             commandProcStatus = AuthorizationCopyRights(
1165                 auth, 
1166                 &rights, 
1167                 kAuthorizationEmptyEnvironment, 
1168                 kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, 
1169                 NULL
1170             );
1171         }
1172     
1173         // Call callback to execute command based on the request.
1174         
1175         if (commandProcStatus == noErr) {
1176             commandProcStatus = commandProcs[commandIndex](auth, commands[commandIndex].userData, request, response, asl, aslMsg);
1177
1178             if (commandProcStatus == noErr) {
1179                 junkInt = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Command callback succeeded");
1180                 assert(junkInt == 0);
1181             } else {
1182                 junkInt = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Command callback failed: %ld", (long) commandProcStatus);
1183                 assert(junkInt == 0);
1184             }
1185         }
1186
1187         // If the command didn't insert its own error value, we use its function 
1188         // result as the error value.
1189         
1190         if ( ! CFDictionaryContainsKey(response, CFSTR(kBASErrorKey)) ) {
1191             CFNumberRef     numRef;
1192             
1193             numRef = CFNumberCreate(NULL, kCFNumberSInt32Type, &commandProcStatus);
1194             if (numRef == NULL) {
1195                 retval = BASOSStatusToErrno( coreFoundationUnknownErr );
1196             } else {
1197                 CFDictionaryAddValue(response, CFSTR(kBASErrorKey), numRef);
1198                 CFRelease(numRef);
1199             }
1200         }
1201     }
1202                                                                     
1203     // Write response back to the client.
1204     if (retval == 0) {
1205         retval = BASWriteDictionaryAndDescriptors(response, fd);
1206     }
1207     
1208     // Clean up.
1209     
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);
1215     }
1216     if (request != NULL) {
1217         CFRelease(request);
1218     }
1219     if (auth != NULL) {
1220         junk = AuthorizationFree(auth, kAuthorizationFlagDefaults);
1221         assert(junk == noErr);
1222     }
1223     
1224     return retval;
1225 }
1226
1227 #if !defined(NDEBUG)
1228
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:
1232         //
1233         // $ sudo launchctl stop com.example.BetterAuthorizationSample
1234         // $ sudo launchctl setenv BASWaitForDebugger 1
1235     {
1236         int         err;
1237         const char *value;
1238         
1239         // asl may be NULL
1240         // aslMsg may be NULL
1241         
1242         value = getenv("BASWaitForDebugger");
1243         if ( ((value != NULL) && (atoi(value) != 0)) ) {
1244             err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Waiting for debugger");
1245             assert(err == 0);
1246             (void) pause();
1247         }
1248     }
1249
1250 #endif
1251
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.
1258 {
1259     int             err;
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;
1265     int             fd = -1;
1266     
1267     // Pre-conditions
1268
1269     // asl may be NULL
1270     // aslMsg may be NULL
1271     assert( errStrPtr != NULL);
1272     assert(*errStrPtr == NULL);
1273     
1274         // Check in with launchd.  Create a checkin request, then run it, then 
1275     // check if we got an error.
1276     
1277     checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN);
1278         if (checkinRequest == NULL) {
1279         *errStrPtr = "Could not create checkin request: %m";
1280                 goto done;
1281         }
1282     checkinResponse = launch_msg(checkinRequest);
1283         if (checkinResponse == NULL) {
1284         *errStrPtr = "Error checking in: %m";
1285                 goto done;
1286         }
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";
1290                 goto done;
1291         }
1292         
1293         // Retrieve the dictionary of sockets entries from the job.  This corresponds to the 
1294     // value of the "Sockets" key in our plist file.
1295
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";
1299                 goto done;
1300         }
1301         if (launch_data_get_type(socketsDict) != LAUNCH_DATA_DICTIONARY) {
1302         *errStrPtr = "Could not get socket dictionary from checkin response: Type mismatch";
1303                 goto done;
1304         }
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");
1307         assert(err == 0);
1308         }
1309         
1310         // Get the dictionary value from the key "MasterSocket", as defined in the launchd 
1311         // property list file.
1312
1313         fdArray = launch_data_dict_lookup(socketsDict, kLaunchDSocketDictKey);
1314         if (fdArray == NULL) {
1315         *errStrPtr = "Could not get file descriptor array: %m";
1316                 goto done;
1317         }
1318         if (launch_data_get_type(fdArray) != LAUNCH_DATA_ARRAY) {
1319         *errStrPtr = "Could not get file descriptor array: Type mismatch";
1320                 goto done;
1321         }
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");
1324         assert(err == 0);
1325         }
1326         
1327         // Get the socket file descriptor from the array.
1328
1329     fdData = launch_data_array_get_index(fdArray, 0);
1330     if (fdData == NULL) {
1331         *errStrPtr = "Could not get file descriptor array entry: %m";
1332                 goto done;
1333     }
1334     if (launch_data_get_type(fdData) != LAUNCH_DATA_FD) {
1335         *errStrPtr = "Could not get file descriptor array entry: Type mismatch";
1336                 goto done;
1337     }
1338     fd = launch_data_get_fd(fdData);
1339     assert(fd >= 0);
1340
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.
1343     
1344     if (false) {
1345         err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Listening descriptor is %d", fd);
1346         assert(err == 0);
1347     }
1348     
1349 done:
1350     if (checkinResponse != NULL) {
1351         launch_data_free(checkinResponse);
1352     }
1353     if (checkinRequest != NULL) {
1354         launch_data_free(checkinRequest);
1355     }
1356     
1357     return fd;
1358 }
1359
1360 static int SetNonBlocking(int fd, Boolean nonBlocking)
1361     // Sets the non-blocking state of fd.
1362 {
1363     int     err;
1364     int     flags;
1365
1366     // Pre-conditions
1367
1368     assert(fd >= 0);
1369
1370     // Get the flags.
1371     
1372     err = 0;
1373     flags = fcntl(fd, F_GETFL);
1374     if (flags < 0) {
1375         err = errno;
1376     }
1377     
1378     // If the current state of O_NONBLOCK doesn't match the required 
1379     // state, toggle that flag and set it back.
1380     
1381     if ( (err == 0) && (((flags & O_NONBLOCK) != 0) != nonBlocking) ) {
1382         flags ^= O_NONBLOCK;
1383         err = fcntl(fd, F_SETFL, flags);
1384         if (err < 0) {
1385             err = errno;
1386         }
1387     }
1388     
1389     return err;
1390 }
1391
1392 extern int BASHelperToolMain(
1393         const BASCommandSpec            commands[], 
1394         const BASCommandProc            commandProcs[]
1395 )
1396     // See comment in header.
1397 {
1398     const char *                errStr = NULL;
1399     int                         err;
1400         aslclient                                       asl = NULL;
1401         aslmsg                                          aslMsg = NULL;
1402         sig_t                                           pipeSet;
1403     int                         listener;
1404         int                                                     kq;
1405         struct kevent                           initEvent;
1406         
1407         // Pre-conditions
1408         
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) );
1413         
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).
1417     
1418         asl     = asl_open(NULL, "HelperTools", ASL_OPT_STDERR);
1419     assert(asl != NULL);
1420     
1421         aslMsg = asl_new(ASL_TYPE_MSG);
1422     assert(aslMsg != NULL);
1423
1424     err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Starting up");
1425     assert(err == 0);
1426
1427     #if !defined(NDEBUG)
1428         WaitForDebugger(asl, aslMsg);
1429     #endif
1430         
1431         // Set up the signal handlers we are interested in.
1432     //
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.
1436     //
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.
1439     //
1440     // o SIGPIPE -- We don't want to quit when write to a dead socket, so we 
1441     //   ignore this signal.
1442         
1443     pipeSet = signal(SIGPIPE, SIG_IGN);
1444     if (pipeSet == SIG_ERR) {
1445         errStr = "Could not ignore SIGPIPE: %m";
1446         goto done;
1447     }
1448         
1449     // Check in with launchd and get our listening socket.
1450     
1451     listener = CheckInWithLaunchd(asl, aslMsg, &errStr);
1452     if (listener < 0) {
1453         assert(errStr != NULL);
1454         goto done;
1455     }
1456
1457     // Create a kqueue and wrap the listening socket in it.
1458
1459     kq = kqueue();
1460         if (kq < 0) {
1461         errStr = "Could not create kqueue: %m";
1462                 goto done;
1463         }
1464
1465     EV_SET(&initEvent, listener, EVFILT_READ, EV_ADD, 0, 0, NULL);
1466     err = kevent(kq, &initEvent, 1, NULL, 0, NULL);
1467     if (err < 0) {
1468         errStr = "Could not add listening socket to kqueue: %m";
1469         goto done;
1470     }
1471         
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.
1476
1477     err = SetNonBlocking(listener, true);
1478     if (err != 0) {
1479         errno = err;            // for %m
1480         errStr = "Could not check/set socket flags: %m";
1481         goto done;
1482     }
1483     
1484         // Loop servicing connection requests one at a time.
1485     
1486     while (true) {
1487         int                         eventCount;
1488         struct kevent               thisEvent;
1489                 int                         thisConnection;
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 };
1494         
1495                 // Wait on the kqueue for a connection request.
1496
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.
1500             break;
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";
1504             goto done;
1505         }
1506
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.
1509         
1510         EnableWatchdog();
1511                 
1512         // The accept should never get stuck because this is a non-blocking 
1513         // socket.
1514         
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 
1520                 // and don't quit.
1521                 err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Connection disappeared before we could accept it: %m");
1522                 assert(err == 0);
1523             } else {
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";
1527                 goto done;
1528             }
1529         }
1530
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.
1533
1534         if (thisConnection != -1) {
1535             err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Request started");
1536             assert(err == 0);
1537             
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.
1541             
1542             thisConnectionError = BASErrnoToOSStatus( SetNonBlocking(thisConnection, false) );
1543
1544             // Entering heavy liftiing.  We have a separate routine to actually 
1545             // read the request from the connection, call the client, and send 
1546             // the reply.
1547
1548             if (thisConnectionError == noErr) {
1549                 thisConnectionError = HandleConnection(asl, aslMsg, commands, commandProcs, thisConnection);
1550             }
1551
1552             err = close(thisConnection);
1553             assert(err == 0);
1554
1555             if (thisConnectionError == 0) {
1556                 err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Request finished");
1557             } else {
1558                 errno = thisConnectionError;            // so it can be picked up by %m
1559                 err = asl_log(asl, aslMsg, ASL_LEVEL_ERR, "Request failed: %m");
1560             }
1561             assert(err == 0);
1562         }
1563
1564         DisableWatchdog();
1565         }
1566         
1567 done:
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.
1570     
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.
1574     
1575     if (errStr != NULL) {
1576         err = asl_log(asl, aslMsg, ASL_LEVEL_ERR, errStr);
1577         assert(err == 0);
1578     }
1579     err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Shutting down");
1580     assert(err == 0);
1581     return (errStr == NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
1582 }
1583
1584 /////////////////////////////////////////////////////////////////
1585 #pragma mark ***** App Code
1586
1587 extern void BASSetDefaultRules(
1588         AuthorizationRef                        auth,
1589         const BASCommandSpec            commands[],
1590         CFStringRef                                     bundleID,
1591         CFStringRef                                     descriptionStringTableName
1592 )
1593     // See comment in header.
1594 {       
1595         OSStatus                                        err;
1596     CFBundleRef                 bundle;
1597         size_t                                          commandIndex;
1598         
1599         // Pre-conditions
1600         
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
1606     
1607     bundle = CFBundleGetBundleWithIdentifier(bundleID);
1608     assert(bundle != NULL);
1609         
1610     // For each command, set up the default authorization right specification, as 
1611     // indicated by the command specification.
1612     
1613     commandIndex = 0;
1614     while (commands[commandIndex].commandName != NULL) {
1615         // Some no-obvious assertions:
1616         
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.
1619
1620         assert( (commands[commandIndex].rightName == NULL) == (commands[commandIndex].rightDefaultRule == NULL) );
1621
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).
1625
1626         assert( (commands[commandIndex].rightName != NULL) || (commands[commandIndex].rightDescriptionKey == NULL) );
1627         
1628         // If there's a right name but no current right specification, set up the 
1629         // right specification.
1630         
1631         if (commands[commandIndex].rightName != NULL) {
1632             err = AuthorizationRightGet(commands[commandIndex].rightName, (CFDictionaryRef*) NULL);
1633             if (err == errAuthorizationDenied) {
1634                 CFStringRef thisDescription;
1635                 CFStringRef     thisRule;
1636                 
1637                 // The right is not already defined.  Set up a definition based on 
1638                 // the fields in the command specification.
1639                 
1640                 thisRule = CFStringCreateWithCString(
1641                     kCFAllocatorDefault, 
1642                     commands[commandIndex].rightDefaultRule, 
1643                     kCFStringEncodingUTF8
1644                 );
1645                 assert(thisRule != NULL);
1646                 
1647                 thisDescription = NULL;
1648                 if (commands[commandIndex].rightDescriptionKey != NULL) {
1649                     thisDescription = CFStringCreateWithCString (
1650                         kCFAllocatorDefault, 
1651                         commands[commandIndex].rightDescriptionKey, 
1652                         kCFStringEncodingUTF8
1653                     );
1654                     assert(thisDescription != NULL);
1655                 }
1656                 
1657                 err = AuthorizationRightSet(
1658                     auth,                                                                               // authRef
1659                     commands[commandIndex].rightName,           // rightName
1660                     thisRule,                                   // rightDefinition
1661                     thisDescription,                                                    // descriptionKey
1662                     bundle,                                     // bundle
1663                     descriptionStringTableName                                  // localeTableName
1664                 );                                                                                              // NULL indicates "Localizable.strings"
1665                 assert(err == noErr);
1666                 
1667                 if (thisDescription != NULL) {
1668                                         CFRelease(thisDescription);
1669                                 }
1670                 if (thisRule != NULL) {
1671                                         CFRelease(thisRule);
1672                                 }
1673             } else { 
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 
1677                 // us to do.
1678             }
1679         }
1680         commandIndex += 1;
1681         }
1682 }
1683
1684 extern OSStatus BASExecuteRequestInHelperTool(
1685         AuthorizationRef                        auth,
1686         const BASCommandSpec            commands[],
1687         CFStringRef                                     bundleID,
1688         CFDictionaryRef                         request,
1689         CFDictionaryRef *                       response
1690 )
1691     // See comment in header.
1692 {       
1693         OSStatus                                        retval = noErr;
1694     int                         junk;
1695     size_t                      commandIndex;
1696     char                        bundleIDC[PATH_MAX];
1697         int                                                     fd = -1;
1698         struct sockaddr_un                      addr;
1699         AuthorizationExternalForm       extAuth;
1700         
1701         // Pre-conditions
1702         
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);
1710     
1711         // For debugging.
1712
1713         assert(CFDictionaryContainsKey(request, CFSTR(kBASCommandKey)));
1714         assert(CFGetTypeID(CFDictionaryGetValue(request, CFSTR(kBASCommandKey))) == CFStringGetTypeID());
1715
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.
1721     
1722     retval = FindCommand(request, commands, &commandIndex);
1723
1724     #if !defined(BAS_PREAUTHORIZE)
1725         #define BAS_PREAUTHORIZE 1
1726     #endif
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 };
1731             
1732             retval = AuthorizationCopyRights(auth, &rights, kAuthorizationEmptyEnvironment, kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize, NULL);
1733         }
1734     #endif
1735
1736     // Create the socket and tell it to not generate SIGPIPE.
1737     
1738         if (retval == noErr) {
1739                 fd = socket(AF_UNIX, SOCK_STREAM, 0);
1740                 if (fd == -1) { 
1741                         retval = BASErrnoToOSStatus(errno);
1742                 }
1743         }
1744         if (retval == noErr) {
1745                 static const int kOne = 1;
1746                 
1747                 if ( setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &kOne, sizeof(kOne)) < 0 ) {
1748                         retval = BASErrnoToOSStatus(errno);
1749                 }
1750         }
1751     
1752     // Form the socket address, including a path based on the bundle ID.
1753         
1754         if (retval == noErr) {
1755         if ( ! CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC)) ) {
1756             retval = coreFoundationUnknownErr;
1757         }
1758     }
1759     if (retval == noErr) {
1760         int         pathLen;
1761         
1762                 memset(&addr, 0, sizeof(addr));
1763         
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
1768         } else {
1769                         addr.sun_len = SUN_LEN(&addr);
1770                 }
1771     }
1772     
1773     // Attempt to connect.
1774     
1775     if (retval == noErr) {
1776                 if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
1777                         retval = BASErrnoToOSStatus(errno);
1778                 }
1779         }
1780         
1781     // Send the flattened AuthorizationRef to the tool.
1782     
1783     if (retval == noErr) {
1784         retval = AuthorizationMakeExternalForm(auth, &extAuth);
1785     }
1786         if (retval == noErr) {  
1787                 retval = BASErrnoToOSStatus( BASWrite(fd, &extAuth, sizeof(extAuth), NULL) );
1788         }
1789         
1790     // Write the request.
1791     
1792         if (retval == noErr) {  
1793                 retval = BASErrnoToOSStatus( BASWriteDictionary(request, fd) );
1794         }
1795         
1796     // Read response, including any descriptors.
1797     
1798         if (retval == noErr) {
1799                 retval = BASErrnoToOSStatus( BASReadDictioanaryTranslatingDescriptors(fd, response) );
1800     }
1801     
1802     // Clean up.
1803
1804     if (fd != -1) {
1805         junk = close(fd);
1806         assert(junk == 0);
1807     }
1808     NormaliseOSStatusErrorCode(&retval);
1809     
1810     assert( (retval == noErr) == (*response != NULL) );
1811     
1812         return retval;
1813 }
1814
1815 extern OSStatus BASGetErrorFromResponse(CFDictionaryRef response)
1816     // See comment in header.
1817 {
1818         OSStatus        err;
1819         CFNumberRef num;
1820         
1821         assert(response != NULL);
1822         
1823         num = (CFNumberRef) CFDictionaryGetValue(response, CFSTR(kBASErrorKey));
1824     err = noErr;
1825     if ( (num == NULL) || (CFGetTypeID(num) != CFNumberGetTypeID()) ) {
1826         err = coreFoundationUnknownErr;
1827     }
1828         if (err == noErr) {
1829                 if ( ! CFNumberGetValue(num, kCFNumberSInt32Type, &err) ) {
1830             err = coreFoundationUnknownErr;
1831         }
1832         }
1833         
1834     NormaliseOSStatusErrorCode(&err);
1835         return err;
1836 }
1837
1838 extern BASFailCode BASDiagnoseFailure(
1839         AuthorizationRef                        auth,
1840         CFStringRef                                     bundleID
1841 )
1842     // See comment in header.
1843 {       
1844     BASFailCode                 retval = kBASFailUnknown;
1845     int                         err;
1846     int                         pathLen;
1847     char                        bundleIDC   [ PATH_MAX ];
1848         char                                            toolPath        [ PATH_MAX ];
1849         char                                            plistPath       [ PATH_MAX ];
1850                 
1851         struct stat                                     fileStatus;
1852         int                                                     toolErr; 
1853         int                                                     plistErr;
1854         int                                                     fd;
1855         struct sockaddr_un                      addr;
1856         
1857         // Pre-conditions
1858         
1859         assert(auth != NULL);
1860         assert(bundleID != NULL);
1861         
1862     // Construct paths to the tool and plist.
1863     
1864     if ( CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC)) ) {
1865
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
1868
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
1871         
1872         // Check if files exist at those paths.
1873         
1874         toolErr  = stat(toolPath,  &fileStatus);
1875         plistErr = stat(plistPath, &fileStatus);
1876         
1877         if ( (toolErr == 0) && (plistErr == 0) ) {
1878             // If both items are present, try to connect and see what we get.
1879             
1880             fd = socket(AF_UNIX, SOCK_STREAM, 0);
1881             if (fd != -1) { 
1882                 memset(&addr, 0, sizeof(addr));
1883             
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);
1887             
1888                 // Attempt to connect to the socket.  If we get ECONNREFUSED, it means no one is 
1889                 // listening.
1890                 
1891                 if ( (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) && (errno == ECONNREFUSED) ) {
1892                     retval = kBASFailDisabled;
1893                 }
1894                 err = close(fd);
1895                 assert(err == 0);
1896             }
1897         } else {
1898             if ( (toolErr == 0) || (plistErr == 0) ) {
1899                 retval = kBASFailPartiallyInstalled;
1900             } else {
1901                 retval = kBASFailNotInstalled;
1902             }
1903         }
1904     }
1905         
1906         return retval;
1907 }
1908
1909 // kPlistTemplate is a template for our launchd.plist file.
1910
1911 static const char * kPlistTemplate =
1912     // The standard plist header.
1913     
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"
1917     "<dict>\n"
1918
1919     // We install the job disabled, then enable it as the last step.
1920
1921     "   <key>Disabled</key>\n"
1922     "   <true/>\n"
1923
1924     // Use the bundle identifier as the job label.
1925
1926     "   <key>Label</key>\n"
1927     "   <string>%s</string>\n"
1928
1929     // Use launch on demaind.
1930
1931     "   <key>OnDemand</key>\n"
1932     "   <true/>\n"
1933
1934     // There are no program arguments, other that the path to the helper tool itself.
1935     //
1936     // IMPORTANT
1937     // kBASToolPathFormat embeds a %s
1938
1939     "   <key>ProgramArguments</key>\n"
1940     "   <array>\n"
1941     "           <string>" kBASToolPathFormat "</string>\n"
1942     "   </array>\n"
1943
1944     // The tool is required to check in with launchd.
1945
1946     "   <key>ServiceIPC</key>\n"
1947     "   <true/>\n"
1948
1949     // This specifies the UNIX domain socket used to launch the tool, including 
1950     // the permissions on the socket (438 is 0666).
1951     //
1952     // IMPORTANT
1953     // kBASSocketPathFormat embeds a %s
1954
1955     "   <key>Sockets</key>\n"
1956     "   <dict>\n"
1957     "           <key>" kLaunchDSocketDictKey "</key>\n"
1958     "           <dict>\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"
1967     "           </dict>\n"
1968     "   </dict>\n"
1969     "</dict>\n"
1970     "</plist>\n"
1971     ;
1972         
1973
1974 //  Installation
1975 //  ------------
1976 //  We install by running our "InstallTool" using AuthorizationExecuteWithPrivileges 
1977 //  (AEWP) and passing the relevant parameters to it through AEWP.
1978 //    
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 
1983 //  EUID == 0.
1984 //    
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 
1989 //  casual hacking. 
1990 //    
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.
1997 //  
1998 //  The obvious disadvantages stem from the first advantage of the former, namely,
1999 //  it's a little more coding and accounting effort (-:
2000 //
2001 //
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.
2011 //
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.
2019 //
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.
2024
2025 #if !defined(NDEBUG)
2026
2027     static Boolean gBASLogInteractions = false;
2028         // Set gBASLogInteractions to have BASFixFailure log its interactions with 
2029         // the installation tool to stderr.
2030
2031     static Boolean gBASLogInteractionsInitialised = false;
2032         // This indicates whether we've initialised gBASLogInteractions from the 
2033                 // environment variable.
2034
2035 #endif
2036
2037 static OSStatus RunInstallToolAsRoot(
2038         AuthorizationRef                        auth, 
2039     const char *                installToolPath,
2040         const char *                            command, 
2041                                                                 ...
2042 )
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.
2046 {
2047     OSStatus    retval;
2048     size_t      argCount;
2049     size_t      argIndex;
2050     va_list     ap;
2051     char **     args;
2052     Boolean     success;
2053     FILE *      channel;
2054     int         junk;
2055         pid_t           childPID;
2056
2057     // Pre-conditions
2058     
2059     assert(auth != NULL);
2060     assert(installToolPath != NULL);
2061     assert(command != NULL);
2062     
2063     channel = NULL;
2064     args    = NULL;
2065         childPID = -1;
2066     
2067     // Count the number of arguments.
2068     
2069     argCount = 0;
2070     va_start(ap, command);
2071     while ( va_arg(ap, char *) != NULL ) {
2072         argCount += 1;
2073     }
2074     va_end(ap);
2075
2076     // Allocate an argument array and populate it, checking each argument along the way.
2077     
2078     retval = noErr;
2079     args = calloc(argCount + 3, sizeof(char *));        // +3 for installToolPath, command and trailing NULL
2080     if (args == NULL) {
2081         retval = memFullErr;
2082     }
2083     if (retval == noErr) {
2084         argIndex = 0;
2085
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);
2093         do {
2094             args[argIndex] = va_arg(ap, char *);
2095             if (args[argIndex] == NULL) {
2096                 break;
2097             }
2098             argIndex += 1;
2099         } while (true);
2100         va_end(ap);
2101     }
2102     
2103     // Go go gadget AEWP!
2104     
2105     if (retval == noErr) {
2106         #if !defined(NDEBUG)
2107                         if ( ! gBASLogInteractionsInitialised ) {
2108                                 const char *    value;
2109                                 
2110                                 value = getenv("BASLogInteractions");
2111                                 gBASLogInteractions = ( ((value != NULL) && (atoi(value) != 0)) );
2112                                 
2113                                 gBASLogInteractionsInitialised = true;
2114                         }
2115                 
2116             if (gBASLogInteractions) {
2117                 argIndex = 0;
2118                 while (args[argIndex] != NULL) {
2119                     fprintf(stderr, "args[%zd] = %s\n", argIndex, args[argIndex]);
2120                     argIndex += 1;
2121                 }
2122             }
2123         #endif
2124         retval = AuthorizationExecuteWithPrivileges(auth, args[0], kAuthorizationFlagDefaults, &args[1], &channel);
2125     }
2126     
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.
2129     //
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.
2132
2133     if (retval == noErr) {
2134         char    thisLine[1024];
2135                 long    tmpLong;
2136         int     tmpInt;
2137
2138         // This loops is a little more complex than you might expect.  There are 
2139         // a number of reasons for this:
2140         //
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).
2144         //
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.
2149         //
2150         // o Because we're parsing special tokens anyway, we might as well extract 
2151         //   the real error code from the failure token.
2152         //
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.
2157         //
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.
2162         //
2163         //       2.     The second copy of launchd prints its status line (that is, 
2164         //              "Workaround Bonjour: 0") well after the tool prints the success 
2165         //      token.
2166         //
2167         //   I solved these problems by parsing each line for the success or failure 
2168         //   token and ignoring any output after that.
2169         //
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").
2173         
2174         do {
2175             success = (fgets(thisLine, sizeof(thisLine), channel) != NULL);
2176             if ( ! success ) {
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 
2180                 // other way.
2181                 retval = errState;
2182                 break;
2183             }
2184             
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.
2189             
2190             #if !defined(NDEBUG)
2191                 if (gBASLogInteractions) {
2192                     fprintf(stderr, ">%s", thisLine);
2193                 }
2194             #endif
2195                         
2196             // Look for the success token and terminate with no error in that case.
2197             
2198                         if (strcmp(thisLine, kBASInstallToolSuccess "\n") == 0) {
2199                 assert(retval == noErr);
2200                                 break;
2201                         }
2202             
2203             // Look for the failure token and extract the error result from that.
2204             
2205             if ( sscanf(thisLine, kBASInstallToolFailure "\n", &tmpInt) == 1 ) {
2206                 retval = BASErrnoToOSStatus( tmpInt );
2207                 if (retval == noErr) {
2208                     assert(false);
2209                     retval = errState;
2210                 }
2211                 break;
2212             }
2213                         
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.
2217                         
2218                         if ( (childPID == -1) && (sscanf(thisLine, kBASAntiZombiePIDToken1 "%ld" kBASAntiZombiePIDToken2 "\n", &tmpLong) == 1) ) {
2219                                 childPID = (pid_t) tmpLong;
2220                         }
2221         } while (true);
2222     }
2223         
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.
2229         
2230         #if !defined(NDEBUG)
2231                 if (gBASLogInteractions) {
2232                         fprintf(stderr, "childPID=%ld\n", (long) childPID);
2233                 }
2234         #endif
2235         if (childPID != -1) {
2236                 pid_t   waitResult;
2237                 int             junkStatus;
2238                 
2239                 do {
2240                         waitResult = waitpid(childPID, &junkStatus, 0);
2241                 } while ( (waitResult < 0) && (errno == EINTR) );
2242         }
2243     
2244     // Clean up.
2245     
2246     if (channel != NULL) {
2247         junk = fclose(channel);
2248         assert(junk == 0);
2249     }
2250     free(args);
2251
2252     NormaliseOSStatusErrorCode(&retval);
2253     return retval;
2254 }
2255
2256 static OSStatus BASInstall(
2257         AuthorizationRef                        auth, 
2258         const char *                            bundleID, 
2259     const char *                installToolPath,
2260     const char *                helperToolPath
2261 )
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".
2265 {
2266     OSStatus    retval;
2267     int         junk;
2268     char *      plistText;
2269     int         fd;
2270     char        plistPath[PATH_MAX];
2271
2272     // Pre-conditions
2273     
2274     assert(auth != NULL);
2275     assert(bundleID != NULL);
2276     assert(installToolPath != NULL);
2277     assert(helperToolPath != NULL);
2278
2279     // Prepare for failure
2280     
2281     plistText = NULL;
2282     fd = -1;
2283     plistPath[0] = 0;
2284
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.
2288     
2289     retval = asprintf(&plistText, kPlistTemplate, bundleID, bundleID, bundleID);
2290     if (retval < 0) {
2291         retval = memFullErr;
2292     } else {
2293         retval = noErr;
2294     }
2295     
2296     // Write the plist to a temporary file.
2297
2298     if (retval == noErr) {
2299         strlcpy(plistPath, "/tmp/BASTemp-XXXXXXXX.plist", sizeof(plistPath));
2300         
2301         fd = mkstemps(plistPath, strlen( strrchr(plistPath, '.') ) );
2302         if (fd < 0) {
2303             retval = BASErrnoToOSStatus( errno );
2304         }
2305     }
2306     if (retval == noErr) {
2307         retval = BASErrnoToOSStatus( BASWrite(fd, plistText, strlen(plistText), NULL) );
2308     }
2309     
2310     // Run the tool as root using AuthorizationExecuteWithPrivileges.
2311     
2312     if (retval == noErr) {
2313         retval = RunInstallToolAsRoot(auth, installToolPath, kBASInstallToolInstallCommand, bundleID, helperToolPath, plistPath, NULL);
2314     }
2315
2316     // Clean up.
2317     
2318     free(plistText);
2319     if (fd != -1) {
2320         junk = close(fd);
2321         assert(junk == 0);
2322         
2323         junk = unlink(plistPath);
2324         assert(junk == 0);
2325     }
2326
2327     return retval;
2328 }
2329
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.
2333 {
2334     OSStatus    err;
2335     CFBundleRef bundle;
2336     Boolean     success;
2337     CFURLRef    toolURL;
2338     
2339     assert(bundleID != NULL);
2340     assert(toolName != NULL);
2341     assert(toolPath != NULL);
2342     assert(toolPathSize > 0);
2343     
2344     toolURL = NULL;
2345     
2346     err = noErr;
2347     bundle = CFBundleGetBundleWithIdentifier(bundleID);
2348     if (bundle == NULL) {
2349         err = coreFoundationUnknownErr;
2350     }
2351     if (err == noErr) {
2352         toolURL = CFBundleCopyAuxiliaryExecutableURL(bundle, toolName);
2353         if (toolURL == NULL) {
2354             err = coreFoundationUnknownErr;
2355         }
2356     }
2357     if (err == noErr) {
2358         success = CFURLGetFileSystemRepresentation(toolURL, true, (UInt8 *) toolPath, toolPathSize);
2359         if ( ! success ) {
2360             err = coreFoundationUnknownErr;
2361         }
2362     }
2363     
2364     if (toolURL != NULL) {
2365         CFRelease(toolURL);
2366     }
2367     
2368     return err;
2369 }
2370
2371 extern OSStatus BASFixFailure(
2372         AuthorizationRef                        auth,
2373         CFStringRef                                     bundleID,
2374         CFStringRef                                     installToolName,
2375         CFStringRef                                     helperToolName,
2376         BASFailCode                                     failCode
2377 )
2378     // See comment in header.
2379 {       
2380         OSStatus    retval;
2381     Boolean     success;
2382     char        bundleIDC[PATH_MAX];
2383     char        installToolPath[PATH_MAX];
2384     char        helperToolPath[PATH_MAX];
2385
2386     // Pre-conditions
2387     
2388     assert(auth != NULL);
2389     assert(bundleID != NULL);
2390     assert(installToolName != NULL);
2391     assert(helperToolName  != NULL);
2392     
2393     // Get the bundle identifier as a UTF-8 C string.  Also, get paths for both of 
2394     // the tools.
2395     
2396     retval = noErr;
2397     success = CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC));
2398     if ( ! success ) {
2399         retval = coreFoundationUnknownErr;
2400     }
2401     if (retval == noErr) {
2402         retval = GetToolPath(bundleID, installToolName, installToolPath, sizeof(installToolPath));
2403     }
2404     if (retval == noErr) {
2405         retval = GetToolPath(bundleID, helperToolName,  helperToolPath,  sizeof(helperToolPath));
2406     }
2407     
2408     // Depending on the failure code, either run the enable command or the install 
2409     // from scratch command.
2410     
2411     if (retval == noErr) {
2412         if (failCode == kBASFailDisabled) {
2413             retval = RunInstallToolAsRoot(auth, installToolPath, kBASInstallToolEnableCommand, bundleIDC, NULL);
2414         } else {
2415             retval = BASInstall(auth, bundleIDC, installToolPath, helperToolPath);
2416         }
2417     }
2418
2419     return retval;
2420 }