2 * Copyright (c) 2000, 2001, 2003-2005, 2007-2011 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 * Modification History
27 * June 1, 2001 Allan Nathanson <ajn@apple.com>
28 * - public API conversion
30 * March 24, 2000 Allan Nathanson <ajn@apple.com>
35 #include "configd_server.h"
39 #include <bsm/libbsm.h>
43 /* information maintained for each active session */
44 static serverSessionRef
*sessions
= NULL
;
45 static int nSessions
= 0; /* # of allocated sessions */
46 static int lastSession
= -1; /* # of last used session */
48 /* CFMachPortInvalidation runloop */
49 static CFRunLoopRef sessionRunLoop
= NULL
;
52 static serverSessionRef temp_session
= NULL
;
56 CFMachPortInvalidateSessionCallback(CFMachPortRef port
, void *info
)
58 CFRunLoopRef currentRunLoop
= CFRunLoopGetCurrent();
61 if (!_SC_CFEqual(currentRunLoop
, sessionRunLoop
)) {
62 _SC_crash("SCDynamicStore CFMachPort invalidation error",
63 CFSTR("CFMachPort invalidated"),
64 CFSTR("An SCDynamicStore CFMachPort has incorrectly been invalidated."));
71 getSession(mach_port_t server
)
75 if (server
== MACH_PORT_NULL
) {
76 SCLog(TRUE
, LOG_ERR
, CFSTR("Excuse me, why is getSession() being called with an invalid port?"));
80 /* look for matching session (note: slot 0 is the "server" port) */
81 for (i
= 1; i
<= lastSession
; i
++) {
82 serverSessionRef thisSession
= sessions
[i
];
84 if (thisSession
== NULL
) {
85 /* found an empty slot, skip it */
89 if (thisSession
->key
== server
) {
90 /* we've seen this server before */
94 if ((thisSession
->store
!= NULL
) &&
95 (((SCDynamicStorePrivateRef
)thisSession
->store
)->notifySignalTask
== server
)) {
96 /* we've seen this task port before */
101 /* no sessions available */
108 tempSession(mach_port_t server
, CFStringRef name
, audit_token_t auditToken
)
110 static dispatch_once_t once
;
111 SCDynamicStorePrivateRef storePrivate
;
113 if (sessions
[0]->key
!= server
) {
114 // if not SCDynamicStore "server" port
118 dispatch_once(&once
, ^{
119 temp_session
= sessions
[0]; /* use "server" session */
120 (void) __SCDynamicStoreOpen(&temp_session
->store
, NULL
);
123 /* save audit token */
124 temp_session
->auditToken
= auditToken
;
125 temp_session
->callerEUID
= -1; /* not "root" */
126 temp_session
->callerRootAccess
= UNKNOWN
;
127 temp_session
->callerWriteAccess
= UNKNOWN
;
130 storePrivate
= (SCDynamicStorePrivateRef
)temp_session
->store
;
131 if (storePrivate
->name
!= NULL
) CFRelease(storePrivate
->name
);
132 storePrivate
->name
= CFRetain(name
);
140 addSession(mach_port_t server
, CFStringRef (*copyDescription
)(const void *info
))
142 CFMachPortContext context
= { 0, NULL
, NULL
, NULL
, NULL
};
143 mach_port_t mp
= server
;
146 /* save current (SCDynamicStore) runloop */
147 if (sessionRunLoop
== NULL
) {
148 sessionRunLoop
= CFRunLoopGetCurrent();
151 if (nSessions
<= 0) {
152 /* if first session (the "server" port) */
153 n
= 0; /* use slot "0" */
154 lastSession
= 0; /* last used slot */
157 sessions
= malloc(nSessions
* sizeof(serverSessionRef
));
161 /* check to see if we already have an open session (note: slot 0 is the "server" port) */
162 for (i
= 1; i
<= lastSession
; i
++) {
163 serverSessionRef thisSession
= sessions
[i
];
165 if (thisSession
== NULL
) {
166 /* found an empty slot */
168 /* keep track of the first [empty] slot */
172 /* and keep looking for a matching session */
176 if (thisSession
->key
== server
) {
177 /* we've seen this server before */
181 if ((thisSession
->store
!= NULL
) &&
182 (((SCDynamicStorePrivateRef
)thisSession
->store
)->notifySignalTask
== server
)) {
183 /* we've seen this task port before */
188 /* add a new session */
190 /* if no empty slots */
192 if (lastSession
>= nSessions
) {
193 /* expand the session list */
195 sessions
= reallocf(sessions
, (nSessions
* sizeof(serverSessionRef
)));
199 // create mach port for SCDynamicStore client
201 (void) mach_port_allocate(mach_task_self(),
202 MACH_PORT_RIGHT_RECEIVE
,
206 // allocate a new session for this server
207 sessions
[n
] = malloc(sizeof(serverSession
));
208 bzero(sessions
[n
], sizeof(serverSession
));
210 // create server port
211 context
.info
= sessions
[n
];
212 context
.copyDescription
= copyDescription
;
215 // Note: we create the CFMachPort *before* we insert a send
216 // right present to ensure that CF does not establish
217 // its dead name notification.
219 sessions
[n
]->serverPort
= _SC_CFMachPortCreateWithPort("SCDynamicStore/session",
225 // Set bear trap (an invalidation callback) to catch other
226 // threads stomping on us
228 CFMachPortSetInvalidationCallBack(sessions
[n
]->serverPort
,
229 CFMachPortInvalidateSessionCallback
);
232 // insert send right that will be moved to the client
233 (void) mach_port_insert_right(mach_task_self(),
236 MACH_MSG_TYPE_MAKE_SEND
);
239 sessions
[n
]->key
= mp
;
240 // sessions[n]->serverRunLoopSource = NULL;
241 // sessions[n]->store = NULL;
242 sessions
[n
]->callerEUID
= 1; /* not "root" */
243 sessions
[n
]->callerRootAccess
= UNKNOWN
;
244 sessions
[n
]->callerWriteAccess
= UNKNOWN
;
252 cleanupSession(mach_port_t server
)
256 for (i
= 1; i
<= lastSession
; i
++) {
257 CFStringRef sessionKey
;
258 serverSessionRef thisSession
= sessions
[i
];
260 if (thisSession
== NULL
) {
261 /* found an empty slot, skip it */
265 if (thisSession
->key
== server
) {
267 * session entry still exists.
270 if (_configd_trace
) {
271 SCTrace(TRUE
, _configd_trace
, CFSTR("cleanup : %5d\n"), server
);
275 * Close any open connections including cancelling any outstanding
276 * notification requests and releasing any locks.
278 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession", server
);
279 (void) __SCDynamicStoreClose(&thisSession
->store
);
280 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession (after __SCDynamicStoreClose)", server
);
283 * Our send right has already been removed. Remove our receive right.
285 (void) mach_port_mod_refs(mach_task_self(), server
, MACH_PORT_RIGHT_RECEIVE
, -1);
288 * We don't need any remaining information in the
289 * sessionData dictionary, remove it.
291 sessionKey
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), server
);
292 CFDictionaryRemoveValue(sessionData
, sessionKey
);
293 CFRelease(sessionKey
);
296 * get rid of the per-session structure.
301 if (i
== lastSession
) {
302 /* we are removing the last session, update last used slot */
303 while (--lastSession
> 0) {
304 if (sessions
[lastSession
] != NULL
) {
314 SCLog(TRUE
, LOG_ERR
, CFSTR("MACH_NOTIFY_NO_SENDERS w/no session, port = %d"), server
);
315 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession w/no session", server
);
322 listSessions(FILE *f
)
326 SCPrint(TRUE
, f
, CFSTR("Current sessions :\n"));
327 for (i
= 0; i
<= lastSession
; i
++) {
328 serverSessionRef thisSession
= sessions
[i
];
330 if (thisSession
== NULL
) {
334 SCPrint(TRUE
, f
, CFSTR("\t%d : port = 0x%x"), i
, thisSession
->key
);
336 if (thisSession
->store
!= NULL
) {
337 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)thisSession
->store
;
339 if (storePrivate
->notifySignalTask
!= TASK_NULL
) {
340 SCPrint(TRUE
, f
, CFSTR(", task = %d"), storePrivate
->notifySignalTask
);
344 if (sessionData
!= NULL
) {
345 CFDictionaryRef info
;
348 key
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), thisSession
->key
);
349 info
= CFDictionaryGetValue(sessionData
, key
);
354 name
= CFDictionaryGetValue(info
, kSCDName
);
356 SCPrint(TRUE
, f
, CFSTR(", name = %@"), name
);
361 if (thisSession
->serverPort
!= NULL
) {
362 SCPrint(TRUE
, f
, CFSTR("\n\t\t%@"), thisSession
->serverPort
);
365 if (thisSession
->serverRunLoopSource
!= NULL
) {
366 SCPrint(TRUE
, f
, CFSTR("\n\t\t%@"), thisSession
->serverRunLoopSource
);
369 SCPrint(TRUE
, f
, CFSTR("\n"));
372 SCPrint(TRUE
, f
, CFSTR("\n"));
377 #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1070)
379 #include <Security/Security.h>
380 #include <Security/SecTask.h>
383 sessionName(serverSessionRef session
)
385 CFDictionaryRef info
;
386 CFStringRef name
= NULL
;
387 CFStringRef sessionKey
;
389 sessionKey
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), session
->key
);
390 info
= CFDictionaryGetValue(sessionData
, sessionKey
);
391 CFRelease(sessionKey
);
394 name
= CFDictionaryGetValue(info
, kSCDName
);
397 return (name
!= NULL
) ? name
: CFSTR("???");
402 hasEntitlement(serverSessionRef session
, CFStringRef entitlement
)
404 Boolean hasEntitlement
= FALSE
;
407 /* Create the security task from the audit token. */
408 task
= SecTaskCreateWithAuditToken(NULL
, session
->auditToken
);
410 CFErrorRef error
= NULL
;
413 /* Get the value for the entitlement. */
414 value
= SecTaskCopyValueForEntitlement(task
, entitlement
, &error
);
416 if (isA_CFBoolean(value
)) {
417 if (CFBooleanGetValue(value
)) {
418 /* if client DOES have entitlement */
419 hasEntitlement
= TRUE
;
423 CFSTR("hasEntitlement: entitlement not valid: %@"),
424 sessionName(session
));
431 (value
== NULL
) ? LOG_ERR
: LOG_DEBUG
,
432 CFSTR("hasEntitlement SecTaskCopyValueForEntitlement() %s, error domain=%@, error code=%lx"),
433 (value
== NULL
) ? "failed" : "warned",
434 CFErrorGetDomain(error
),
435 CFErrorGetCode(error
));
442 CFSTR("hasEntitlement SecTaskCreateWithAuditToken() failed: %@"),
443 sessionName(session
));
446 return hasEntitlement
;
449 #endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1070)
454 hasRootAccess(serverSessionRef session
)
456 if (session
->callerRootAccess
== UNKNOWN
) {
458 * get the credentials associated with the caller.
460 audit_token_to_au32(session
->auditToken
,
462 &session
->callerEUID
, // euid
470 session
->callerRootAccess
= (session
->callerEUID
== 0) ? YES
: NO
;
473 return (session
->callerRootAccess
== YES
) ? TRUE
: FALSE
;
479 hasWriteAccess(serverSessionRef session
)
481 if (session
->callerWriteAccess
== UNKNOWN
) {
482 /* assume that the client DOES NOT have the entitlement */
483 session
->callerWriteAccess
= NO
;
485 if (hasRootAccess(session
)) {
486 // grant write access to eUID==0 processes
487 session
->callerWriteAccess
= YES
;
489 #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1070)
490 else if (hasEntitlement(session
, kSCWriteEntitlementName
)) {
491 // grant write access to "entitled" processes
492 session
->callerWriteAccess
= YES
;
494 #endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1070)
497 return (session
->callerWriteAccess
== YES
) ? TRUE
: FALSE
;
503 hasPathAccess(serverSessionRef session
, const char *path
)
506 char realPath
[PATH_MAX
];
508 if (realpath(path
, realPath
) == NULL
) {
509 SCLog(TRUE
, LOG_DEBUG
, CFSTR("hasPathAccess realpath() failed: %s"), strerror(errno
));
513 audit_token_to_au32(session
->auditToken
,
523 if (sandbox_check(pid
, "file-write-data", SANDBOX_FILTER_PATH
, realPath
) > 0) {
524 SCLog(TRUE
, LOG_DEBUG
, CFSTR("hasPathAccess sandbox access denied: %s"), strerror(errno
));