1 | /* |
---|
2 | File: ddnswriteconfig.m |
---|
3 | |
---|
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. |
---|
7 | |
---|
8 | Copyright: (c) Copyright 2005 Apple Computer, Inc. All rights reserved. |
---|
9 | |
---|
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. |
---|
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 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. |
---|
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 | #import "PrivilegedOperations.h" |
---|
48 | #import "ConfigurationRights.h" |
---|
49 | |
---|
50 | #import <stdio.h> |
---|
51 | #import <stdint.h> |
---|
52 | #import <stdlib.h> |
---|
53 | #import <unistd.h> |
---|
54 | #import <fcntl.h> |
---|
55 | #import <errno.h> |
---|
56 | #import <sys/types.h> |
---|
57 | #import <sys/stat.h> |
---|
58 | #import <sys/mman.h> |
---|
59 | #import <mach-o/dyld.h> |
---|
60 | #import <dns_sd.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> |
---|
67 | |
---|
68 | |
---|
69 | static AuthorizationRef gAuthRef = 0; |
---|
70 | |
---|
71 | static OSStatus |
---|
72 | WriteArrayToDynDNS(CFStringRef arrayKey, CFArrayRef domainArray) |
---|
73 | { |
---|
74 | SCPreferencesRef store; |
---|
75 | OSStatus err = noErr; |
---|
76 | CFDictionaryRef origDict; |
---|
77 | CFMutableDictionaryRef dict = NULL; |
---|
78 | Boolean result; |
---|
79 | CFStringRef scKey = CFSTR("/System/Network/DynamicDNS"); |
---|
80 | |
---|
81 | |
---|
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;); |
---|
88 | |
---|
89 | origDict = SCPreferencesPathGetValue(store, scKey); |
---|
90 | if (origDict) |
---|
91 | { |
---|
92 | dict = CFDictionaryCreateMutableCopy(NULL, 0, origDict); |
---|
93 | } |
---|
94 | |
---|
95 | if (!dict) |
---|
96 | { |
---|
97 | dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
---|
98 | } |
---|
99 | require_action( dict != NULL, NoDict, err=memFullErr;); |
---|
100 | |
---|
101 | if (CFArrayGetCount(domainArray) > 0) |
---|
102 | { |
---|
103 | CFDictionarySetValue(dict, arrayKey, domainArray); |
---|
104 | } |
---|
105 | else |
---|
106 | { |
---|
107 | CFDictionaryRemoveValue(dict, arrayKey); |
---|
108 | } |
---|
109 | |
---|
110 | result = SCPreferencesPathSetValue(store, scKey, dict); |
---|
111 | require_action(result, SCError, err=kernelPrivilegeErr;); |
---|
112 | |
---|
113 | result = SCPreferencesCommitChanges(store); |
---|
114 | require_action(result, SCError, err=kernelPrivilegeErr;); |
---|
115 | result = SCPreferencesApplyChanges(store); |
---|
116 | require_action(result, SCError, err=kernelPrivilegeErr;); |
---|
117 | |
---|
118 | SCError: |
---|
119 | CFRelease(dict); |
---|
120 | NoDict: |
---|
121 | SCPreferencesUnlock(store); |
---|
122 | LockFailed: |
---|
123 | CFRelease(store); |
---|
124 | SysConfigErr: |
---|
125 | return err; |
---|
126 | } |
---|
127 | |
---|
128 | |
---|
129 | static int |
---|
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(). |
---|
132 | { |
---|
133 | ssize_t num; |
---|
134 | u_int32_t tag, len; // Don't use ssize_t because that's different on 32- vs. 64-bit |
---|
135 | int result = 0; |
---|
136 | |
---|
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;); |
---|
141 | |
---|
142 | *ppBuff = (char*) malloc( len); |
---|
143 | require_action(*ppBuff != NULL, AllocFailed, result = -1;); |
---|
144 | |
---|
145 | num = read(fd, *ppBuff, len); |
---|
146 | if (num == (ssize_t)len) |
---|
147 | { |
---|
148 | *pTag = tag; |
---|
149 | *pLen = len; |
---|
150 | } |
---|
151 | else |
---|
152 | { |
---|
153 | free(*ppBuff); |
---|
154 | result = -1; |
---|
155 | } |
---|
156 | |
---|
157 | AllocFailed: |
---|
158 | GetLenFailed: |
---|
159 | GetTagFailed: |
---|
160 | return result; |
---|
161 | } |
---|
162 | |
---|
163 | |
---|
164 | |
---|
165 | static int |
---|
166 | SetAuthInfo( int fd) |
---|
167 | { |
---|
168 | int result = 0; |
---|
169 | u_int32_t tag, len; |
---|
170 | char *p; |
---|
171 | |
---|
172 | result = readTaggedBlock( fd, &tag, &len, &p); |
---|
173 | require( result == 0, ReadParamsFailed); |
---|
174 | require( len == sizeof(AuthorizationExternalForm), ReadParamsFailed); |
---|
175 | require( len == kAuthorizationExternalFormLength, ReadParamsFailed); |
---|
176 | |
---|
177 | if (gAuthRef != 0) |
---|
178 | { |
---|
179 | (void) AuthorizationFree(gAuthRef, kAuthorizationFlagDefaults); |
---|
180 | gAuthRef = 0; |
---|
181 | } |
---|
182 | |
---|
183 | result = AuthorizationCreateFromExternalForm((AuthorizationExternalForm*) p, &gAuthRef); |
---|
184 | |
---|
185 | free( p); |
---|
186 | ReadParamsFailed: |
---|
187 | return result; |
---|
188 | } |
---|
189 | |
---|
190 | |
---|
191 | static int |
---|
192 | HandleWriteDomain(int fd, int domainType) |
---|
193 | { |
---|
194 | CFArrayRef domainArray; |
---|
195 | CFDataRef domainData; |
---|
196 | int result = 0; |
---|
197 | u_int32_t tag, len; |
---|
198 | char *p; |
---|
199 | |
---|
200 | AuthorizationItem scAuth = { UPDATE_SC_RIGHT, 0, NULL, 0 }; |
---|
201 | AuthorizationRights authSet = { 1, &scAuth }; |
---|
202 | |
---|
203 | if (noErr != (result = AuthorizationCopyRights(gAuthRef, &authSet, NULL, (AuthorizationFlags)0, NULL))) |
---|
204 | return result; |
---|
205 | |
---|
206 | result = readTaggedBlock(fd, &tag, &len, &p); |
---|
207 | require(result == 0, ReadParamsFailed); |
---|
208 | |
---|
209 | domainData = CFDataCreate(NULL, (UInt8 *)p, len); |
---|
210 | domainArray = (CFArrayRef)[NSUnarchiver unarchiveObjectWithData:(NSData *)domainData]; |
---|
211 | CFRelease(domainData); |
---|
212 | free(p); |
---|
213 | |
---|
214 | if (domainType) |
---|
215 | { |
---|
216 | result = WriteArrayToDynDNS(SC_DYNDNS_REGDOMAINS_KEY, domainArray); |
---|
217 | } |
---|
218 | else |
---|
219 | { |
---|
220 | result = WriteArrayToDynDNS(SC_DYNDNS_BROWSEDOMAINS_KEY, domainArray); |
---|
221 | } |
---|
222 | |
---|
223 | ReadParamsFailed: |
---|
224 | return result; |
---|
225 | } |
---|
226 | |
---|
227 | |
---|
228 | static int |
---|
229 | HandleWriteHostname(int fd) |
---|
230 | { |
---|
231 | CFArrayRef domainArray; |
---|
232 | CFDataRef domainData; |
---|
233 | int result = 0; |
---|
234 | u_int32_t tag, len; |
---|
235 | char *p; |
---|
236 | |
---|
237 | AuthorizationItem scAuth = { UPDATE_SC_RIGHT, 0, NULL, 0 }; |
---|
238 | AuthorizationRights authSet = { 1, &scAuth }; |
---|
239 | |
---|
240 | if (noErr != (result = AuthorizationCopyRights(gAuthRef, &authSet, NULL, (AuthorizationFlags) 0, NULL))) |
---|
241 | return result; |
---|
242 | |
---|
243 | result = readTaggedBlock(fd, &tag, &len, &p); |
---|
244 | require(result == 0, ReadParamsFailed); |
---|
245 | |
---|
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); |
---|
250 | free(p); |
---|
251 | |
---|
252 | ReadParamsFailed: |
---|
253 | return result; |
---|
254 | } |
---|
255 | |
---|
256 | |
---|
257 | static SecAccessRef |
---|
258 | MyMakeUidAccess(uid_t uid) |
---|
259 | { |
---|
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) |
---|
265 | uid, // uid to match |
---|
266 | 0 // gid (not matched here) |
---|
267 | }; |
---|
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}} }; |
---|
272 | |
---|
273 | |
---|
274 | // rights granted (replace with individual list if desired) |
---|
275 | CSSM_ACL_AUTHORIZATION_TAG rights[] = { |
---|
276 | CSSM_ACL_AUTHORIZATION_ANY // everything |
---|
277 | }; |
---|
278 | // owner component (right to change ACL) |
---|
279 | CSSM_ACL_OWNER_PROTOTYPE owner = { |
---|
280 | // TypedSubject |
---|
281 | { CSSM_LIST_TYPE_UNKNOWN, &subject1, &subject2 }, |
---|
282 | // Delegate |
---|
283 | false |
---|
284 | }; |
---|
285 | // ACL entries (any number, just one here) |
---|
286 | CSSM_ACL_ENTRY_INFO acls = |
---|
287 | { |
---|
288 | // CSSM_ACL_ENTRY_PROTOTYPE |
---|
289 | { |
---|
290 | { CSSM_LIST_TYPE_UNKNOWN, &subject1, &subject2 }, // TypedSubject |
---|
291 | false, // Delegate |
---|
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 |
---|
295 | }, |
---|
296 | // CSSM_ACL_HANDLE |
---|
297 | 0 |
---|
298 | }; |
---|
299 | |
---|
300 | SecAccessRef a = NULL; |
---|
301 | (void) SecAccessCreateFromOwnerAndACL(&owner, 1, &acls, &a); |
---|
302 | return a; |
---|
303 | } |
---|
304 | |
---|
305 | |
---|
306 | static OSStatus |
---|
307 | MyAddDynamicDNSPassword(SecKeychainRef keychain, SecAccessRef a, UInt32 serviceNameLength, const char *serviceName, |
---|
308 | UInt32 accountNameLength, const char *accountName, UInt32 passwordLength, const void *passwordData) |
---|
309 | { |
---|
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); |
---|
316 | OSStatus err; |
---|
317 | |
---|
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 }; |
---|
326 | |
---|
327 | err = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attributes, passwordLength, passwordData, keychain, a, NULL); |
---|
328 | return err; |
---|
329 | } |
---|
330 | |
---|
331 | |
---|
332 | static int |
---|
333 | SetKeychainEntry(int fd) |
---|
334 | // Create a new entry in system keychain, or replace existing |
---|
335 | { |
---|
336 | CFDataRef secretData; |
---|
337 | CFDictionaryRef secretDictionary; |
---|
338 | CFStringRef keyNameString; |
---|
339 | CFStringRef domainString; |
---|
340 | CFStringRef secretString; |
---|
341 | SecKeychainItemRef item = NULL; |
---|
342 | int result = 0; |
---|
343 | u_int32_t tag, len; |
---|
344 | char *p; |
---|
345 | char keyname[kDNSServiceMaxDomainName]; |
---|
346 | char domain[kDNSServiceMaxDomainName]; |
---|
347 | char secret[kDNSServiceMaxDomainName]; |
---|
348 | |
---|
349 | AuthorizationItem kcAuth = { EDIT_SYS_KEYCHAIN_RIGHT, 0, NULL, 0 }; |
---|
350 | AuthorizationRights authSet = { 1, &kcAuth }; |
---|
351 | |
---|
352 | if (noErr != (result = AuthorizationCopyRights(gAuthRef, &authSet, NULL, (AuthorizationFlags)0, NULL))) |
---|
353 | return result; |
---|
354 | |
---|
355 | result = readTaggedBlock(fd, &tag, &len, &p); |
---|
356 | require_noerr(result, ReadParamsFailed); |
---|
357 | |
---|
358 | secretData = CFDataCreate(NULL, (UInt8 *)p, len); |
---|
359 | secretDictionary = (CFDictionaryRef)[NSUnarchiver unarchiveObjectWithData:(NSData *)secretData]; |
---|
360 | CFRelease(secretData); |
---|
361 | free(p); |
---|
362 | |
---|
363 | keyNameString = (CFStringRef)CFDictionaryGetValue(secretDictionary, SC_DYNDNS_KEYNAME_KEY); |
---|
364 | assert(keyNameString != NULL); |
---|
365 | |
---|
366 | domainString = (CFStringRef)CFDictionaryGetValue(secretDictionary, SC_DYNDNS_DOMAIN_KEY); |
---|
367 | assert(domainString != NULL); |
---|
368 | |
---|
369 | secretString = (CFStringRef)CFDictionaryGetValue(secretDictionary, SC_DYNDNS_SECRET_KEY); |
---|
370 | assert(secretString != NULL); |
---|
371 | |
---|
372 | CFStringGetCString(keyNameString, keyname, kDNSServiceMaxDomainName, kCFStringEncodingUTF8); |
---|
373 | CFStringGetCString(domainString, domain, kDNSServiceMaxDomainName, kCFStringEncodingUTF8); |
---|
374 | CFStringGetCString(secretString, secret, kDNSServiceMaxDomainName, kCFStringEncodingUTF8); |
---|
375 | |
---|
376 | result = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem); |
---|
377 | if (result == noErr) |
---|
378 | { |
---|
379 | result = SecKeychainFindGenericPassword(NULL, strlen(domain), domain, 0, NULL, 0, NULL, &item); |
---|
380 | if (result == noErr) |
---|
381 | { |
---|
382 | result = SecKeychainItemDelete(item); |
---|
383 | if (result != noErr) fprintf(stderr, "SecKeychainItemDelete returned %d\n", result); |
---|
384 | } |
---|
385 | |
---|
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); |
---|
389 | } |
---|
390 | |
---|
391 | ReadParamsFailed: |
---|
392 | return result; |
---|
393 | } |
---|
394 | |
---|
395 | |
---|
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 |
---|
405 | */ |
---|
406 | { |
---|
407 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
---|
408 | int commFD = -1, iArg, result = 0; |
---|
409 | |
---|
410 | if ( 0 != seteuid( 0)) |
---|
411 | return -1; |
---|
412 | |
---|
413 | if ( argc == 3 && 0 == strcmp( argv[2], "V")) |
---|
414 | return PRIV_OP_TOOL_VERS; |
---|
415 | |
---|
416 | if ( argc > 1) |
---|
417 | { |
---|
418 | commFD = strtol( argv[1], NULL, 0); |
---|
419 | lseek( commFD, 0, SEEK_SET); |
---|
420 | } |
---|
421 | for ( iArg = 2; iArg < argc && result == 0; iArg++) |
---|
422 | { |
---|
423 | if ( 0 == strcmp( "A", argv[ iArg])) // get auth info |
---|
424 | { |
---|
425 | result = SetAuthInfo( commFD); |
---|
426 | } |
---|
427 | else if ( 0 == strcmp( "Wd", argv[ iArg])) // Write registration domain |
---|
428 | { |
---|
429 | result = HandleWriteDomain( commFD, 1); |
---|
430 | } |
---|
431 | else if ( 0 == strcmp( "Wb", argv[ iArg])) // Write browse domain |
---|
432 | { |
---|
433 | result = HandleWriteDomain( commFD, 0); |
---|
434 | } |
---|
435 | else if ( 0 == strcmp( "Wh", argv[ iArg])) // Write hostname |
---|
436 | { |
---|
437 | result = HandleWriteHostname( commFD); |
---|
438 | } |
---|
439 | else if ( 0 == strcmp( "Wk", argv[ iArg])) // Write keychain entry |
---|
440 | { |
---|
441 | result = SetKeychainEntry( commFD); |
---|
442 | } |
---|
443 | } |
---|
444 | [pool release]; |
---|
445 | return result; |
---|
446 | } |
---|
447 | |
---|
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) |
---|
453 | |
---|
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__ ")"; |
---|
457 | |
---|
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"); |
---|
462 | #endif |
---|