/*
- * Copyright (c) 2000-2004,2011-2014 Apple Inc. All Rights Reserved.
- *
+ * Copyright (c) 2000-2004,2011-2016 Apple Inc. All Rights Reserved.
+ *
* @APPLE_LICENSE_HEADER_START@
- *
+ *
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
- *
+ *
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
- *
+ *
* @APPLE_LICENSE_HEADER_END@
*/
#include <list>
#include "Globals.h"
+#include <security_cdsa_utilities/Schema.h>
#include <security_keychain/SecCFTypes.h>
#include <securityd_client/SharedMemoryCommon.h>
#include <securityd_client/ssnotify.h>
+#include <utilities/SecCFRelease.h>
#include <notify.h>
+#include <Security/SecCertificatePriv.h>
using namespace KeychainCore;
using namespace CssmClient;
#pragma mark ÑÑÑÑ CallbackInfo ÑÑÑÑ
-CallbackInfo::CallbackInfo() : mCallback(NULL),mEventMask(0),mContext(NULL)
+CallbackInfo::CallbackInfo() : mCallback(NULL),mEventMask(0),mContext(NULL), mRunLoop(NULL), mActive(false)
{
}
CallbackInfo::CallbackInfo(SecKeychainCallback inCallbackFunction,
- SecKeychainEventMask inEventMask, void *inContext)
- : mCallback(inCallbackFunction), mEventMask(inEventMask), mContext(inContext)
+ SecKeychainEventMask inEventMask, void *inContext, CFRunLoopRef runLoop)
+ : mCallback(inCallbackFunction), mEventMask(inEventMask), mContext(inContext), mRunLoop(NULL), mActive(false)
{
+ mRunLoop = runLoop;
+ CFRetainSafe(mRunLoop);
+}
+
+CallbackInfo::CallbackInfo(const CallbackInfo& cb) {
+ mCallback = cb.mCallback;
+ mEventMask = cb.mEventMask;
+ mContext = cb.mContext;
+ mActive = cb.mActive;
+
+ mRunLoop = cb.mRunLoop;
+ CFRetainSafe(mRunLoop);
}
CallbackInfo::~CallbackInfo()
{
+ CFReleaseNull(mRunLoop);
}
bool CallbackInfo::operator==(const CallbackInfo& other) const
return gCallbackMaker().instance();
}
-void CCallbackMgr::AddCallback( SecKeychainCallback inCallbackFunction,
+void CCallbackMgr::AddCallback( SecKeychainCallback inCallbackFunction,
SecKeychainEventMask inEventMask,
void* inContext)
{
- CallbackInfo info( inCallbackFunction, inEventMask, inContext );
- CallbackInfo existingInfo;
-
+ CallbackInfo info( inCallbackFunction, inEventMask, inContext, CFRunLoopGetCurrent() );
CallbackInfoListIterator ix = find( CCallbackMgr::Instance().mEventCallbacks.begin(),
CCallbackMgr::Instance().mEventCallbacks.end(), info );
-
+
// make sure it is not already there
if ( ix!=CCallbackMgr::Instance().mEventCallbacks.end() )
{
// On Mac OS X this list is per process so this is always a duplicate
MacOSError::throwMe(errSecDuplicateCallback);
}
-
+
CCallbackMgr::Instance().mEventCallbacks.push_back(info);
+
+ // We want to deliver these notifications if the CFRunLoop we just wrote down is actually actively serviced.
+ // Otherwise, it'll be a continuous (undetectable) leak.
+ CFRunLoopTimerContext ctx;
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.info = info.mRunLoop;
+
+ CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 0, 0, 0, CCallbackMgr::cfrunLoopActive, &ctx);
+ secdebug("kcnotify", "adding a activate callback on run loop %p", info.mRunLoop);
+ CFRunLoopAddTimer(info.mRunLoop, timerRef, kCFRunLoopDefaultMode);
+}
+
+void CCallbackMgr::cfrunLoopActive(CFRunLoopTimerRef timer, void* info) {
+ CFRunLoopRef runLoop = (CFRunLoopRef) info;
+ secdebug("kcnotify", "activating run loop %p", runLoop);
+
+ // Use the notification queue to serialize setting the mActive bits
+ static dispatch_queue_t notification_queue = EventListener::getNotificationQueue();
+ dispatch_async(notification_queue, ^() {
+ // Iterate through list, and activate every notification on this run loop
+ for(CallbackInfoListIterator ix = CCallbackMgr::Instance().mEventCallbacks.begin(); ix != CCallbackMgr::Instance().mEventCallbacks.end(); ix++) {
+ // pointer comparison, not CFEqual.
+ if(ix->mRunLoop == runLoop) {
+ secdebug("kcnotify", "activating callback on run loop %p", runLoop);
+ ix->mActive = true;
+ }
+ }
+ });
+
+ CFRelease(timer);
}
MacOSError::throwMe(errSecInvalidCallback);
}
+struct CallbackMgrInfo {
+ SecKeychainEvent event;
+ SecKeychainCallbackInfo secKeychainCallbackInfo;
+ SecKeychainCallback callback;
+ void *callbackContext;
+};
+
+void CCallbackMgr::tellClient(CFRunLoopTimerRef timer, void* info) {
+ CallbackMgrInfo* cbmInfo = (CallbackMgrInfo*) info;
+ if(!cbmInfo || !(cbmInfo->callback)) {
+ return;
+ }
+
+ cbmInfo->callback(cbmInfo->event, &(cbmInfo->secKeychainCallbackInfo), cbmInfo->callbackContext);
+ if (cbmInfo->secKeychainCallbackInfo.item) CFRelease(cbmInfo->secKeychainCallbackInfo.item);
+ if (cbmInfo->secKeychainCallbackInfo.keychain) CFRelease(cbmInfo->secKeychainCallbackInfo.keychain);
+ free(cbmInfo);
+ CFRelease(timer);
+}
+
+static SecKeychainItemRef createItemReference(const Item &inItem)
+{
+ SecKeychainItemRef itemRef = (inItem) ? inItem->handle() : 0;
+ if(!itemRef) { return NULL; }
+
+#if SECTRUST_OSX
+ SecItemClass itemClass = Schema::itemClassFor(inItem->recordType());
+ if (itemClass == kSecCertificateItemClass) {
+ SecCertificateRef certRef = SecCertificateCreateFromItemImplInstance((SecCertificateRef)itemRef);
+ CFRelease(itemRef); /* certRef maintains its own internal reference to itemRef */
+ itemRef = (SecKeychainItemRef) certRef;
+ }
+#endif
+ return itemRef;
+}
+
+static SecKeychainRef createKeychainReference(const Keychain &inKeychain)
+{
+ return (inKeychain) ? inKeychain->handle() : 0;
+}
+
void CCallbackMgr::AlertClients(const list<CallbackInfo> &eventCallbacks,
SecKeychainEvent inEvent,
pid_t inPid,
const Keychain &inKeychain,
const Item &inItem)
{
- secdebug("kcnotify", "dispatch event %ld pid %d keychain %p item %p",
+ secinfo("kcnotify", "dispatch event %ld pid %d keychain %p item %p",
(unsigned long)inEvent, inPid, &inKeychain, !!inItem ? &*inItem : NULL);
// Iterate through callbacks, looking for those registered for inEvent
if (!(ix->mEventMask & theMask))
continue;
- SecKeychainCallbackInfo cbInfo;
- cbInfo.version = 0; // @@@ kKeychainAPIVersion;
- cbInfo.item = inItem ? inItem->handle() : 0;
- cbInfo.keychain = inKeychain ? inKeychain->handle() : 0;
- cbInfo.pid = inPid;
+ if(!(ix->mActive)) {
+ // We haven't received our callback from this CFRunLoop yet. Assume it's not being pumped, and don't schedule.
+ secdebug("kcnotify", "not sending event to run loop %p", ix->mRunLoop);
+ continue;
+ }
+
+ // The previous notification system required a CFRunLoop to be executing. Schedule the client's notifications back on their CFRunLoop, just in case it's important.
+ CFRunLoopRef runLoop = ix->mRunLoop;
+ secdebug("kcnotify", "sending event to runloop %p", runLoop);
+
+ // Set up our callback structures
+ CallbackMgrInfo* cbmInfo = (CallbackMgrInfo*) calloc(sizeof(CallbackMgrInfo), 1);
+
+ cbmInfo->secKeychainCallbackInfo.version = 0; // @@@ kKeychainAPIVersion;
+ cbmInfo->secKeychainCallbackInfo.item = createItemReference(inItem);
+ cbmInfo->secKeychainCallbackInfo.keychain = createKeychainReference(inKeychain);
+ cbmInfo->secKeychainCallbackInfo.pid = inPid;
+
+ cbmInfo->event = inEvent;
+ cbmInfo->callback = ix->mCallback;
+ cbmInfo->callbackContext = ix->mContext;
+
+ CFRunLoopTimerContext ctx;
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.info = cbmInfo;
+
+ // make a run loop timer
+ CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 0, 0, 0, CCallbackMgr::tellClient, &ctx);
- ix->mCallback(inEvent, &cbInfo, ix->mContext);
- if (cbInfo.item) CFRelease(cbInfo.item);
- if (cbInfo.keychain) CFRelease(cbInfo.keychain);
+ // Actually call the callback the next time the run loop fires
+ CFRunLoopAddTimer(runLoop, timerRef, kCFRunLoopDefaultMode);
}
}