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.
47 #import "PrivilegedOperations.h"
48 #import "ConfigurationRights.h"
59 #import <mach-o/dyld.h>
61 #import <AssertMacros.h>
62 #import <Security/Security.h>
63 #import <CoreServices/CoreServices.h>
64 #import <CoreFoundation/CoreFoundation.h>
65 #import <SystemConfiguration/SystemConfiguration.h>
66 #import <Foundation/Foundation.h>
69 static AuthorizationRef gAuthRef = 0;
72 WriteArrayToDynDNS(CFStringRef arrayKey, CFArrayRef domainArray)
74 SCPreferencesRef store;
76 CFDictionaryRef origDict;
77 CFMutableDictionaryRef dict = NULL;
79 CFStringRef scKey = CFSTR("/System/Network/DynamicDNS");
82 // Add domain to the array member ("arrayKey") of the DynamicDNS dictionary
83 // Will replace duplicate, at head of list
84 // At this point, we only support a single-item list
85 store = SCPreferencesCreate(NULL, CFSTR("com.apple.preference.bonjour"), NULL);
86 require_action(store != NULL, SysConfigErr, err=paramErr;);
87 require_action(true == SCPreferencesLock( store, true), LockFailed, err=coreFoundationUnknownErr;);
89 origDict = SCPreferencesPathGetValue(store, scKey);
92 dict = CFDictionaryCreateMutableCopy(NULL, 0, origDict);
97 dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
99 require_action( dict != NULL, NoDict, err=memFullErr;);
101 if (CFArrayGetCount(domainArray) > 0)
103 CFDictionarySetValue(dict, arrayKey, domainArray);
107 CFDictionaryRemoveValue(dict, arrayKey);
110 result = SCPreferencesPathSetValue(store, scKey, dict);
111 require_action(result, SCError, err=kernelPrivilegeErr;);
113 result = SCPreferencesCommitChanges(store);
114 require_action(result, SCError, err=kernelPrivilegeErr;);
115 result = SCPreferencesApplyChanges(store);
116 require_action(result, SCError, err=kernelPrivilegeErr;);
121 SCPreferencesUnlock(store);
130 readTaggedBlock(int fd, u_int32_t *pTag, u_int32_t *pLen, char **ppBuff)
131 // Read tag, block len and block data from stream and return. Dealloc *ppBuff via free().
134 u_int32_t tag, len; // Don't use ssize_t because that's different on 32- vs. 64-bit
137 num = read(fd, &tag, sizeof tag);
138 require_action(num == sizeof tag, GetTagFailed, result = -1;);
139 num = read(fd, &len, sizeof len);
140 require_action(num == sizeof len, GetLenFailed, result = -1;);
142 *ppBuff = (char*) malloc( len);
143 require_action(*ppBuff != NULL, AllocFailed, result = -1;);
145 num = read(fd, *ppBuff, len);
146 if (num == (ssize_t)len)
172 result = readTaggedBlock( fd, &tag, &len, &p);
173 require( result == 0, ReadParamsFailed);
174 require( len == sizeof(AuthorizationExternalForm), ReadParamsFailed);
175 require( len == kAuthorizationExternalFormLength, ReadParamsFailed);
179 (void) AuthorizationFree(gAuthRef, kAuthorizationFlagDefaults);
183 result = AuthorizationCreateFromExternalForm((AuthorizationExternalForm*) p, &gAuthRef);
192 HandleWriteDomain(int fd, int domainType)
194 CFArrayRef domainArray;
195 CFDataRef domainData;
200 AuthorizationItem scAuth = { UPDATE_SC_RIGHT, 0, NULL, 0 };
201 AuthorizationRights authSet = { 1, &scAuth };
203 if (noErr != (result = AuthorizationCopyRights(gAuthRef, &authSet, NULL, (AuthorizationFlags)0, NULL)))
206 result = readTaggedBlock(fd, &tag, &len, &p);
207 require(result == 0, ReadParamsFailed);
209 domainData = CFDataCreate(NULL, (UInt8 *)p, len);
210 domainArray = (CFArrayRef)[NSUnarchiver unarchiveObjectWithData:(NSData *)domainData];
211 CFRelease(domainData);
216 result = WriteArrayToDynDNS(SC_DYNDNS_REGDOMAINS_KEY, domainArray);
220 result = WriteArrayToDynDNS(SC_DYNDNS_BROWSEDOMAINS_KEY, domainArray);
229 HandleWriteHostname(int fd)
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, (const UInt8 *)p, len);
247 domainArray = (CFArrayRef)[NSUnarchiver unarchiveObjectWithData:(NSData *)domainData];
248 result = WriteArrayToDynDNS(SC_DYNDNS_HOSTNAMES_KEY, domainArray);
249 CFRelease(domainData);
258 MyMakeUidAccess(uid_t uid)
260 // make the "uid/gid" ACL subject
261 // this is a CSSM_LIST_ELEMENT chain
262 CSSM_ACL_PROCESS_SUBJECT_SELECTOR selector = {
263 CSSM_ACL_PROCESS_SELECTOR_CURRENT_VERSION, // selector version
264 CSSM_ACL_MATCH_UID, // set mask: match uids (only)
266 0 // gid (not matched here)
268 CSSM_LIST_ELEMENT subject2 = { NULL, 0, 0, {{0,0,0}} };
269 subject2.Element.Word.Data = (UInt8 *)&selector;
270 subject2.Element.Word.Length = sizeof(selector);
271 CSSM_LIST_ELEMENT subject1 = { &subject2, CSSM_ACL_SUBJECT_TYPE_PROCESS, CSSM_LIST_ELEMENT_WORDID, {{0,0,0}} };
274 // rights granted (replace with individual list if desired)
275 CSSM_ACL_AUTHORIZATION_TAG rights[] = {
276 CSSM_ACL_AUTHORIZATION_ANY // everything
278 // owner component (right to change ACL)
279 CSSM_ACL_OWNER_PROTOTYPE owner = {
281 { CSSM_LIST_TYPE_UNKNOWN, &subject1, &subject2 },
285 // ACL entries (any number, just one here)
286 CSSM_ACL_ENTRY_INFO acls =
288 // CSSM_ACL_ENTRY_PROTOTYPE
290 { CSSM_LIST_TYPE_UNKNOWN, &subject1, &subject2 }, // TypedSubject
292 { sizeof(rights) / sizeof(rights[0]), rights }, // Authorization rights for this entry
293 { { 0, 0 }, { 0, 0 } }, // CSSM_ACL_VALIDITY_PERIOD
294 "" // CSSM_STRING EntryTag
300 SecAccessRef a = NULL;
301 (void) SecAccessCreateFromOwnerAndACL(&owner, 1, &acls, &a);
307 MyAddDynamicDNSPassword(SecKeychainRef keychain, SecAccessRef a, UInt32 serviceNameLength, const char *serviceName,
308 UInt32 accountNameLength, const char *accountName, UInt32 passwordLength, const void *passwordData)
310 char * description = DYNDNS_KEYCHAIN_DESCRIPTION;
311 UInt32 descriptionLength = strlen(DYNDNS_KEYCHAIN_DESCRIPTION);
312 UInt32 type = 'ddns';
313 UInt32 creator = 'ddns';
314 UInt32 typeLength = sizeof(type);
315 UInt32 creatorLength = sizeof(creator);
318 // set up attribute vector (each attribute consists of {tag, length, pointer})
319 SecKeychainAttribute attrs[] = { { kSecLabelItemAttr, serviceNameLength, (char *)serviceName },
320 { kSecAccountItemAttr, accountNameLength, (char *)accountName },
321 { kSecServiceItemAttr, serviceNameLength, (char *)serviceName },
322 { kSecDescriptionItemAttr, descriptionLength, (char *)description },
323 { kSecTypeItemAttr, typeLength, (UInt32 *)&type },
324 { kSecCreatorItemAttr, creatorLength, (UInt32 *)&creator } };
325 SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs };
327 err = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attributes, passwordLength, passwordData, keychain, a, NULL);
333 SetKeychainEntry(int fd)
334 // Create a new entry in system keychain, or replace existing
336 CFDataRef secretData;
337 CFDictionaryRef secretDictionary;
338 CFStringRef keyNameString;
339 CFStringRef domainString;
340 CFStringRef secretString;
341 SecKeychainItemRef item = NULL;
345 char keyname[kDNSServiceMaxDomainName];
346 char domain[kDNSServiceMaxDomainName];
347 char secret[kDNSServiceMaxDomainName];
349 AuthorizationItem kcAuth = { EDIT_SYS_KEYCHAIN_RIGHT, 0, NULL, 0 };
350 AuthorizationRights authSet = { 1, &kcAuth };
352 if (noErr != (result = AuthorizationCopyRights(gAuthRef, &authSet, NULL, (AuthorizationFlags)0, NULL)))
355 result = readTaggedBlock(fd, &tag, &len, &p);
356 require_noerr(result, ReadParamsFailed);
358 secretData = CFDataCreate(NULL, (UInt8 *)p, len);
359 secretDictionary = (CFDictionaryRef)[NSUnarchiver unarchiveObjectWithData:(NSData *)secretData];
360 CFRelease(secretData);
363 keyNameString = (CFStringRef)CFDictionaryGetValue(secretDictionary, SC_DYNDNS_KEYNAME_KEY);
364 assert(keyNameString != NULL);
366 domainString = (CFStringRef)CFDictionaryGetValue(secretDictionary, SC_DYNDNS_DOMAIN_KEY);
367 assert(domainString != NULL);
369 secretString = (CFStringRef)CFDictionaryGetValue(secretDictionary, SC_DYNDNS_SECRET_KEY);
370 assert(secretString != NULL);
372 CFStringGetCString(keyNameString, keyname, kDNSServiceMaxDomainName, kCFStringEncodingUTF8);
373 CFStringGetCString(domainString, domain, kDNSServiceMaxDomainName, kCFStringEncodingUTF8);
374 CFStringGetCString(secretString, secret, kDNSServiceMaxDomainName, kCFStringEncodingUTF8);
376 result = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem);
379 result = SecKeychainFindGenericPassword(NULL, strlen(domain), domain, 0, NULL, 0, NULL, &item);
382 result = SecKeychainItemDelete(item);
383 if (result != noErr) fprintf(stderr, "SecKeychainItemDelete returned %d\n", result);
386 result = MyAddDynamicDNSPassword(NULL, MyMakeUidAccess(0), strlen(domain), domain, strlen(keyname)+1, keyname, strlen(secret)+1, secret);
387 if (result != noErr) fprintf(stderr, "MyAddDynamicDNSPassword returned %d\n", result);
388 if (item) CFRelease(item);
396 int main( int argc, char **argv)
397 /* argv[0] is the exec path; argv[1] is a fd for input data; argv[2]... are operation codes.
398 The tool supports the following operations:
399 V -- exit with status PRIV_OP_TOOL_VERS
400 A -- read AuthInfo from input pipe
401 Wd -- write registration domain to dynamic store
402 Wb -- write browse domain to dynamic store
403 Wh -- write hostname to dynamic store
404 Wk -- write keychain entry for given account name
407 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
408 int commFD = -1, iArg, result = 0;
410 if ( 0 != seteuid( 0))
413 if ( argc == 3 && 0 == strcmp( argv[2], "V"))
414 return PRIV_OP_TOOL_VERS;
418 commFD = strtol( argv[1], NULL, 0);
419 lseek( commFD, 0, SEEK_SET);
421 for ( iArg = 2; iArg < argc && result == 0; iArg++)
423 if ( 0 == strcmp( "A", argv[ iArg])) // get auth info
425 result = SetAuthInfo( commFD);
427 else if ( 0 == strcmp( "Wd", argv[ iArg])) // Write registration domain
429 result = HandleWriteDomain( commFD, 1);
431 else if ( 0 == strcmp( "Wb", argv[ iArg])) // Write browse domain
433 result = HandleWriteDomain( commFD, 0);
435 else if ( 0 == strcmp( "Wh", argv[ iArg])) // Write hostname
437 result = HandleWriteHostname( commFD);
439 else if ( 0 == strcmp( "Wk", argv[ iArg])) // Write keychain entry
441 result = SetKeychainEntry( commFD);
448 // Note: The C preprocessor stringify operator ('#') makes a string from its argument, without macro expansion
449 // e.g. If "version" is #define'd to be "4", then STRINGIFY_AWE(version) will return the string "version", not "4"
450 // To expand "version" to its value before making the string, use STRINGIFY(version) instead
451 #define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) #s
452 #define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s)
454 // NOT static -- otherwise the compiler may optimize it out
455 // The "@(#) " pattern is a special prefix the "what" command looks for
456 const char VersionString_SCCS[] = "@(#) ddnswriteconfig " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")";
458 #if _BUILDING_XCODE_PROJECT_
459 // If the process crashes, then this string will be magically included in the automatically-generated crash log
460 const char *__crashreporter_info__ = VersionString_SCCS + 5;
461 asm(".desc ___crashreporter_info__, 0x10");