1/*
2 * Copyright (c) 2000-2004,2011-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25/*
26	File:		CCallbackMgr.cp
27
28	Contains:	Code that communicates with processes that install a callback
29                with the Keychain Manager to receive keychain events.
30
31*/
32
33#include "CCallbackMgr.h"
34
35#include <algorithm>
36#include <list>
37
38#include "Globals.h"
39#include <security_keychain/SecCFTypes.h>
40#include <securityd_client/SharedMemoryCommon.h>
41#include <securityd_client/ssnotify.h>
42#include <notify.h>
43
44using namespace KeychainCore;
45using namespace CssmClient;
46using namespace SecurityServer;
47
48#pragma mark ���� CallbackInfo ����
49
50CallbackInfo::CallbackInfo() : mCallback(NULL),mEventMask(0),mContext(NULL)
51{
52}
53
54CallbackInfo::CallbackInfo(SecKeychainCallback inCallbackFunction,
55	SecKeychainEventMask inEventMask, void *inContext)
56	: mCallback(inCallbackFunction), mEventMask(inEventMask), mContext(inContext)
57{
58}
59
60CallbackInfo::~CallbackInfo()
61{
62}
63
64bool CallbackInfo::operator==(const CallbackInfo& other) const
65{
66    return mCallback==other.mCallback;
67}
68
69bool CallbackInfo::operator!=(const CallbackInfo& other) const
70{
71	return !(*this==other);
72}
73
74
75#pragma mark ���� CCallbackMgr ����
76
77
78class CallbackMaker
79{
80protected:
81	RefPointer<CCallbackMgr> mCallbackManager;
82
83public:
84	CallbackMaker();
85	CCallbackMgr& instance() {return *mCallbackManager;}
86};
87
88
89CallbackMaker::CallbackMaker()
90{
91	CCallbackMgr* manager = new CCallbackMgr();
92	mCallbackManager = manager;
93}
94
95
96
97ModuleNexus<CallbackMaker> gCallbackMaker;
98
99CCallbackMgr::CCallbackMgr() : EventListener (kNotificationDomainDatabase, kNotificationAllEvents)
100{
101    EventListener::FinishedInitialization(this);
102}
103
104CCallbackMgr::~CCallbackMgr()
105{
106}
107
108CCallbackMgr& CCallbackMgr::Instance()
109{
110	return gCallbackMaker().instance();
111}
112
113void CCallbackMgr::AddCallback( SecKeychainCallback inCallbackFunction,
114                             SecKeychainEventMask 	inEventMask,
115                             void* 			inContext)
116
117{
118	CallbackInfo info( inCallbackFunction, inEventMask, inContext );
119	CallbackInfo existingInfo;
120
121
122    CallbackInfoListIterator ix = find( CCallbackMgr::Instance().mEventCallbacks.begin(),
123                                        CCallbackMgr::Instance().mEventCallbacks.end(), info );
124
125	// make sure it is not already there
126	if ( ix!=CCallbackMgr::Instance().mEventCallbacks.end() )
127    {
128        // It's already there. This could mean that the old process died unexpectedly,
129        // so we need to validate the process ID of the existing callback.
130        // On Mac OS X this list is per process so this is always a duplicate
131		MacOSError::throwMe(errSecDuplicateCallback);
132	}
133
134	CCallbackMgr::Instance().mEventCallbacks.push_back(info);
135}
136
137
138class Predicate
139{
140	SecKeychainCallback mCallbackFunction;
141public:
142	Predicate(SecKeychainCallback inCallbackFunction) : mCallbackFunction(inCallbackFunction) {}
143	bool operator()(const CallbackInfo &cbInfo) { return cbInfo.mCallback == mCallbackFunction; }
144};
145
146void CCallbackMgr::RemoveCallback(SecKeychainCallback inCallbackFunction)
147{
148	size_t oldSize = CCallbackMgr::Instance().mEventCallbacks.size();
149	Predicate predicate(inCallbackFunction);
150	CCallbackMgr::Instance().mEventCallbacks.remove_if(predicate);
151
152	if (oldSize == CCallbackMgr::Instance().mEventCallbacks.size())
153		MacOSError::throwMe(errSecInvalidCallback);
154}
155
156void CCallbackMgr::AlertClients(const list<CallbackInfo> &eventCallbacks,
157								SecKeychainEvent inEvent,
158								pid_t inPid,
159                                const Keychain &inKeychain,
160                                const Item &inItem)
161{
162    secdebug("kcnotify", "dispatch event %ld pid %d keychain %p item %p",
163        (unsigned long)inEvent, inPid, &inKeychain, !!inItem ? &*inItem : NULL);
164
165	// Iterate through callbacks, looking for those registered for inEvent
166	const SecKeychainEventMask theMask = 1U << inEvent;
167
168	for (ConstCallbackInfoListIterator ix = eventCallbacks.begin(); ix != eventCallbacks.end(); ++ix)
169	{
170		if (!(ix->mEventMask & theMask))
171			continue;
172
173		SecKeychainCallbackInfo	cbInfo;
174		cbInfo.version = 0; // @@@ kKeychainAPIVersion;
175		cbInfo.item = inItem ? inItem->handle() : 0;
176		cbInfo.keychain = inKeychain ? inKeychain->handle() : 0;
177		cbInfo.pid = inPid;
178
179		ix->mCallback(inEvent, &cbInfo, ix->mContext);
180		if (cbInfo.item) CFRelease(cbInfo.item);
181		if (cbInfo.keychain) CFRelease(cbInfo.keychain);
182	}
183}
184
185
186
187void CCallbackMgr::consume (SecurityServer::NotificationDomain domain, SecurityServer::NotificationEvent whichEvent, const CssmData &data)
188{
189    NameValueDictionary dictionary (data);
190
191    // Decode from userInfo the event type, 'keychain' CFDict, and 'item' CFDict
192	SecKeychainEvent thisEvent = whichEvent;
193
194    pid_t thisPid;
195	const NameValuePair* pidRef = dictionary.FindByName(PID_KEY);
196	if (pidRef == 0)
197	{
198		thisPid = 0;
199	}
200	else
201	{
202		thisPid = n2h(*reinterpret_cast<pid_t*>(pidRef->Value().data ()));
203	}
204
205	Keychain thisKeychain;
206    Item thisItem;
207	list<CallbackInfo> eventCallbacks;
208	{
209		// Lock the global API lock before doing stuff with StorageManager.
210		// make sure we have a database identifier
211		if (dictionary.FindByName (SSUID_KEY) != 0)
212		{
213            StLock<Mutex>_(*globals().storageManager.getStorageManagerMutex());
214			DLDbIdentifier dbid = NameValueDictionary::MakeDLDbIdentifierFromNameValueDictionary(dictionary);
215			thisKeychain = globals().storageManager.keychain(dbid);
216		}
217
218		const NameValuePair* item = dictionary.FindByName(ITEM_KEY);
219
220		if (item && thisKeychain)
221		{
222            PrimaryKey pk(item->Value());
223            thisItem = thisKeychain->item(pk);
224		}
225
226		// Deal with events that we care about ourselves first.
227		if (thisEvent == kSecDeleteEvent && thisKeychain.get() && thisItem.get())
228			thisKeychain->didDeleteItem(thisItem.get());
229		else if (thisEvent == kSecKeychainListChangedEvent)
230			globals().storageManager.forceUserSearchListReread();
231
232		eventCallbacks = CCallbackMgr::Instance().mEventCallbacks;
233		// We can safely release the global API lock now since thisKeychain and thisItem
234		// are CFRetained and will be until they go out of scope.
235	}
236
237    // Notify our process of this event.
238	CCallbackMgr::AlertClients(eventCallbacks, thisEvent, thisPid, thisKeychain, thisItem);
239}
240