2 File: ddnswriteconfig.m
4 Abstract: Setuid root tool invoked by Preference Pane to perform
5 privileged accesses to system configuration preferences and the system keychain.
6 Invoked by PrivilegedOperations.c.
8 Copyright: (c) Copyright 2005 Apple Computer, Inc. All rights reserved.
10 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc.
11 ("Apple") in consideration of your agreement to the following terms, and your
12 use, installation, modification or redistribution of this Apple software
13 constitutes acceptance of these terms. If you do not agree with these terms,
14 please do not use, install, modify or redistribute this Apple software.
16 In consideration of your agreement to abide by the following terms, and subject
17 to these terms, Apple grants you a personal, non-exclusive license, under Apple's
18 copyrights in this original Apple software (the "Apple Software"), to use,
19 reproduce, modify and redistribute the Apple Software, with or without
20 modifications, in source and/or binary forms; provided that if you redistribute
21 the Apple Software in its entirety and without modifications, you must retain
22 this notice and the following text and disclaimers in all such redistributions of
23 the Apple Software. Neither the name, trademarks, service marks or logos of
24 Apple Computer, Inc. may be used to endorse or promote products derived from the
25 Apple Software without specific prior written permission from Apple. Except as
26 expressly stated in this notice, no other rights or licenses, express or implied,
27 are granted by Apple herein, including but not limited to any patent rights that
28 may be infringed by your derivative works or by other works in which the Apple
29 Software may be incorporated.
31 The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
32 WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
33 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
34 PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
35 COMBINATION WITH YOUR PRODUCTS.
37 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
38 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
39 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40 ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
41 OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
42 (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
43 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45 Change History (most recent first):
47 $Log: ddnswriteconfig.m,v $
48 Revision 1.13 2008/11/04 20:08:44 cheshire
49 Use constant kDNSServiceMaxDomainName instead of literal value "1005"
51 Revision 1.12 2008/09/15 23:52:30 cheshire
52 <rdar://problem/6218902> mDNSResponder-177 fails to compile on Linux with .desc pseudo-op
53 Made __crashreporter_info__ symbol conditional, so we only use it for OS X build
55 Revision 1.11 2008/06/26 17:34:18 mkrochma
56 <rdar://problem/6030630> Pref pane destroying shared "system.preferences" authorization right
58 Revision 1.10 2007/11/30 23:43:04 cheshire
59 Fixed compile warning: declaration of 'access' shadows a global declaration
61 Revision 1.9 2007/09/18 19:09:02 cheshire
62 <rdar://problem/5489549> mDNSResponderHelper (and other binaries) missing SCCS version strings
64 Revision 1.8 2007/07/20 23:41:03 mkrochma
65 <rdar://problem/5348663> null deref in ddnswriteconfig
67 Revision 1.7 2007/03/07 00:49:00 cheshire
68 <rdar://problem/4618207> Security: ddnswriteconfig does not verify that authorization blob is of correct size
70 Revision 1.6 2007/02/09 00:39:06 cheshire
73 Revision 1.5 2006/08/14 23:15:47 cheshire
74 Tidy up Change History comment
76 Revision 1.4 2005/06/04 04:47:47 cheshire
77 <rdar://problem/4138070> ddnswriteconfig (Bonjour PreferencePane) vulnerability
78 Remove self-installing capability of ddnswriteconfig
80 Revision 1.3 2005/02/16 00:17:35 cheshire
81 Don't create empty arrays -- CFArrayGetValueAtIndex(array,0) returns an essentially random (non-null)
82 result for empty arrays, which can lead to code crashing if it's not sufficiently defensive.
84 Revision 1.2 2005/02/10 22:35:20 cheshire
85 <rdar://problem/3727944> Update name
87 Revision 1.1 2005/02/05 01:59:19 cheshire
88 Add Preference Pane to facilitate testing of DDNS & wide-area features
93 #import "PrivilegedOperations.h"
94 #import "ConfigurationRights.h"
102 #import <sys/types.h>
105 #import <mach-o/dyld.h>
107 #import <AssertMacros.h>
108 #import <Security/Security.h>
109 #import <CoreServices/CoreServices.h>
110 #import <CoreFoundation/CoreFoundation.h>
111 #import <SystemConfiguration/SystemConfiguration.h>
112 #import <Foundation/Foundation.h>
115 static AuthorizationRef gAuthRef = 0;
118 WriteArrayToDynDNS(CFStringRef arrayKey, CFArrayRef domainArray)
120 SCPreferencesRef store;
121 OSStatus err = noErr;
122 CFDictionaryRef origDict;
123 CFMutableDictionaryRef dict = NULL;
125 CFStringRef scKey = CFSTR("/System/Network/DynamicDNS");
128 // Add domain to the array member ("arrayKey") of the DynamicDNS dictionary
129 // Will replace duplicate, at head of list
130 // At this point, we only support a single-item list
131 store = SCPreferencesCreate(NULL, CFSTR("com.apple.preference.bonjour"), NULL);
132 require_action(store != NULL, SysConfigErr, err=paramErr;);
133 require_action(true == SCPreferencesLock( store, true), LockFailed, err=coreFoundationUnknownErr;);
135 origDict = SCPreferencesPathGetValue(store, scKey);
137 dict = CFDictionaryCreateMutableCopy(NULL, 0, origDict);
141 dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
143 require_action( dict != NULL, NoDict, err=memFullErr;);
145 if (CFArrayGetCount(domainArray) > 0) {
146 CFDictionarySetValue(dict, arrayKey, domainArray);
148 CFDictionaryRemoveValue(dict, arrayKey);
151 result = SCPreferencesPathSetValue(store, scKey, dict);
152 require_action(result, SCError, err=kernelPrivilegeErr;);
154 result = SCPreferencesCommitChanges(store);
155 require_action(result, SCError, err=kernelPrivilegeErr;);
156 result = SCPreferencesApplyChanges(store);
157 require_action(result, SCError, err=kernelPrivilegeErr;);
162 SCPreferencesUnlock(store);
171 readTaggedBlock(int fd, u_int32_t *pTag, u_int32_t *pLen, char **ppBuff)
172 // Read tag, block len and block data from stream and return. Dealloc *ppBuff via free().
178 num = read(fd, &tag, sizeof tag);
179 require_action(num == sizeof tag, GetTagFailed, result = -1;);
180 num = read(fd, &len, sizeof len);
181 require_action(num == sizeof len, GetLenFailed, result = -1;);
183 *ppBuff = (char*) malloc( len);
184 require_action(*ppBuff != NULL, AllocFailed, result = -1;);
186 num = read(fd, *ppBuff, len);
210 result = readTaggedBlock( fd, &tag, &len, &p);
211 require( result == 0, ReadParamsFailed);
212 require( len == sizeof(AuthorizationExternalForm), ReadParamsFailed);
213 require( len == kAuthorizationExternalFormLength, ReadParamsFailed);
216 (void) AuthorizationFree(gAuthRef, kAuthorizationFlagDefaults);
220 result = AuthorizationCreateFromExternalForm((AuthorizationExternalForm*) p, &gAuthRef);
229 HandleWriteDomain(int fd, int domainType)
231 CFArrayRef domainArray;
232 CFDataRef domainData;
237 AuthorizationItem scAuth = { UPDATE_SC_RIGHT, 0, NULL, 0 };
238 AuthorizationRights authSet = { 1, &scAuth };
240 if (noErr != (result = AuthorizationCopyRights(gAuthRef, &authSet, NULL, (AuthorizationFlags)0, NULL)))
243 result = readTaggedBlock(fd, &tag, &len, &p);
244 require(result == 0, ReadParamsFailed);
246 domainData = CFDataCreate(NULL, (UInt8 *)p, len);
247 domainArray = (CFArrayRef)[NSUnarchiver unarchiveObjectWithData:(NSData *)domainData];
250 result = WriteArrayToDynDNS(SC_DYNDNS_REGDOMAINS_KEY, domainArray);
252 result = WriteArrayToDynDNS(SC_DYNDNS_BROWSEDOMAINS_KEY, domainArray);
261 HandleWriteHostname(int fd)
263 CFArrayRef domainArray;
264 CFDataRef domainData;
269 AuthorizationItem scAuth = { UPDATE_SC_RIGHT, 0, NULL, 0 };
270 AuthorizationRights authSet = { 1, &scAuth };
272 if (noErr != (result = AuthorizationCopyRights(gAuthRef, &authSet, NULL, (AuthorizationFlags) 0, NULL)))
275 result = readTaggedBlock(fd, &tag, &len, &p);
276 require(result == 0, ReadParamsFailed);
278 domainData = CFDataCreate(NULL, (const UInt8 *)p, len);
279 domainArray = (CFArrayRef)[NSUnarchiver unarchiveObjectWithData:(NSData *)domainData];
280 result = WriteArrayToDynDNS(SC_DYNDNS_HOSTNAMES_KEY, domainArray);
288 MyMakeUidAccess(uid_t uid)
290 // make the "uid/gid" ACL subject
291 // this is a CSSM_LIST_ELEMENT chain
292 CSSM_ACL_PROCESS_SUBJECT_SELECTOR selector = {
293 CSSM_ACL_PROCESS_SELECTOR_CURRENT_VERSION, // selector version
294 CSSM_ACL_MATCH_UID, // set mask: match uids (only)
296 0 // gid (not matched here)
298 CSSM_LIST_ELEMENT subject2 = { NULL, 0, 0, {{0,0,0}} };
299 subject2.Element.Word.Data = (UInt8 *)&selector;
300 subject2.Element.Word.Length = sizeof(selector);
301 CSSM_LIST_ELEMENT subject1 = { &subject2, CSSM_ACL_SUBJECT_TYPE_PROCESS, CSSM_LIST_ELEMENT_WORDID, {{0,0,0}} };
304 // rights granted (replace with individual list if desired)
305 CSSM_ACL_AUTHORIZATION_TAG rights[] = {
306 CSSM_ACL_AUTHORIZATION_ANY // everything
308 // owner component (right to change ACL)
309 CSSM_ACL_OWNER_PROTOTYPE owner = {
311 { CSSM_LIST_TYPE_UNKNOWN, &subject1, &subject2 },
315 // ACL entries (any number, just one here)
316 CSSM_ACL_ENTRY_INFO acls =
318 // CSSM_ACL_ENTRY_PROTOTYPE
320 { CSSM_LIST_TYPE_UNKNOWN, &subject1, &subject2 }, // TypedSubject
322 { sizeof(rights) / sizeof(rights[0]), rights }, // Authorization rights for this entry
323 { { 0, 0 }, { 0, 0 } }, // CSSM_ACL_VALIDITY_PERIOD
324 "" // CSSM_STRING EntryTag
330 SecAccessRef a = NULL;
331 (void) SecAccessCreateFromOwnerAndACL(&owner, 1, &acls, &a);
337 MyAddDynamicDNSPassword(SecKeychainRef keychain, SecAccessRef a, UInt32 serviceNameLength, const char *serviceName,
338 UInt32 accountNameLength, const char *accountName, UInt32 passwordLength, const void *passwordData)
340 char * description = DYNDNS_KEYCHAIN_DESCRIPTION;
341 UInt32 descriptionLength = strlen(DYNDNS_KEYCHAIN_DESCRIPTION);
342 UInt32 type = 'ddns';
343 UInt32 creator = 'ddns';
344 UInt32 typeLength = sizeof(type);
345 UInt32 creatorLength = sizeof(creator);
348 // set up attribute vector (each attribute consists of {tag, length, pointer})
349 SecKeychainAttribute attrs[] = { { kSecLabelItemAttr, serviceNameLength, (char *)serviceName },
350 { kSecAccountItemAttr, accountNameLength, (char *)accountName },
351 { kSecServiceItemAttr, serviceNameLength, (char *)serviceName },
352 { kSecDescriptionItemAttr, descriptionLength, (char *)description },
353 { kSecTypeItemAttr, typeLength, (UInt32 *)&type },
354 { kSecCreatorItemAttr, creatorLength, (UInt32 *)&creator } };
355 SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs };
357 err = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attributes, passwordLength, passwordData, keychain, a, NULL);
363 SetKeychainEntry(int fd)
364 // Create a new entry in system keychain, or replace existing
366 CFDataRef secretData;
367 CFDictionaryRef secretDictionary;
368 CFStringRef keyNameString;
369 CFStringRef domainString;
370 CFStringRef secretString;
371 SecKeychainItemRef item = NULL;
375 char keyname[kDNSServiceMaxDomainName];
376 char domain[kDNSServiceMaxDomainName];
377 char secret[kDNSServiceMaxDomainName];
379 AuthorizationItem kcAuth = { EDIT_SYS_KEYCHAIN_RIGHT, 0, NULL, 0 };
380 AuthorizationRights authSet = { 1, &kcAuth };
382 if (noErr != (result = AuthorizationCopyRights(gAuthRef, &authSet, NULL, (AuthorizationFlags)0, NULL)))
385 result = readTaggedBlock(fd, &tag, &len, &p);
386 require_noerr(result, ReadParamsFailed);
388 secretData = CFDataCreate(NULL, (UInt8 *)p, len);
389 secretDictionary = (CFDictionaryRef)[NSUnarchiver unarchiveObjectWithData:(NSData *)secretData];
391 keyNameString = (CFStringRef)CFDictionaryGetValue(secretDictionary, SC_DYNDNS_KEYNAME_KEY);
392 assert(keyNameString != NULL);
394 domainString = (CFStringRef)CFDictionaryGetValue(secretDictionary, SC_DYNDNS_DOMAIN_KEY);
395 assert(domainString != NULL);
397 secretString = (CFStringRef)CFDictionaryGetValue(secretDictionary, SC_DYNDNS_SECRET_KEY);
398 assert(secretString != NULL);
400 CFStringGetCString(keyNameString, keyname, kDNSServiceMaxDomainName, kCFStringEncodingUTF8);
401 CFStringGetCString(domainString, domain, kDNSServiceMaxDomainName, kCFStringEncodingUTF8);
402 CFStringGetCString(secretString, secret, kDNSServiceMaxDomainName, kCFStringEncodingUTF8);
404 result = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem);
405 if (result == noErr) {
406 result = SecKeychainFindGenericPassword(NULL, strlen(domain), domain, 0, NULL, 0, NULL, &item);
407 if (result == noErr) {
408 result = SecKeychainItemDelete(item);
409 if (result != noErr) fprintf(stderr, "SecKeychainItemDelete returned %d\n", result);
412 result = MyAddDynamicDNSPassword(NULL, MyMakeUidAccess(0), strlen(domain), domain, strlen(keyname)+1, keyname, strlen(secret)+1, secret);
413 if (result != noErr) fprintf(stderr, "MyAddDynamicDNSPassword returned %d\n", result);
414 if (item) CFRelease(item);
422 int main( int argc, char **argv)
423 /* argv[0] is the exec path; argv[1] is a fd for input data; argv[2]... are operation codes.
424 The tool supports the following operations:
425 V -- exit with status PRIV_OP_TOOL_VERS
426 A -- read AuthInfo from input pipe
427 Wd -- write registration domain to dynamic store
428 Wb -- write browse domain to dynamic store
429 Wh -- write hostname to dynamic store
430 Wk -- write keychain entry for given account name
433 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
434 int commFD = -1, iArg, result = 0;
436 if ( 0 != seteuid( 0))
439 if ( argc == 3 && 0 == strcmp( argv[2], "V"))
440 return PRIV_OP_TOOL_VERS;
444 commFD = strtol( argv[1], NULL, 0);
445 lseek( commFD, 0, SEEK_SET);
447 for ( iArg = 2; iArg < argc && result == 0; iArg++)
449 if ( 0 == strcmp( "A", argv[ iArg])) // get auth info
451 result = SetAuthInfo( commFD);
453 else if ( 0 == strcmp( "Wd", argv[ iArg])) // Write registration domain
455 result = HandleWriteDomain( commFD, 1);
457 else if ( 0 == strcmp( "Wb", argv[ iArg])) // Write browse domain
459 result = HandleWriteDomain( commFD, 0);
461 else if ( 0 == strcmp( "Wh", argv[ iArg])) // Write hostname
463 result = HandleWriteHostname( commFD);
465 else if ( 0 == strcmp( "Wk", argv[ iArg])) // Write keychain entry
467 result = SetKeychainEntry( commFD);
474 // Note: The C preprocessor stringify operator ('#') makes a string from its argument, without macro expansion
475 // e.g. If "version" is #define'd to be "4", then STRINGIFY_AWE(version) will return the string "version", not "4"
476 // To expand "version" to its value before making the string, use STRINGIFY(version) instead
477 #define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) #s
478 #define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s)
480 // NOT static -- otherwise the compiler may optimize it out
481 // The "@(#) " pattern is a special prefix the "what" command looks for
482 const char VersionString_SCCS[] = "@(#) ddnswriteconfig " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")";
484 #if _BUILDING_XCODE_PROJECT_
485 // If the process crashes, then this string will be magically included in the automatically-generated crash log
486 const char *__crashreporter_info__ = VersionString_SCCS + 5;
487 asm(".desc ___crashreporter_info__, 0x10");