1/*
2 * Copyright (c) 2009 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#include "ODBridge.h"
25
26static const char* kEmailName = "Alias";
27static const char* kPrintName = "PrintName";
28static const char *dbname =  "Open Directory Data Library";
29static long long OD_query_time_out = 5LL * NSEC_PER_SEC;  // 5 second timeout for all OD queries.
30
31// If we want to get more specific translations for OD Errors this would be the place.  For now we'll settle for CSSMERR_DL_INTERNAL_ERROR
32CSSM_RETURN cssme_for_OD(CFErrorRef ODReturn)
33{
34	switch (CFErrorGetCode(ODReturn))
35	{
36		case 0:
37			return CSSM_OK;
38		default:
39			return CSSMERR_DL_INTERNAL_ERROR;
40	}
41}
42
43
44DirectoryService::DirectoryService()
45{
46	this->db_name = (char *)dbname;
47	CFErrorRef ODReturn;
48	this->all_open_queries = CFArrayCreateMutable(kCFAllocatorDefault, 5, NULL);
49
50	this->query_dispatch_queue = dispatch_queue_create("com.apple.ldapdl", NULL);
51	dispatch_retain(this->query_dispatch_queue);
52
53	if((this->node = ODNodeCreateWithNodeType(NULL, kODSessionDefault, kODNodeTypeContacts, &ODReturn)) == NULL)
54		throw DirectoryServiceException (ODReturn);
55}
56
57DirectoryService::~DirectoryService()
58{
59	/* Walk the list of results and close them all. */
60
61	CFIndex i, count;
62	for (i = 0, count = CFArrayGetCount(this->all_open_queries); i < count; i++) {
63		ODdl_results_handle result;
64		result = (ODdl_results_handle) CFArrayGetValueAtIndex(this->all_open_queries, i);
65		CFRelease(result->query);
66		CFRelease(result->certificates);
67		CFRelease(result->searchString);
68		free(result);
69	}
70	CFRelease(this->node);
71}
72
73// Print the Cert Bytes
74
75#define LINEWIDTH 80
76#define BYTESPERBLOB 4
77#define BUFFERSIZE (LINEWIDTH+1)
78#define BLOBSPERLINE (LINEWIDTH / ((BYTESPERBLOB * 2) +1))
79#define BYTESPERLINE (BLOBSPERLINE * BYTESPERBLOB)
80
81
82// These two functions handle the callbacks from the OD query - cert_query_callback is called first with a record entry; getCertsFromArray can take multiple cert returns from that entry.
83static void
84getCertsFromArray(const void *value, void *context)
85{
86	ODdl_results_handle results = (ODdl_results_handle) context;
87	CFRetain((CFDataRef) value);
88	CFArrayAppendValue(results->certificates, value);
89}
90
91void
92cert_query_callback(ODQueryRef query, CFArrayRef qresults, CFErrorRef error, void *context)
93{
94	ODdl_results_handle results = (ODdl_results_handle) context;
95	__block CFErrorRef	retError;
96
97	dispatch_sync(results->result_modifier_queue, ^{
98		if (qresults == NULL) {
99			if(error == NULL) {
100				if(results->results_done) dispatch_semaphore_signal(results->results_done);
101			}
102			return;
103		}
104
105		CFIndex i, count;
106		for (i = 0, count = CFArrayGetCount(qresults); i < count; i++) {
107			ODRecordRef rec = (ODRecordRef)CFArrayGetValueAtIndex(qresults, i);
108			CFArrayRef certs = ODRecordCopyValues(rec, kODAttributeTypeUserCertificate, &retError);
109			if(certs && CFArrayGetCount(certs)) {
110				CFArrayApplyFunction(certs, CFRangeMake( 0, CFArrayGetCount(certs)) , getCertsFromArray, context);
111				CFRelease(certs);
112			}
113		}
114	});
115	error = retError;
116}
117
118// simplistic e-mail address filter
119
120static bool isValidEmailString(CFStringRef s)
121{
122	char buf[256];
123
124	if(CFStringGetCString(s, buf, 256, kCFStringEncodingASCII) == 0) return false;
125	if(CFStringGetLength(s) < 10) return false;  // must be at least 3 chars (arbitrary) of local, an @ sign, then 3 letters (arbitrary) dot(.) 2 letters (like in .ru)
126	if(index(buf, '@') == NULL) return false;
127	if(index(buf, '.') == NULL) return false;
128	return true;
129}
130
131ODdl_results_handle
132DirectoryService::translate_cssm_query_to_OD_query(const CSSM_QUERY *Query, CSSM_RETURN *error)
133{
134	ODMatchType  matchType;
135	CFIndex      ODmaxResults = 0;
136	ODdl_results_handle results;
137	int	i, searchPred;
138	CFErrorRef ODerror;
139
140	*error = 0;
141
142	// make sure we're asked to look for something we can find.
143	CSSM_SELECTION_PREDICATE_PTR pred;
144	for (i=0, searchPred = -1; searchPred == -1 && i < (int) Query->NumSelectionPredicates; i++) {
145		pred = (CSSM_SELECTION_PREDICATE_PTR) &(Query->SelectionPredicate[i]);
146		if(pred->Attribute.Info.AttributeNameFormat == CSSM_DB_ATTRIBUTE_NAME_AS_STRING) {
147			if(strncmp(pred->Attribute.Info.Label.AttributeName, kEmailName, strlen(kEmailName)) == 0 ||
148			   strncmp(pred->Attribute.Info.Label.AttributeName, kEmailName, strlen(kPrintName)) == 0) {
149				searchPred = i;
150			}
151		}
152	}
153
154	if (searchPred == -1) {
155		*error = CSSMERR_DL_INVALID_QUERY;
156		return NULL;
157	}
158
159	pred = (CSSM_SELECTION_PREDICATE_PTR) &(Query->SelectionPredicate[searchPred]);
160	if (pred->Attribute.NumberOfValues != 1) {
161		*error = CSSMERR_DL_INVALID_QUERY;
162		return NULL;
163	}
164
165	// translate cssm comparisons into OD comparisons - we will only ever match - no fishing allowed.
166	switch(pred->DbOperator) {
167		case CSSM_DB_EQUAL:
168		case CSSM_DB_LESS_THAN:
169		case CSSM_DB_GREATER_THAN:
170		case CSSM_DB_CONTAINS:
171		case CSSM_DB_CONTAINS_INITIAL_SUBSTRING:
172		case CSSM_DB_CONTAINS_FINAL_SUBSTRING:
173			matchType = kODMatchEqualTo;
174			break;
175		default:
176			*error = CSSMERR_DL_INVALID_QUERY;
177			return NULL;
178	}
179
180	if((results = (ODdl_results_handle) malloc(sizeof(struct ODdl_results))) == NULL) {
181		*error = CSSMERR_DL_MEMORY_ERROR;
182		return NULL;
183	}
184	CFArrayAppendValue(this->all_open_queries, (const void *)results);
185
186	// Bookkeeping for this query
187	results->recordid = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
188	results->certificates = CFArrayCreateMutable(NULL, 20, &kCFTypeArrayCallBacks);
189	results->currentRecord = 0;
190	results->results_done = dispatch_semaphore_create(0);
191	results->result_modifier_queue = dispatch_queue_create("com.apple.ldapdl.results", NULL);
192
193	// Get remaining query parameters
194	if (Query->QueryLimits.SizeLimit != CSSM_QUERY_SIZELIMIT_NONE) ODmaxResults = Query->QueryLimits.SizeLimit;
195	results->searchString = CFStringCreateWithBytes(NULL, pred->Attribute.Value->Data, pred->Attribute.Value->Length, kCFStringEncodingUTF8, false);
196
197	// Make sure the e-mail address looks sane.  Keychain Access at least will bombard us with character-by-character lookups and we don't want to spam the server until we have something reasonable.
198	if(!isValidEmailString(results->searchString)) {
199		*error = CSSMERR_DL_ENDOFDATA;
200		return NULL;
201	}
202
203	// Create the query looking for the e-mail address and returning that certificate.
204	if((results->query = ODQueryCreateWithNode(NULL, this->node, kODRecordTypeUsers, kODAttributeTypeEMailAddress, matchType, results->searchString, kODAttributeTypeUserCertificate, ODmaxResults, &ODerror)) == NULL) {
205		*error =  cssme_for_OD(ODerror);
206		return NULL;
207	}
208
209	// Prepare callback for query
210	ODQuerySetCallback(results->query, cert_query_callback, (void *)results);
211
212	ODQuerySetDispatchQueue( results->query, query_dispatch_queue ); // dispatch_queue is setup in constructor now.
213	dispatch_semaphore_wait(results->results_done, dispatch_time(DISPATCH_TIME_NOW, OD_query_time_out));
214	return results;
215}
216
217// return certificates sequentially as if they are individual records.
218CFDataRef
219DirectoryService::getNextCertFromResults(ODdl_results_handle results)
220{
221	__block CFDataRef	retval;
222
223	if(results->result_modifier_queue == NULL) {
224		return NULL;
225	}
226	dispatch_sync(results->result_modifier_queue, ^{
227		if(!results || !results->certificates) {
228			retval =  NULL;
229		} else if(results->currentRecord < CFArrayGetCount(results->certificates)) {
230			retval = (CFDataRef) CFArrayGetValueAtIndex(results->certificates, results->currentRecord);
231			results->currentRecord++;
232		} else {
233			retval =  NULL;
234		}
235	});
236	return retval;
237
238}
239
240