1/*
2 * Copyright (c) 2000-2012 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 * ocspdNetwork.cpp - Network support for ocspd and CRL/cert fetch
26 */
27#if OCSP_DEBUG
28#define OCSP_USE_SYSLOG	1
29#endif
30#include <security_ocspd/ocspdDebug.h>
31#include "appleCrlIssuers.h"
32#include "ocspdNetwork.h"
33#include <security_ocspd/ocspdUtils.h>
34#include <CoreFoundation/CoreFoundation.h>
35#include <security_cdsa_utils/cuEnc64.h>
36#include <security_cdsa_utils/cuFileIo.h>
37#include <stdlib.h>
38#include <sys/stat.h>
39#include <dispatch/dispatch.h>
40#include <Security/cssmapple.h>
41#include <security_utilities/cfutilities.h>
42#include <CoreServices/CoreServices.h>
43#include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
44
45/* enable deprecated function declarations */
46#ifndef LDAP_DEPRECATED
47#define LDAP_DEPRECATED 1
48#endif
49#include <LDAP/ldap.h>
50
51/* useful macros for CF */
52#define CFReleaseSafe(CF) { CFTypeRef _cf = (CF); if (_cf) CFRelease(_cf); }
53#define CFReleaseNull(CF) { CFTypeRef _cf = (CF); \
54	if (_cf) { (CF) = NULL; CFRelease(_cf); } }
55
56extern Mutex gParamsLock;
57extern Mutex gFileWriteLock;
58extern Mutex gListLock;
59extern CFMutableArrayRef gDownloadList;
60extern CFMutableDictionaryRef gIssuersDict;
61
62extern bool crlSignatureValid(
63	const char *crlFileName,
64	const char *issuersFileName,
65	const char *updateFileName,
66	const char *revokedFileName);
67
68extern int crlCheckCachePath();
69
70#pragma mark ----- async HTTP -----
71
72/* how long to wait for more data to come in before we give up */
73#define READ_STREAM_TIMEOUT	7.0
74
75/* read buffer size */
76#define READ_BUFFER_SIZE	4096
77
78/* post buffer size */
79#define POST_BUFFER_SIZE	1024
80
81/* context for an asynchronous HTTP request */
82typedef struct asynchttp_s {
83	CFHTTPMessageRef request;
84	CFHTTPMessageRef response;
85	CFMutableDataRef data;
86	CFIndex increment;
87	size_t responseLength; /* how much data we have currently read */
88	size_t previousLength; /* how much data we had read when the timer was started */
89	CFReadStreamRef stream;
90	CFRunLoopTimerRef timer;
91	int finished;
92} asynchttp_t;
93
94
95static void asynchttp_complete(
96	asynchttp_t *http)
97{
98    /* Shut down stream and timer. */
99    if (http->stream) {
100		CFReadStreamSetClient(http->stream, kCFStreamEventNone, NULL, NULL);
101		CFReadStreamUnscheduleFromRunLoop(http->stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
102		CFReadStreamClose(http->stream);
103        CFReleaseNull(http->stream);
104    }
105    if (http->timer) {
106        CFRunLoopTimerInvalidate(http->timer);
107        CFReleaseNull(http->timer);
108    }
109	/* We're done. */
110	http->finished = 1;
111}
112
113static void asynchttp_free(
114	asynchttp_t *http)
115{
116    if(http == NULL) return;
117	CFReleaseNull(http->request);
118	CFReleaseNull(http->response);
119	CFReleaseNull(http->data);
120	CFReleaseNull(http->stream);
121	CFReleaseNull(http->timer);
122}
123
124static void asynchttp_timer_proc(
125	CFRunLoopTimerRef timer,
126	void *info)
127{
128    asynchttp_t *http = (asynchttp_t *)info;
129#if HTTP_DEBUG
130    CFStringRef req_meth = http->request ? CFHTTPMessageCopyRequestMethod(http->request) : NULL;
131    CFURLRef req_url = http->request ? CFHTTPMessageCopyRequestURL(http->request) : NULL;
132    //%%% Add logging of url that timed out.
133    //asl_log(NULL, NULL, ASL_LEVEL_NOTICE, "Timeout during %@ %@.", req_meth, req_url);
134    if(req_url) CFRelease(req_url);
135    if(req_meth) CFRelease(req_meth);
136#endif
137	bool hasNewData = (http->responseLength > http->previousLength);
138	http->previousLength = http->responseLength;
139
140	if(hasNewData) {
141		/* Still getting data, so restart the timer. */
142		CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL };
143		timerContext.info = http;
144		CFRunLoopTimerInvalidate(http->timer);
145		CFReleaseNull(http->timer);
146		http->timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
147			CFAbsoluteTimeGetCurrent() + READ_STREAM_TIMEOUT,
148			0, 0, 0, asynchttp_timer_proc, &timerContext);
149		if (http->timer) {
150			CFRunLoopAddTimer(CFRunLoopGetCurrent(), http->timer, kCFRunLoopDefaultMode);
151			return;
152		}
153	}
154	/* Haven't gotten any more data since last time (or failed to restart timer) */
155	asynchttp_complete(http);
156}
157
158static void handle_server_response(
159	CFReadStreamRef stream,
160    CFStreamEventType type,
161	void *info)
162{
163    asynchttp_t *http = (asynchttp_t *)info;
164    switch (type)
165	{
166		case kCFStreamEventHasBytesAvailable:
167		{
168			size_t len = (http->increment) ? http->increment : READ_BUFFER_SIZE;
169			UInt8 *buf = (UInt8 *) malloc(len);
170			if (!buf) {
171				ocspdErrorLog("http stream: failed to malloc %ld bytes\n", len);
172				asynchttp_complete(http);
173				break;
174			}
175			if (!http->data) {
176				http->data = CFDataCreateMutable(kCFAllocatorDefault, 0);
177				http->responseLength = 0;
178			}
179			do {
180				CFIndex bytesRead = CFReadStreamRead(stream, buf, len);
181				if (bytesRead < 0) {
182					/* Negative length == error */
183					asynchttp_complete(http);
184					break;
185				} else if (bytesRead == 0) {
186					/* Read 0 bytes, we're done */
187					ocspdDebug("http stream: transfer complete, moved %ld bytes\n",
188						http->responseLength);
189					asynchttp_complete(http);
190					break;
191				} else {
192					/* Read some number of bytes */
193					CFDataAppendBytes(http->data, buf, bytesRead);
194					http->responseLength += bytesRead;
195				}
196			} while (CFReadStreamHasBytesAvailable(stream));
197			free(buf);
198			break;
199		}
200		case kCFStreamEventErrorOccurred:
201		{
202			CFStreamError error = CFReadStreamGetError(stream);
203			ocspdErrorLog("http stream: %p kCFStreamEventErrorOccurred, domain: %ld error: %ld\n",
204				stream, error.domain, (long int)error.error);
205			if (error.domain == kCFStreamErrorDomainPOSIX) {
206				ocspdErrorLog("CFReadStream POSIX error: %s\n", strerror(error.error));
207			} else if (error.domain == kCFStreamErrorDomainMacOSStatus) {
208				ocspdErrorLog("CFReadStream OSStatus error: %ld\n", (long int)error.error);
209			} else {
210				ocspdErrorLog("CFReadStream domain: %ld error: %ld\n", error.domain, (long int)error.error);
211			}
212			asynchttp_complete(http);
213			break;
214		}
215		case kCFStreamEventEndEncountered:
216		{
217			http->response = (CFHTTPMessageRef)CFReadStreamCopyProperty(
218				stream, kCFStreamPropertyHTTPResponseHeader);
219			ocspdErrorLog("http stream: %p kCFStreamEventEndEncountered hdr: %p\n",
220				stream, http->response);
221			CFHTTPMessageSetBody(http->response, http->data);
222			asynchttp_complete(http);
223			break;
224		}
225		default:
226		{
227			ocspdErrorLog("handle_server_response: unexpected event type: %lu\n", type);
228			break;
229		}
230    }
231}
232
233
234#pragma mark ----- OCSP support -----
235
236/* POST method has Content-Type header line equal to "application/ocsp-request" */
237static CFStringRef kContentType		= CFSTR("Content-Type");
238static CFStringRef kAppOcspRequest	= CFSTR("application/ocsp-request");
239static CFStringRef kUserAgent		= CFSTR("User-Agent");
240static CFStringRef kAppUserAgent	= CFSTR("ocspd/1.0");
241
242#if OCSP_DEBUG
243#define DUMP_BLOBS	1
244#endif
245
246#define OCSP_GET_FILE	"/tmp/ocspGet"
247#define OCSP_RESP_FILE	"/tmp/ocspResp"
248
249#if		DUMP_BLOBS
250
251static void writeBlob(
252	const char *fileName,
253	const char *whatIsIt,
254	const unsigned char *data,
255	unsigned dataLen)
256{
257	if(writeFile(fileName, data, dataLen)) {
258		printf("***Error writing %s to %s\n", whatIsIt, fileName);
259	}
260	else {
261		printf("...wrote %u bytes of %s to %s\n", dataLen, whatIsIt, fileName);
262	}
263}
264
265#else
266
267#define writeBlob(f,w,d,l)
268
269#endif	/* DUMP_BLOBS */
270
271#if		ENABLE_OCSP_VIA_GET
272
273/* fetch via HTTP GET */
274CSSM_RETURN ocspdHttpGet(
275	SecAsn1CoderRef		coder,
276	const CSSM_DATA 	&url,
277	const CSSM_DATA		&ocspReq,	// DER encoded
278	CSSM_DATA			&fetched)	// mallocd in coder space and RETURNED
279{
280	ocspdHttpDebug("ocspdHttpGet: start\n");
281
282	CSSM_RETURN result = CSSM_OK;
283	CFDataRef urlData = NULL;
284	SInt32 errorCode;
285	unsigned char *endp = NULL;
286	bool done = false;
287	size_t totalLen;
288	unsigned char *fullUrl = NULL;
289	CFURLRef cfUrl = NULL;
290	Boolean brtn;
291	CFIndex len;
292
293	/* trim off possible NULL terminator from incoming URL */
294	size_t urlLen = url.Length;
295	if(url.Data[urlLen - 1] == '\0') {
296		urlLen--;
297	}
298
299	#if OCSP_DEBUG
300	{
301		char *ustr = (char *)malloc(urlLen + 1);
302		memmove(ustr, url.Data, urlLen);
303		ustr[urlLen] = '\0';
304		ocspdDebug("ocspdHttpGet: fetching from URI %s\n", ustr);
305		free(ustr);
306	}
307	#endif
308
309	/* base64 encode the OCSP request; that's used as a path */
310	unsigned char *req64 = NULL;
311	unsigned req64Len = 0;
312	req64 = cuEnc64(ocspReq.Data, ocspReq.Length, &req64Len);
313	if(req64 == NULL) {
314		ocspdErrorLog("ocspdHttpGet: error base64-encoding request\n");
315		result = CSSMERR_TP_INTERNAL_ERROR;
316		goto cleanup;
317	}
318
319	writeBlob(OCSP_GET_FILE, "OCSP Request as URL", req64, req64Len);
320
321	/* trim off trailing NULL and newline */
322	endp = req64 + req64Len - 1;
323	for(;;) {
324		switch(*endp) {
325			case '\0':
326			case '\n':
327			case '\r':
328				endp--;
329				req64Len--;
330				break;
331			default:
332				done = true;
333				break;
334		}
335		if(done) {
336			break;
337		}
338	}
339
340	/* concatenate: URL plus path (see RFC 2616 3.2.2, 5.1.2) */
341	if( (req64Len >= INT_MAX) || (urlLen > (INT_MAX - (1 + req64Len))) ) {
342		/* long URL is long; concatenating these components would overflow totalLen */
343		result = CSSMERR_TP_INVALID_DATA;
344		goto cleanup;
345	}
346	totalLen = urlLen + 1 + req64Len;
347	fullUrl = (unsigned char *)malloc(totalLen);
348	memmove(fullUrl, url.Data, urlLen);
349	fullUrl[urlLen] = '/';
350	memmove(fullUrl + urlLen + 1, req64, req64Len);
351
352	cfUrl = CFURLCreateWithBytes(NULL,
353		fullUrl, totalLen,
354		kCFStringEncodingUTF8,		// right?
355		NULL);						// this is absolute path
356	if(!cfUrl) {
357		ocspdErrorLog("ocspdHttpGet: CFURLCreateWithBytes returned NULL\n");
358		result = CSSMERR_APPLETP_CRL_BAD_URI;
359		goto cleanup;
360	}
361	brtn = CFURLCreateDataAndPropertiesFromResource(NULL,
362		cfUrl,
363		&urlData,
364		NULL,			// no properties
365		NULL,
366		&errorCode);
367	if(!brtn) {
368		ocspdErrorLog("ocspdHttpGet: CFURLCreateDataAndPropertiesFromResource err: %d\n",
369			(int)errorCode);
370		result = CSSMERR_APPLETP_CRL_BAD_URI;
371		goto cleanup;
372	}
373	if(urlData == NULL) {
374		ocspdErrorLog("ocspdHttpGet: CFURLCreateDataAndPropertiesFromResource: no data\n");
375		result = CSSMERR_APPLETP_CRL_BAD_URI;
376		goto cleanup;
377	}
378	len = CFDataGetLength(urlData);
379	fetched.Data = (uint8 *)SecAsn1Malloc(coder, len);
380	fetched.Length = len;
381	memmove(fetched.Data, CFDataGetBytePtr(urlData), len);
382	writeBlob(OCSP_RESP_FILE, "OCSP Response", fetched.Data, fetched.Length);
383cleanup:
384	CFReleaseSafe(cfUrl);
385	CFReleaseSafe(urlData);
386	if(fullUrl) {
387		free(fullUrl);
388	}
389	if(req64) {
390		free(req64);
391	}
392	return result;
393}
394
395#endif		/* ENABLE_OCSP_VIA_GET */
396
397/* fetch via HTTP POST */
398
399CSSM_RETURN ocspdHttpPost(
400	SecAsn1CoderRef		coder,
401	const CSSM_DATA 	&url,
402	const CSSM_DATA		&ocspReq,	// DER encoded
403	CSSM_DATA			&fetched)	// mallocd in coder space and RETURNED
404{
405	ocspdHttpDebug("ocspdHttpPost: start\n");
406
407	CSSM_RETURN result = CSSM_OK;
408	CFURLRef cfUrl = NULL;
409	CFDictionaryRef proxyDict = NULL;
410	CFStreamClientContext clientContext = { 0, NULL, NULL, NULL, NULL };
411	CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL };
412	CFAbsoluteTime startTime, stopTime;
413	asynchttp_t *httpContext = NULL;
414
415	CFDataRef postData = NULL;
416
417	/* trim off possible NULL terminator from incoming URL */
418	uint32 urlLen = url.Length;
419	if(url.Data[urlLen - 1] == '\0') {
420		urlLen--;
421	}
422
423	/* create URL with explicit path (see RFC 2616 3.2.2, 5.1.2) */
424	cfUrl = CFURLCreateWithBytes(NULL,
425		url.Data, urlLen,
426		kCFStringEncodingUTF8,		// right?
427		NULL);						// this is absolute path
428	if(cfUrl) {
429		CFStringRef pathStr = CFURLCopyLastPathComponent(cfUrl);
430		if(pathStr) {
431			if (CFStringGetLength(pathStr) == 0) {
432				CFURLRef tmpUrl = CFURLCreateCopyAppendingPathComponent(NULL,
433					cfUrl, CFSTR(""), FALSE);
434				CFRelease(cfUrl);
435				cfUrl = tmpUrl;
436			}
437			CFRelease(pathStr);
438		}
439	}
440	if(!cfUrl) {
441		ocspdErrorLog("ocspdHttpPost: CFURLCreateWithBytes returned NULL\n");
442		result = CSSMERR_APPLETP_CRL_BAD_URI;
443		goto cleanup;
444	}
445
446	#if OCSP_DEBUG
447	{
448		char *ustr = (char *)malloc(urlLen + 1);
449		memmove(ustr, url.Data, urlLen);
450		ustr[urlLen] = '\0';
451		ocspdDebug("ocspdHttpPost: posting to URI %s\n", ustr);
452		free(ustr);
453	}
454	#endif
455
456	writeBlob(OCSP_GET_FILE, "OCSP Request as POST data", ocspReq.Data, ocspReq.Length);
457	postData = CFDataCreate(NULL, ocspReq.Data, ocspReq.Length);
458
459	/* allocate our http context */
460	httpContext = (asynchttp_t *) malloc(sizeof(asynchttp_t));
461	if(!httpContext) {
462		result = memFullErr;
463		goto cleanup;
464	}
465	memset(httpContext, 0, sizeof(asynchttp_t));
466
467	/* read this many bytes at a time */
468	httpContext->increment = POST_BUFFER_SIZE;
469
470	/* create the http POST request */
471	httpContext->request = CFHTTPMessageCreateRequest(kCFAllocatorDefault,
472		CFSTR("POST"), cfUrl, kCFHTTPVersion1_1);
473	if(!httpContext->request) {
474		ocspdErrorLog("ocspdHttpPost: error creating CFHTTPMessage\n");
475		result = CSSMERR_TP_INTERNAL_ERROR;
476		goto cleanup;
477	}
478
479	/* set the body and required header fields */
480	CFHTTPMessageSetBody(httpContext->request, postData);
481	CFHTTPMessageSetHeaderFieldValue(httpContext->request,
482		kContentType, kAppOcspRequest);
483	CFHTTPMessageSetHeaderFieldValue(httpContext->request,
484		kUserAgent, kAppUserAgent);
485
486	/* create the stream */
487	httpContext->stream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault,
488		httpContext->request);
489	if(!httpContext->stream) {
490		ocspdErrorLog("ocspdHttpPost: error creating CFReadStream\n");
491		result = CSSMERR_TP_INTERNAL_ERROR;
492		goto cleanup;
493	}
494
495	/* specify automatic redirect handling */
496	if(!CFReadStreamSetProperty(httpContext->stream,
497		kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue)) {
498		ocspdErrorLog("ocspdHttpPost: error setting autoredirect property\n");
499		/* this error is non-fatal; keep going */
500	}
501
502	/* set up possible proxy info */
503	proxyDict = SCDynamicStoreCopyProxies(NULL);
504	if(proxyDict) {
505		ocspdDebug("ocspdHttpPost: setting proxy dict\n");
506		CFReadStreamSetProperty(httpContext->stream, kCFStreamPropertyHTTPProxy, proxyDict);
507        CFReleaseNull(proxyDict);
508	}
509
510	/* set a reasonable timeout */
511    timerContext.info = httpContext;
512    httpContext->timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
513        CFAbsoluteTimeGetCurrent() + READ_STREAM_TIMEOUT,
514        0, 0, 0, asynchttp_timer_proc, &timerContext);
515    if (httpContext->timer == NULL) {
516		ocspdErrorLog("ocspdHttpPost: error setting kCFStreamPropertyReadTimeout\n");
517		/* but keep going */
518    } else {
519        CFRunLoopAddTimer(CFRunLoopGetCurrent(), httpContext->timer,
520            kCFRunLoopDefaultMode);
521    }
522
523	/* set up our response callback and schedule it on the current run loop */
524	httpContext->finished = 0;
525	clientContext.info = httpContext;
526    CFReadStreamSetClient(httpContext->stream,
527        (kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered),
528        handle_server_response, &clientContext);
529    CFReadStreamScheduleWithRunLoop(httpContext->stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
530
531	/* open the stream */
532	if(CFReadStreamOpen(httpContext->stream) == false) {
533		ocspdErrorLog("ocspdHttpPost: error opening CFReadStream\n");
534		result = CSSMERR_APPLETP_NETWORK_FAILURE;
535		goto cleanup;
536	}
537
538	/* cycle the run loop until we get a response or time out */
539	startTime = stopTime = CFAbsoluteTimeGetCurrent();
540	while (!httpContext->finished) {
541		(void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, TRUE);
542		CFAbsoluteTime curTime = CFAbsoluteTimeGetCurrent();
543		if (curTime != stopTime) {
544			stopTime = curTime;
545		}
546	}
547
548	if(!httpContext->data || httpContext->responseLength == 0) {
549		ocspdErrorLog("CFReadStreamRead: no data was read after %f seconds\n",
550			stopTime-startTime);
551		result = CSSMERR_APPLETP_NETWORK_FAILURE;
552		goto cleanup;
553	}
554	ocspdDebug("ocspdHttpPost: total %lu bytes read in %f seconds\n",
555		(unsigned long)httpContext->responseLength, stopTime-startTime);
556	SecAsn1AllocCopy(coder, CFDataGetBytePtr(httpContext->data),
557		CFDataGetLength(httpContext->data), &fetched);
558	writeBlob(OCSP_RESP_FILE, "OCSP Response", fetched.Data, fetched.Length);
559	result = CSSM_OK;
560cleanup:
561	CFReleaseSafe(postData);
562	CFReleaseSafe(cfUrl);
563	asynchttp_free(httpContext);
564
565	return result;
566}
567
568#pragma mark ----- LDAP fetch -----
569
570/*
571 * LDAP attribute names, used if not present in URI.
572 */
573#define LDAP_ATTR_CERT		"cacertificate;binary"
574#define LDAP_ATTR_CRL		"certificaterevocationlist;binary"
575
576/*
577 * Default LDAP options.
578 */
579#define LDAP_REFERRAL_DEFAULT	LDAP_OPT_ON
580
581static CSSM_RETURN ldapRtnToCssm(
582	int rtn)
583{
584	switch(rtn) {
585		case LDAP_SERVER_DOWN:
586		case LDAP_TIMEOUT:
587		case LDAP_CONNECT_ERROR:
588			return CSSMERR_APPLETP_CRL_SERVER_DOWN;
589		case LDAP_PARAM_ERROR:
590		case LDAP_FILTER_ERROR:
591			return CSSMERR_APPLETP_CRL_BAD_URI;
592		default:
593			return CSSMERR_APPLETP_CRL_NOT_FOUND;
594	}
595}
596
597static CSSM_RETURN ldapFetch(
598	Allocator			&alloc,
599	const CSSM_DATA 	&url,
600	LF_Type				lfType,
601	CSSM_DATA			&fetched)	// mallocd in alloc space and RETURNED
602{
603	BerValue 		**value = NULL;
604	LDAPURLDesc 	*urlDesc = NULL;
605	int 			rtn;
606	LDAPMessage 	*msg = NULL;
607	LDAP 			*ldap = NULL;
608	LDAPMessage 	*entry = NULL;
609	bool 			mallocdString = false;
610	char 			*urlStr;
611	int 			numEntries;
612	CSSM_RETURN 	ourRtn = CSSM_OK;
613	/* attr input to ldap_search_s() */
614	char			*attrArray[2];
615	char			**attrArrayP = NULL;
616
617	/* don't assume URL string is NULL terminated */
618	if(url.Data[url.Length - 1] == '\0') {
619		urlStr = (char *)url.Data;
620	}
621	else {
622		urlStr = (char *)malloc(url.Length + 1);
623		memmove(urlStr, url.Data, url.Length);
624		urlStr[url.Length] = '\0';
625		mallocdString = true;
626	}
627
628	/* break up the URL into something usable */
629	rtn = ldap_url_parse(urlStr, &urlDesc);
630	if(rtn) {
631		ocspdErrorLog("ldap_url_parse returned %d", rtn);
632		return CSSMERR_APPLETP_CRL_BAD_URI;
633	}
634
635	/*
636	 * Determine what attr we're looking for.
637	 */
638	if((urlDesc->lud_attrs != NULL) &&		// attrs present in URL
639	   (urlDesc->lud_attrs[0] != NULL) &&	// at least one attr present
640	   (urlDesc->lud_attrs[1] == NULL))	{
641		/*
642		 * Exactly one attr present in the caller-specified URL;
643		 * assume that this is exactly what we want.
644		 */
645		attrArrayP = &urlDesc->lud_attrs[0];
646	}
647	else {
648		/* use caller-specified attr */
649		switch(lfType) {
650			case LT_Crl:
651				attrArray[0] = (char *)LDAP_ATTR_CRL;
652				break;
653			case LT_Cert:
654				attrArray[0] = (char *)LDAP_ATTR_CERT;
655				break;
656			default:
657				printf("***ldapFetch screwup: bogus lfType (%d)\n",
658					(int)lfType);
659				return CSSMERR_CSSM_INTERNAL_ERROR;
660		}
661		attrArray[1] = NULL;
662		attrArrayP = &attrArray[0];
663	}
664
665	/* establish connection */
666	rtn = ldap_initialize(&ldap, urlStr);
667	if(rtn) {
668		ocspdErrorLog("ldap_initialize returned %d\n", rtn);
669		return ldapRtnToCssm(rtn);
670	}
671	/* subsequent errors to cleanup: */
672	rtn = ldap_simple_bind_s(ldap, NULL, NULL);
673	if(rtn) {
674		ocspdErrorLog("ldap_simple_bind_s returned %d\n", rtn);
675		ourRtn = ldapRtnToCssm(rtn);
676		goto cleanup;
677	}
678
679	rtn = ldap_set_option(ldap, LDAP_OPT_REFERRALS, LDAP_REFERRAL_DEFAULT);
680	if(rtn) {
681		ocspdErrorLog("ldap_set_option(referrals) returned %d\n", rtn);
682		ourRtn = ldapRtnToCssm(rtn);
683		goto cleanup;
684	}
685
686	rtn = ldap_search_s(
687		ldap,
688		urlDesc->lud_dn,
689		LDAP_SCOPE_SUBTREE,
690		urlDesc->lud_filter,
691		urlDesc->lud_attrs,
692		0, 			// attrsonly
693		&msg);
694	if(rtn) {
695		ocspdErrorLog("ldap_search_s returned %d\n", rtn);
696		ourRtn = ldapRtnToCssm(rtn);
697		goto cleanup;
698	}
699
700	/*
701	 * We require exactly one entry (for now).
702	 */
703	numEntries = ldap_count_entries(ldap, msg);
704	if(numEntries != 1) {
705		ocspdErrorLog("tpCrlViaLdap: numEntries %d\n", numEntries);
706		ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND;
707		goto cleanup;
708	}
709
710	entry = ldap_first_entry(ldap, msg);
711	value = ldap_get_values_len(ldap, msg, attrArrayP[0]);
712	if(value == NULL) {
713		ocspdErrorLog("Error on ldap_get_values_len\n");
714		ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND;
715		goto cleanup;
716	}
717
718	fetched.Length = value[0]->bv_len;
719	fetched.Data = (uint8 *)alloc.malloc(fetched.Length);
720	memmove(fetched.Data, value[0]->bv_val, fetched.Length);
721
722	ldap_value_free_len(value);
723	ourRtn = CSSM_OK;
724cleanup:
725	if(msg) {
726		ldap_msgfree(msg);
727	}
728	if(mallocdString) {
729		free(urlStr);
730	}
731	ldap_free_urldesc(urlDesc);
732	rtn = ldap_unbind(ldap);
733	if(rtn) {
734		ocspdErrorLog("Error %d on ldap_unbind\n", rtn);
735		/* oh well */
736	}
737	return ourRtn;
738}
739
740#pragma mark ----- HTTP fetch via GET -----
741
742/* fetch via HTTP */
743static CSSM_RETURN httpFetch(
744	Allocator			&alloc,
745	const CSSM_DATA 	&url,
746	LF_Type				lfType,
747	CSSM_DATA			&fetched)	// mallocd in alloc space and RETURNED
748{
749	#pragma unused (lfType)
750	ocspdHttpDebug("httpFetch: start\n");
751
752	CSSM_RETURN result = CSSM_OK;
753	CFURLRef cfUrl = NULL;
754	CFDictionaryRef proxyDict = NULL;
755	CFStreamClientContext clientContext = { 0, NULL, NULL, NULL, NULL };
756	CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL };
757	CFAbsoluteTime startTime, stopTime;
758	asynchttp_t *httpContext = NULL;
759
760	/* trim off possible NULL terminator from incoming URL */
761	CSSM_DATA theUrl = url;
762	if(theUrl.Data[theUrl.Length - 1] == '\0') {
763		theUrl.Length--;
764	}
765
766	/* create URL with explicit path (see RFC 2616 3.2.2, 5.1.2) */
767	cfUrl = CFURLCreateWithBytes(NULL,
768		theUrl.Data, theUrl.Length,
769		kCFStringEncodingUTF8,		// right?
770		NULL);						// this is absolute path
771	if(cfUrl) {
772		CFStringRef pathStr = CFURLCopyLastPathComponent(cfUrl);
773		if(pathStr) {
774			if (CFStringGetLength(pathStr) == 0) {
775				CFURLRef tmpUrl = CFURLCreateCopyAppendingPathComponent(NULL,
776					cfUrl, CFSTR(""), FALSE);
777				CFRelease(cfUrl);
778				cfUrl = tmpUrl;
779			}
780			CFRelease(pathStr);
781		}
782	}
783	if(!cfUrl) {
784		ocspdErrorLog("httpFetch: CFURLCreateWithBytes returned NULL\n");
785		result = CSSMERR_APPLETP_CRL_BAD_URI;
786		goto cleanup;
787	}
788
789	#if OCSP_DEBUG
790	{
791		char *ustr = (char *)malloc(theUrl.Length + 1);
792		memmove(ustr, theUrl.Data, theUrl.Length);
793		ustr[theUrl.Length] = '\0';
794		ocspdDebug("httpFetch: GET URI %s\n", ustr);
795		free(ustr);
796	}
797	#endif
798
799	/* allocate our http context */
800	httpContext = (asynchttp_t *) malloc(sizeof(asynchttp_t));
801	if(!httpContext) {
802		result = memFullErr;
803		goto cleanup;
804	}
805	memset(httpContext, 0, sizeof(asynchttp_t));
806
807	/* read this many bytes at a time */
808	httpContext->increment = READ_BUFFER_SIZE;
809
810	/* create the http GET request */
811	httpContext->request = CFHTTPMessageCreateRequest(NULL,
812		CFSTR("GET"), cfUrl, kCFHTTPVersion1_1);
813	if(!httpContext->request) {
814		ocspdErrorLog("httpFetch: error creating CFHTTPMessage\n");
815		result = CSSMERR_TP_INTERNAL_ERROR;
816		goto cleanup;
817	}
818	CFHTTPMessageSetHeaderFieldValue(httpContext->request,
819		kUserAgent, kAppUserAgent);
820
821	/* create the stream */
822	httpContext->stream = CFReadStreamCreateForHTTPRequest(NULL,
823		httpContext->request);
824	if(!httpContext->stream) {
825		ocspdErrorLog("httpFetch: error creating CFReadStream\n");
826		result = CSSMERR_TP_INTERNAL_ERROR;
827		goto cleanup;
828	}
829
830	/* specify automatic redirect handling */
831	if(!CFReadStreamSetProperty(httpContext->stream,
832		kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue)) {
833		ocspdErrorLog("httpFetch: error setting autoredirect property\n");
834		/* this error is non-fatal; keep going */
835	}
836
837	/* set up possible proxy info */
838	proxyDict = SCDynamicStoreCopyProxies(NULL);
839	if(proxyDict) {
840		ocspdDebug("httpFetch: setting proxy dict\n");
841		CFReadStreamSetProperty(httpContext->stream, kCFStreamPropertyHTTPProxy, proxyDict);
842        CFReleaseNull(proxyDict);
843	}
844
845	/* set a reasonable timeout */
846    timerContext.info = httpContext;
847    httpContext->timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
848        CFAbsoluteTimeGetCurrent() + READ_STREAM_TIMEOUT,
849        0, 0, 0, asynchttp_timer_proc, &timerContext);
850    if (httpContext->timer == NULL) {
851        ocspdErrorLog("httpFetch: error creating timer\n");
852		/* oh well - keep going */
853    } else {
854        CFRunLoopAddTimer(CFRunLoopGetCurrent(), httpContext->timer,
855            kCFRunLoopDefaultMode);
856    }
857
858	/* set up our response callback and schedule it on the current run loop */
859	httpContext->finished = 0;
860	clientContext.info = httpContext;
861    CFReadStreamSetClient(httpContext->stream,
862        (kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered),
863        handle_server_response, &clientContext);
864    CFReadStreamScheduleWithRunLoop(httpContext->stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
865
866	/* open the stream */
867	if(CFReadStreamOpen(httpContext->stream) == false) {
868		ocspdErrorLog("httpFetch: error opening CFReadStream\n");
869		result = CSSMERR_APPLETP_NETWORK_FAILURE;
870		goto cleanup;
871	}
872
873	/* cycle the run loop until we get a response or time out */
874	startTime = stopTime = CFAbsoluteTimeGetCurrent();
875	while (!httpContext->finished) {
876		(void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, TRUE);
877		CFAbsoluteTime curTime = CFAbsoluteTimeGetCurrent();
878		if (curTime != stopTime) {
879			stopTime = curTime;
880		}
881	}
882
883	if(httpContext->responseLength == 0) {
884		ocspdErrorLog("httpFetch: CFReadStreamRead: no data was read after %f seconds\n",
885			stopTime-startTime);
886		result = CSSMERR_APPLETP_NETWORK_FAILURE;
887		goto cleanup;
888	}
889	ocspdDebug("httpFetch: total %lu bytes read in %f seconds\n",
890		(unsigned long)httpContext->responseLength, stopTime-startTime);
891	if(httpContext->data) {
892		CFIndex len = CFDataGetLength(httpContext->data);
893		fetched.Data = (uint8 *)alloc.malloc(len);
894		fetched.Length = len;
895		memmove(fetched.Data, CFDataGetBytePtr(httpContext->data), len);
896	} else {
897		result = CSSMERR_TP_INTERNAL_ERROR;
898	}
899cleanup:
900	CFReleaseSafe(cfUrl);
901	asynchttp_free(httpContext);
902
903	return result;
904}
905
906/* Fetch cert or CRL from net, we figure out the schema */
907CSSM_RETURN ocspdNetFetch(
908	Allocator			&alloc,
909	const CSSM_DATA 	&url,
910	LF_Type				lfType,
911	CSSM_DATA			&fetched)	// mallocd in alloc space and RETURNED
912{
913	#if OCSP_DEBUG
914	{
915		char *ustr = (char *)malloc(url.Length + 1);
916		memmove(ustr, url.Data, url.Length);
917		ustr[url.Length] = '\0';
918		ocspdDebug("ocspdNetFetch: fetching from URI %s\n", ustr);
919		free(ustr);
920	}
921	#endif
922
923	if(url.Length < 5) {
924		return CSSMERR_APPLETP_CRL_BAD_URI;
925	}
926	if(!strncmp((char *)url.Data, "ldap:", 5)) {
927		return ldapFetch(alloc, url, lfType, fetched);
928	}
929	if(!strncmp((char *)url.Data, "http:", 5) ||
930	   !strncmp((char *)url.Data, "https:", 6)) {
931		return httpFetch(alloc, url, lfType, fetched);
932	}
933	return CSSMERR_APPLETP_CRL_BAD_URI;
934}
935
936/* Maximum CRL length to consider putting in the cache db (128KB) */
937#define CRL_MAX_DATA_LENGTH (1024*128)
938
939/* Post-process network fetched data after finishing download. */
940CSSM_RETURN ocspdFinishNetFetch(
941	async_fetch_t *fetchParams)
942{
943	CSSM_RETURN crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
944	if(!fetchParams) {
945		return crtn;
946	}
947	StLock<Mutex> _(gParamsLock); /* lock before accessing parameters */
948	if(fetchParams->result != CSSM_OK) {
949		ocspdErrorLog("ocspdFinishNetFetch: CRL not found on net");
950		crtn = fetchParams->result;
951	}
952	else if(fetchParams->fetched.Length == 0) {
953		ocspdErrorLog("ocspdFinishNetFetch: no CRL data found");
954		crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
955	}
956	else if(fetchParams->fetched.Length > CRL_MAX_DATA_LENGTH) {
957		if (fetchParams->fetched.Data) {
958			/* Write oversize CRL data to file */
959			StLock<Mutex> w_(gFileWriteLock);
960			crlCheckCachePath();
961			int rtn = writeFile(fetchParams->outFile, fetchParams->fetched.Data,
962				fetchParams->fetched.Length);
963			if(rtn) {
964				ocspdErrorLog("Error %d writing %s\n", rtn, fetchParams->outFile);
965			}
966			else {
967				ocspdCrlDebug("ocspdFinishNetFetch wrote %lu bytes to %s",
968					fetchParams->fetched.Length, fetchParams->outFile);
969
970				if(chmod(fetchParams->outFile, 0644)) {
971					ocspdErrorLog("ocspdFinishNetFetch: chmod error %d for %s",
972						errno, fetchParams->outFile);
973				}
974			}
975			(*(fetchParams->alloc)).free(fetchParams->fetched.Data);
976			fetchParams->fetched.Data = NULL;
977		}
978		crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
979	}
980	return crtn;
981}
982
983/* Fetch cert or CRL from net asynchronously. */
984void ocspdNetFetchAsync(
985	void *context)
986{
987	async_fetch_t *params = (async_fetch_t *)context;
988	ocspdCrlDebug("ocspdNetFetchAsync with context %p", context);
989	CSSM_RETURN crtn = 0;
990	CFStringRef fileNameStr = NULL;
991	CFStringRef pemNameStr = NULL;
992	CFAbsoluteTime fetchTime, verifyTime;
993	CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
994	Boolean downloadInProgress = false;
995	Boolean wroteFile = false;
996	Boolean isCRL = false;
997
998	if(params) {
999		StLock<Mutex> _(gParamsLock); /* lock before accessing parameters */
1000		params->finished = 0;
1001		isCRL = (params->lfType == LT_Crl);
1002		if(params->crlNames.pemFile) {
1003			pemNameStr = CFStringCreateWithCString(kCFAllocatorDefault,
1004				params->crlNames.pemFile, kCFStringEncodingUTF8);
1005		}
1006		if(params->outFile) {
1007			fileNameStr = CFStringCreateWithCString(kCFAllocatorDefault,
1008				params->outFile, kCFStringEncodingUTF8);
1009		}
1010		if(fileNameStr) {
1011			/* make sure we aren't already downloading this file */
1012			StLock<Mutex> _(gListLock); /* lock before examining list */
1013			if(gDownloadList == NULL) {
1014				gDownloadList = CFArrayCreateMutable(kCFAllocatorDefault,
1015					0, &kCFTypeArrayCallBacks);
1016				crtn = (gDownloadList) ? crtn : CSSMERR_TP_INTERNAL_ERROR;
1017				params->result = crtn;
1018			}
1019			if(!crtn) {
1020				downloadInProgress = CFArrayContainsValue(gDownloadList,
1021					CFRangeMake(0, CFArrayGetCount(gDownloadList)), fileNameStr);
1022				if(!downloadInProgress) {
1023					/* add this filename to the global list which tells other
1024					 * callers of the crlStatus MIG function that we are
1025					 * already downloading this file.
1026					 */
1027					CFArrayAppendValue(gDownloadList, fileNameStr);
1028				} else {
1029					/* already downloading; indicate "busy, try later" status */
1030					crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
1031					params->result = crtn;
1032				}
1033			}
1034		}
1035	}
1036
1037	if(params && !crtn && !downloadInProgress) {
1038		/* fetch data into buffer */
1039		crtn = ocspdNetFetch(*(params->alloc),
1040			params->url, params->lfType, params->fetched);
1041		{
1042			StLock<Mutex> _(gParamsLock);
1043			params->result = crtn;
1044		}
1045		/* potentially write data to file */
1046		crtn = ocspdFinishNetFetch(params);
1047		{
1048			StLock<Mutex> _(gParamsLock);
1049			params->result = crtn;
1050			wroteFile = (!params->fetched.Data && params->fetched.Length > CRL_MAX_DATA_LENGTH);
1051		}
1052		fetchTime = CFAbsoluteTimeGetCurrent() - startTime;
1053		ocspdCrlDebug("%f seconds to download file", fetchTime);
1054
1055		if(isCRL && wroteFile) {
1056			/* write issuers to .pem file */
1057			StLock<Mutex> _(gListLock); /* lock before examining list */
1058			CFDataRef issuersData = NULL;
1059			if(gIssuersDict) {
1060				issuersData = (CFDataRef)CFDictionaryGetValue(gIssuersDict,
1061					pemNameStr);
1062			} else {
1063				ocspdCrlDebug("No issuers available for %s",
1064					params->crlNames.pemFile);
1065				gIssuersDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1066					&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1067			}
1068			if(!issuersData) {
1069				/* add the Apple issuers if we have nothing else */
1070				issuersData = CFDataCreate(kCFAllocatorDefault,
1071					(const UInt8 *)Apple_CRL_Issuers, (CFIndex)Apple_CRL_Issuers_Length);
1072				if(issuersData) {
1073					CFDictionarySetValue(gIssuersDict, pemNameStr, issuersData);
1074					CFRelease(issuersData);
1075				}
1076			}
1077			if(issuersData) {
1078				StLock<Mutex> _(gFileWriteLock); /* obtain lock before writing */
1079				crlCheckCachePath();
1080				int rtn = writeFile(params->crlNames.pemFile,
1081					(const unsigned char *)CFDataGetBytePtr(issuersData),
1082					CFDataGetLength(issuersData));
1083				if(rtn) {
1084					ocspdErrorLog("Error %d writing %s\n",
1085						rtn, params->crlNames.pemFile);
1086				}
1087				else if(chmod(params->crlNames.pemFile, 0644)) {
1088					ocspdErrorLog("ocsp_server_crlStatus: chmod error %d for %s",
1089						errno, params->crlNames.pemFile);
1090				}
1091			}
1092		}
1093
1094		if(isCRL && wroteFile) {
1095			/* validate .crl signature (creates .update and .revoked files) */
1096			crlSignatureValid(params->crlNames.crlFile,
1097				params->crlNames.pemFile,
1098				params->crlNames.updateFile,
1099				params->crlNames.revokedFile);
1100			verifyTime = ( CFAbsoluteTimeGetCurrent() - startTime ) - fetchTime;
1101			ocspdCrlDebug("%f seconds to validate CRL", verifyTime);
1102		}
1103
1104		if(fileNameStr) {
1105			/* all finished downloading, so remove filename from global list */
1106			StLock<Mutex> _(gListLock);
1107			CFIndex idx =  CFArrayGetFirstIndexOfValue(gDownloadList,
1108				CFRangeMake(0, CFArrayGetCount(gDownloadList)), fileNameStr);
1109			if(idx >= 0) {
1110				CFArrayRemoveValueAtIndex(gDownloadList, idx);
1111			}
1112		}
1113	}
1114
1115	if(params) {
1116		StLock<Mutex> _(gParamsLock);
1117		params->finished = 1;
1118
1119		if(params->freeOnDone) {
1120			/* caller does not expect a reply; we must clean up everything. */
1121			if(params->url.Data) {
1122				free(params->url.Data);
1123			}
1124			if(params->outFile) {
1125				free(params->outFile);
1126			}
1127			if(params->crlNames.crlFile) {
1128				free(params->crlNames.crlFile);
1129			}
1130			if(params->crlNames.pemFile) {
1131				free(params->crlNames.pemFile);
1132			}
1133			if(params->crlNames.updateFile) {
1134				free(params->crlNames.updateFile);
1135			}
1136			if(params->crlNames.revokedFile) {
1137				free(params->crlNames.revokedFile);
1138			}
1139			if(params->fetched.Data) {
1140				(*(params->alloc)).free(params->fetched.Data);
1141			}
1142			free(params);
1143		}
1144	}
1145
1146	if(fileNameStr) {
1147		CFRelease(fileNameStr);
1148	}
1149	if(pemNameStr) {
1150		CFRelease(pemNameStr);
1151	}
1152}
1153
1154/* Kick off net fetch of a cert or a CRL and return immediately. */
1155CSSM_RETURN ocspdStartNetFetch(
1156	async_fetch_t		*fetchParams)
1157{
1158	dispatch_queue_t queue = dispatch_get_global_queue(
1159		DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1160
1161	ocspdCrlDebug("ocspdStartNetFetch with context %p", (void*)fetchParams);
1162
1163	dispatch_async_f(queue, fetchParams, ocspdNetFetchAsync);
1164
1165	return 0;
1166}
1167
1168