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