1/*
2 * Copyright (c) 2012-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 * ocspdNetwork.mm - Network support for ocspd and CRL/cert fetch
26 */
27
28#if OCSP_DEBUG
29#define OCSP_USE_SYSLOG	1
30#endif
31#include <security_ocspd/ocspdDebug.h>
32#include "appleCrlIssuers.h"
33#include "ocspdNetwork.h"
34#include <security_ocspd/ocspdUtils.h>
35#include <CoreFoundation/CoreFoundation.h>
36#include <security_cdsa_utils/cuEnc64.h>
37#include <security_cdsa_utils/cuFileIo.h>
38#include <stdlib.h>
39#include <netinet/in.h>
40#include <sys/socket.h>
41#include <sys/stat.h>
42#include <dispatch/dispatch.h>
43#include <Security/cssmapple.h>
44#include <security_utilities/cfutilities.h>
45#include <CoreServices/CoreServices.h>
46#include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
47#include <SystemConfiguration/SCNetworkReachability.h>
48
49#include <Foundation/Foundation.h>
50
51/* enable deprecated function declarations */
52#ifndef LDAP_DEPRECATED
53#define LDAP_DEPRECATED 1
54#endif
55#include <LDAP/ldap.h>
56
57/* useful macros for CF */
58#define CFReleaseSafe(CF) { CFTypeRef _cf = (CF); if (_cf) CFRelease(_cf); }
59#define CFReleaseNull(CF) { CFTypeRef _cf = (CF); \
60	if (_cf) { (CF) = NULL; CFRelease(_cf); } }
61
62extern Mutex gParamsLock;
63extern Mutex gFileWriteLock;
64extern Mutex gListLock;
65extern CFMutableArrayRef gDownloadList;
66extern CFMutableDictionaryRef gIssuersDict;
67extern CFAbsoluteTime gLastActivity;
68
69extern bool crlSignatureValid(
70	const char *crlFileName,
71	const char *issuersFileName,
72	const char *updateFileName,
73	const char *revokedFileName);
74
75extern int crlCheckCachePath();
76
77static const char* SYSTEM_KC_PATH = "/Library/Keychains/System.keychain";
78
79/* how long to wait for more data to come in before we give up */
80#define READ_STREAM_TIMEOUT	7.0
81
82/* read buffer size */
83#define READ_BUFFER_SIZE	4096
84
85/* post buffer size */
86#define POST_BUFFER_SIZE	1024
87
88/* max age of upstream cached response */
89#define CACHED_MAX_AGE		300
90
91
92#pragma mark -- SecURLLoader --
93
94@interface SecURLLoader : NSObject
95{
96	NSURL *_url;
97	NSMutableURLRequest *_request;
98	NSURLConnection *_connection;
99	NSMutableData *_receivedData;
100	CFAbsoluteTime _timeToGiveUp;
101	NSData *_data;
102	NSTimer *_timer;
103	NSError *_error;
104	BOOL _finished;
105}
106
107- (id)initWithURL:(NSURL *)theURL;
108- (NSMutableURLRequest *)request;
109- (void)startLoad;
110- (void)syncLoad;
111- (void)cancelLoad;
112- (void)resetTimeout;
113- (BOOL)finished;
114- (NSData *)data;
115- (NSError*)error;
116@end
117
118@implementation SecURLLoader
119
120- (id)initWithURL:(NSURL *)theURL
121{
122	if (self = [super init]) {
123		_url = [theURL copy];
124	}
125	return self;
126}
127
128- (void) dealloc
129{
130	[self cancelLoad];
131	[_url release];
132	[_request release];
133	[_data release];
134	[_error release];
135	[super dealloc];
136}
137
138- (NSMutableURLRequest *)request
139{
140	if (!_request) {
141		_request = [[NSMutableURLRequest alloc] initWithURL:_url];
142	}
143
144	// Set cache policy to always load from origin and not the local cache
145	[_request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
146
147	return _request;
148}
149
150- (void)startLoad
151{
152	// Cancel any load currently in progress and clear instance variables
153	[self cancelLoad];
154
155	_finished = NO;
156
157	// Create an empty data to receive bytes from the download
158	_receivedData = [[NSMutableData alloc] init];
159
160	// Start the download
161	_connection = [[NSURLConnection alloc] initWithRequest:[self request] delegate:self];
162	if (!_connection) {
163		ocspdDebug("Failed to open connection (is network available?)");
164		[self timeout];
165		return;
166	}
167
168	// Start the timer
169	[self resetTimeout];
170	_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self
171		selector:@selector(timeoutCheck)
172		userInfo:nil repeats:YES];
173	[_timer retain];
174}
175
176- (void)syncLoad
177{
178	[self startLoad];
179
180	// cycle the run loop until we get a response or time out
181	CFAbsoluteTime stopTime = CFAbsoluteTimeGetCurrent();
182	while (![self finished]) {
183		(void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, TRUE);
184		CFAbsoluteTime curTime = CFAbsoluteTimeGetCurrent();
185		if (curTime != stopTime) {
186			stopTime = curTime;
187		}
188	}
189}
190
191- (void)cancelLoad
192{
193	if (_timer) {
194		[_timer invalidate];
195		[_timer release];
196		_timer = nil;
197	}
198	if (_connection) {
199		[_connection cancel];
200		[_connection release];
201		_connection = nil;
202	}
203	if (_receivedData) {
204		// Hold onto the last data we received
205		if (_data) [_data release];
206		_data = _receivedData;
207		_receivedData = nil;
208	}
209}
210
211- (void)timeoutCheck
212{
213	if (_finished) {
214		return; // already completed
215	}
216	if (_timeToGiveUp > CFAbsoluteTimeGetCurrent()) {
217		return; // not time yet...
218	}
219	// give up and cancel the download
220	[self cancelLoad];
221	OSStatus err = CSSMERR_APPLETP_NETWORK_FAILURE;
222	_error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
223	_finished = YES;
224}
225
226- (void)resetTimeout
227{
228	gLastActivity = CFAbsoluteTimeGetCurrent();
229	_timeToGiveUp = gLastActivity + READ_STREAM_TIMEOUT;
230}
231
232- (BOOL)finished
233{
234	return _finished;
235}
236
237- (NSData *)data
238{
239	return _data;
240}
241
242- (NSError *)error
243{
244	return _error;
245}
246
247/* NSURLConnection delegate methods */
248- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)newData
249{
250	if (![newData length])
251		return;
252
253	[_receivedData appendData:newData];
254	[self resetTimeout];
255}
256
257- (void)connectionDidFinishLoading:(NSURLConnection *)connection
258{
259	[self cancelLoad];
260	if (_error) {
261		[_error release];
262		_error = nil;
263	}
264	_finished = YES;
265}
266
267- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
268{
269	//CFNetDiagnosticRef diagnostics = CFNetDiagnosticCreateWithURL(NULL, (CFURLRef)url);
270	//%%% add debug code to print diagnostics
271
272	[self cancelLoad];
273	_error = [error retain];
274	_finished = YES;
275}
276
277- (BOOL)connection:(NSURLConnection *)connection
278	canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
279{
280	return ([protectionSpace isProxy]);
281}
282
283- (void)connection:(NSURLConnection *)connection
284	didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
285{
286	NSInteger failCount = [challenge previousFailureCount];
287	NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
288	if (failCount > 0) {
289		#if OCSP_DEBUG
290		NSLog(@"Cancelling authentication challenge (failure count = %ld). Is proxy password correct?", (long)failCount);
291		#endif
292		[[challenge sender] cancelAuthenticationChallenge:challenge];
293		return;
294	}
295	// Try to look up our proxy credential in the System keychain.
296	// Don't specify protocol (kSecProtocolTypeHTTPProxy, etc.) in our item since that
297	// can cause it to be found by AuthBrokerAgent, and since it's running as the user,
298	// it won't have access to the System keychain.
299	//
300	NSURLCredential *credential = NULL;
301	SecKeychainRef keychain = NULL;
302	NSString *host = [protectionSpace host];
303	NSInteger port = [protectionSpace port];
304	OSStatus status = SecKeychainOpen(SYSTEM_KC_PATH, &keychain);
305	if (status == noErr) {
306		// ask for attributes as well, so we can get the account name
307		NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
308                               (id)keychain, kSecUseKeychain,
309                               (id)kSecClassInternetPassword, kSecClass,
310                               (id)host, kSecAttrServer,
311                               (id)[NSNumber numberWithInteger:port], kSecAttrPort,
312                               (id)kSecMatchLimitOne, kSecMatchLimit,
313                               (id)[NSNumber numberWithBool:YES], kSecReturnAttributes,
314                               (id)[NSNumber numberWithBool:YES], kSecReturnData,
315                               (id)nil, (id)nil];
316
317		CFDictionaryRef result = NULL;
318		status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result);
319		#if OCSP_DEBUG
320		NSLog(@"SecItemCopyMatching returned %d looking up host=%@, port=%d",
321			(int)status, host, (int)port);
322		#endif
323		if (!status && result) {
324			if (CFDictionaryGetTypeID() == CFGetTypeID(result)) {
325				NSString *account = [(NSDictionary *)result objectForKey:(id)kSecAttrAccount];
326				NSData *passwordData = [(NSDictionary *)result objectForKey:(id)kSecValueData];
327				NSString *password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding];
328				if (account && password) {
329					#if OCSP_DEBUG
330					NSLog(@"Found credential for %@:%d (length=%ld)", host, (int)port, (long)[password length]);
331					#endif
332					credential = [NSURLCredential credentialWithUser:account password:password persistence:NSURLCredentialPersistenceForSession];
333				}
334				[password release];
335			}
336			CFRelease(result);
337		}
338	}
339	else {
340		#if OCSP_DEBUG
341		NSLog(@"SecKeychainOpen error %d", (int)status);
342		#endif
343	}
344	if (keychain) {
345		CFRelease(keychain);
346	}
347	if (credential) {
348		#if OCSP_DEBUG
349		NSLog(@"Authentication challenge received, setting proxy credential");
350		#endif
351		[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
352		return;
353	}
354
355	NSLog(@"Authentication challenge received for \"%@:%d\", unable to obtain proxy server credential from System keychain.", host, (int)port);
356	static bool printedHint = false;
357	if (!printedHint) {
358		NSLog(@"You can specify the username and password for a proxy server with /usr/bin/security. Example:  sudo security add-internet-password -a squiduser -l \"HTTP Proxy\" -P 3128 -r 'http' -s localhost -w squidpass -U -T /usr/sbin/ocspd /Library/Keychains/System.keychain");
359		printedHint = true;
360	}
361	// Cancel the challenge since we do not have a credential to present (14761252)
362	[[challenge sender] cancelAuthenticationChallenge:challenge];
363}
364
365/* NSURLConnectionDataDelegate methods */
366- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
367{
368	// Returning nil prevents the loader from passing the response to the URL cache sub-system for caching.
369	return (NSCachedURLResponse *)nil;
370}
371
372@end
373
374void enableAutoreleasePool(int enable);
375void enableAutoreleasePool(int enable)
376{
377	static NSAutoreleasePool *_pool = NULL;
378	if (!_pool) {
379		_pool = [[NSAutoreleasePool alloc] init];
380	}
381	if (!enable && _pool) {
382		[_pool drain];
383	}
384}
385
386#pragma mark ----- OCSP fetch -----
387
388/* POST method has Content-Type header line equal to "application/ocsp-request" */
389static NSString* kContentType       = @"Content-Type";
390static NSString* kAppOcspRequest    = @"application/ocsp-request";
391static NSString* kContentLength     = @"Content-Length";
392static NSString* kUserAgent         = @"User-Agent";
393static NSString* kAppUserAgent      = @"ocspd/1.0.3";
394static NSString* kCacheControl      = @"Cache-Control";
395
396#if OCSP_DEBUG
397#define DUMP_BLOBS	1
398#endif
399
400#define OCSP_GET_FILE	"/tmp/ocspGet"
401#define OCSP_RESP_FILE	"/tmp/ocspResp"
402
403#if		DUMP_BLOBS
404
405static void writeBlob(
406	const char *fileName,
407	const char *whatIsIt,
408	const unsigned char *data,
409	unsigned dataLen)
410{
411	if(writeFile(fileName, data, dataLen)) {
412		printf("***Error writing %s to %s\n", whatIsIt, fileName);
413	}
414	else {
415		printf("...wrote %u bytes of %s to %s\n", dataLen, whatIsIt, fileName);
416	}
417}
418
419#else
420
421#define writeBlob(f,w,d,l)
422
423#endif	/* DUMP_BLOBS */
424
425/* OCSP fetch via HTTP using GET (preferred) or POST (if required) */
426
427CSSM_RETURN ocspdHttpFetch(
428    SecAsn1CoderRef		coder,
429    const CSSM_DATA 	&url,
430    const CSSM_DATA		&ocspReq,	// DER encoded
431    CSSM_DATA			&fetched)	// mallocd in coder space and RETURNED
432{
433	CSSM_RETURN result = CSSM_OK;
434	unsigned char *fullUrl = NULL;
435	CFURLRef cfUrl = NULL;
436	CFStringRef urlStr = NULL;
437    CFDataRef postData = NULL;
438    SecURLLoader *urlLoader = nil;
439    bool done = false;
440    size_t totalLen;
441    CFIndex len;
442
443	/* trim off possible NULL terminator from incoming URL */
444	size_t urlLen = url.Length;
445	if(url.Data[urlLen - 1] == '\0') {
446		urlLen--;
447	}
448
449    /* base64 encode the OCSP request; that's used as a path */
450	unsigned char *endp, *req64 = NULL;
451	unsigned req64Len = 0;
452	req64 = cuEnc64(ocspReq.Data, (unsigned)ocspReq.Length, &req64Len);
453	if(req64 == NULL) {
454		ocspdErrorLog("ocspdHttpFetch: error base64-encoding request\n");
455		result = CSSMERR_TP_INTERNAL_ERROR;
456		goto cleanup;
457	}
458
459	/* trim off trailing NULL and newline */
460    endp = req64 + req64Len - 1;
461	for(;;) {
462		switch(*endp) {
463			case '\0':
464			case '\n':
465			case '\r':
466				endp--;
467				req64Len--;
468				break;
469			default:
470				done = true;
471				break;
472		}
473		if(done) {
474			break;
475		}
476	}
477
478    /* sanity check length of request */
479	if( (req64Len >= INT_MAX) || (urlLen > (INT_MAX - (1 + req64Len))) ) {
480		/* long URL is long; concatenating these components would overflow totalLen */
481		result = CSSMERR_TP_INVALID_DATA;
482		goto cleanup;
483	}
484
485	if(urlLen && req64Len) {
486		CFStringRef incomingURLStr = CFStringCreateWithBytes(NULL, (const UInt8 *)url.Data, urlLen, kCFStringEncodingUTF8, false);
487		CFStringRef requestPathStr = CFStringCreateWithBytes(NULL, (const UInt8 *)req64, req64Len, kCFStringEncodingUTF8, false);
488		if(incomingURLStr && requestPathStr) {
489			/* percent-encode all reserved characters from RFC 3986 [2.2] */
490			CFStringRef encodedRequestStr = CFURLCreateStringByAddingPercentEscapes(NULL,
491				requestPathStr, NULL, CFSTR(":/?#[]@!$&'()*+,;="), kCFStringEncodingUTF8);
492			if(encodedRequestStr) {
493				CFMutableStringRef tempStr = CFStringCreateMutable(NULL, 0);
494				if (tempStr) {
495					CFStringAppend(tempStr, incomingURLStr);
496					CFStringAppend(tempStr, CFSTR("/"));
497					CFStringAppend(tempStr, encodedRequestStr);
498					urlStr = (CFStringRef)tempStr;
499				}
500			}
501			CFReleaseSafe(encodedRequestStr);
502		}
503		CFReleaseSafe(incomingURLStr);
504		CFReleaseSafe(requestPathStr);
505	}
506	if(urlStr == NULL) {
507		ocspdErrorLog("ocspdHttpFetch: error percent-encoding request\n");
508		result = CSSMERR_TP_INTERNAL_ERROR;
509		goto cleanup;
510	}
511
512    /* RFC 5019 says we MUST use the GET method if the URI is less than 256 bytes
513     * (to enable response caching), otherwise we SHOULD use the POST method.
514     */
515	totalLen = CFStringGetLength(urlStr);
516    if (totalLen < 256) {
517        /* we can safely use GET */
518		cfUrl = CFURLCreateWithString(NULL, urlStr, NULL);
519    }
520    else {
521        /* request too big for GET; use POST instead */
522        writeBlob(OCSP_GET_FILE, "OCSP Request as POST data", ocspReq.Data, (unsigned int)ocspReq.Length);
523        postData = CFDataCreate(NULL, ocspReq.Data, ocspReq.Length);
524        cfUrl = CFURLCreateWithBytes(NULL, url.Data, urlLen,
525                                     kCFStringEncodingUTF8,
526                                     NULL);
527    }
528
529    if(cfUrl) {
530        /* create URL with explicit path (see RFC 2616 3.2.2, 5.1.2) */
531        CFStringRef pathStr = CFURLCopyLastPathComponent(cfUrl);
532        if(pathStr) {
533            if (CFStringGetLength(pathStr) == 0) {
534                CFURLRef tmpUrl = CFURLCreateCopyAppendingPathComponent(NULL, cfUrl, CFSTR(""), FALSE);
535                CFRelease(cfUrl);
536                cfUrl = tmpUrl;
537            }
538            CFRelease(pathStr);
539        }
540    }
541    if(!cfUrl) {
542        ocspdErrorLog("ocspdHttpFetch: CFURLCreateWithBytes returned NULL\n");
543        result = CSSMERR_APPLETP_CRL_BAD_URI;
544        goto cleanup;
545    }
546
547#if OCSP_DEBUG
548	{
549		size_t len = (fullUrl) ? totalLen : urlLen;
550		char *ubuf = (char *)((fullUrl) ? fullUrl : url.Data);
551		char *ustr = (char *)malloc(len + 1);
552		memmove(ustr, ubuf, len);
553		ustr[len] = '\0';
554		ocspdDebug("ocspdHttpFetch via %s to URI %s\n",
555                   (fullUrl) ? "GET" : "POST", ustr);
556		free(ustr);
557	}
558#endif
559
560@autoreleasepool {
561	/* set up the URL request */
562	urlLoader = [[SecURLLoader alloc] initWithURL:(NSURL*)cfUrl];
563	if (urlLoader) {
564		NSMutableURLRequest *request = [urlLoader request];
565		if (postData) {
566			[request setHTTPMethod:@"POST"];
567			[request setHTTPBody:(NSData*)postData];
568			// Content-Type header
569			[request setValue:kAppOcspRequest forHTTPHeaderField:kContentType];
570			// Content-Length header
571			NSString *postLength = [NSString stringWithFormat:@"%lu",(unsigned long)[(NSData*)postData length]];
572			[request setValue:postLength forHTTPHeaderField:kContentLength];
573		}
574		else {
575			[request setHTTPMethod:@"GET"];
576			// Cache-Control header
577			NSString *maxAge = [NSString stringWithFormat:@"max-age=%d", CACHED_MAX_AGE];
578			[request setValue:maxAge forHTTPHeaderField:kCacheControl];
579		}
580		// User-Agent header
581		[request setValue:kAppUserAgent forHTTPHeaderField:kUserAgent];
582	}
583
584	/* load it! */
585	[urlLoader syncLoad];
586
587	/* return the response data */
588	len = [[urlLoader data] length];
589	if (!len || [urlLoader error]) {
590		result = CSSMERR_APPLETP_NETWORK_FAILURE;
591	}
592	else {
593		fetched.Data = (uint8 *)SecAsn1Malloc(coder, len);
594		fetched.Length = len;
595		memmove(fetched.Data, [[urlLoader data] bytes], len);
596		writeBlob(OCSP_RESP_FILE, "OCSP Response", fetched.Data, (unsigned int)fetched.Length);
597		result = CSSM_OK;
598	}
599    [urlLoader release];
600}	/* end autorelease scope */
601cleanup:
602	CFReleaseSafe(postData);
603	CFReleaseSafe(urlStr);
604	CFReleaseSafe(cfUrl);
605	if(fullUrl) {
606		free(fullUrl);
607	}
608	if(req64) {
609		free(req64);
610	}
611	return result;
612}
613
614
615#pragma mark ----- LDAP fetch -----
616
617/*
618 * LDAP attribute names, used if not present in URI.
619 */
620#define LDAP_ATTR_CERT		"cacertificate;binary"
621#define LDAP_ATTR_CRL		"certificaterevocationlist;binary"
622
623/*
624 * Default LDAP options.
625 */
626#define LDAP_REFERRAL_DEFAULT	LDAP_OPT_ON
627
628static CSSM_RETURN ldapRtnToCssm(
629	int rtn)
630{
631	switch(rtn) {
632		case LDAP_SERVER_DOWN:
633		case LDAP_TIMEOUT:
634		case LDAP_CONNECT_ERROR:
635			return CSSMERR_APPLETP_CRL_SERVER_DOWN;
636		case LDAP_PARAM_ERROR:
637		case LDAP_FILTER_ERROR:
638			return CSSMERR_APPLETP_CRL_BAD_URI;
639		default:
640			return CSSMERR_APPLETP_CRL_NOT_FOUND;
641	}
642}
643
644static CSSM_RETURN ldapFetch(
645	Allocator			&alloc,
646	const CSSM_DATA 	&url,
647	LF_Type				lfType,
648	CSSM_DATA			&fetched)	// mallocd in alloc space and RETURNED
649{
650	BerValue 		**value = NULL;
651	LDAPURLDesc 	*urlDesc = NULL;
652	int 			rtn;
653	LDAPMessage 	*msg = NULL;
654	LDAP 			*ldap = NULL;
655	LDAPMessage 	*entry = NULL;
656	bool 			mallocdString = false;
657	char 			*urlStr;
658	int 			numEntries;
659	CSSM_RETURN 	ourRtn = CSSM_OK;
660	/* attr input to ldap_search_s() */
661	char			*attrArray[2];
662	char			**attrArrayP = NULL;
663
664	/* don't assume URL string is NULL terminated */
665	if(url.Data[url.Length - 1] == '\0') {
666		urlStr = (char *)url.Data;
667	}
668	else {
669		urlStr = (char *)malloc(url.Length + 1);
670		memmove(urlStr, url.Data, url.Length);
671		urlStr[url.Length] = '\0';
672		mallocdString = true;
673	}
674
675	/* break up the URL into something usable */
676	rtn = ldap_url_parse(urlStr, &urlDesc);
677	if(rtn) {
678		ocspdErrorLog("ldap_url_parse returned %d", rtn);
679        if(mallocdString) {
680            free(urlStr);
681        }
682		return CSSMERR_APPLETP_CRL_BAD_URI;
683	}
684
685	/*
686	 * Determine what attr we're looking for.
687	 */
688	if((urlDesc->lud_attrs != NULL) &&		// attrs present in URL
689	   (urlDesc->lud_attrs[0] != NULL) &&	// at least one attr present
690	   (urlDesc->lud_attrs[1] == NULL))	{
691		/*
692		 * Exactly one attr present in the caller-specified URL;
693		 * assume that this is exactly what we want.
694		 */
695		attrArrayP = &urlDesc->lud_attrs[0];
696	}
697	else {
698		/* use caller-specified attr */
699		switch(lfType) {
700			case LT_Crl:
701				attrArray[0] = (char *)LDAP_ATTR_CRL;
702				break;
703			case LT_Cert:
704				attrArray[0] = (char *)LDAP_ATTR_CERT;
705				break;
706			default:
707				printf("***ldapFetch screwup: bogus lfType (%d)\n",
708					(int)lfType);
709				return CSSMERR_CSSM_INTERNAL_ERROR;
710		}
711		attrArray[1] = NULL;
712		attrArrayP = &attrArray[0];
713	}
714
715	/* establish connection */
716	rtn = ldap_initialize(&ldap, urlStr);
717	if(rtn) {
718		ocspdErrorLog("ldap_initialize returned %d\n", rtn);
719        if(mallocdString) {
720            free(urlStr);
721        }
722		return ldapRtnToCssm(rtn);
723	}
724	/* subsequent errors to cleanup: */
725	rtn = ldap_simple_bind_s(ldap, NULL, NULL);
726	if(rtn) {
727		ocspdErrorLog("ldap_simple_bind_s returned %d\n", rtn);
728		ourRtn = ldapRtnToCssm(rtn);
729		goto cleanup;
730	}
731
732	rtn = ldap_set_option(ldap, LDAP_OPT_REFERRALS, LDAP_REFERRAL_DEFAULT);
733	if(rtn) {
734		ocspdErrorLog("ldap_set_option(referrals) returned %d\n", rtn);
735		ourRtn = ldapRtnToCssm(rtn);
736		goto cleanup;
737	}
738
739	rtn = ldap_search_s(
740		ldap,
741		urlDesc->lud_dn,
742		LDAP_SCOPE_SUBTREE,
743		urlDesc->lud_filter,
744		urlDesc->lud_attrs,
745		0, 			// attrsonly
746		&msg);
747	if(rtn) {
748		ocspdErrorLog("ldap_search_s returned %d\n", rtn);
749		ourRtn = ldapRtnToCssm(rtn);
750		goto cleanup;
751	}
752
753	/*
754	 * We require exactly one entry (for now).
755	 */
756	numEntries = ldap_count_entries(ldap, msg);
757	if(numEntries != 1) {
758		ocspdErrorLog("tpCrlViaLdap: numEntries %d\n", numEntries);
759		ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND;
760		goto cleanup;
761	}
762
763	entry = ldap_first_entry(ldap, msg);
764    if(entry) {
765        ocspdErrorLog("ldapFetch: first entry %p\n", entry);
766    }
767	value = ldap_get_values_len(ldap, msg, attrArrayP[0]);
768	if(value == NULL) {
769		ocspdErrorLog("Error on ldap_get_values_len\n");
770		ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND;
771		goto cleanup;
772	}
773
774	fetched.Length = value[0]->bv_len;
775	fetched.Data = (uint8 *)alloc.malloc(fetched.Length);
776	memmove(fetched.Data, value[0]->bv_val, fetched.Length);
777
778	ldap_value_free_len(value);
779	ourRtn = CSSM_OK;
780cleanup:
781	if(msg) {
782		ldap_msgfree(msg);
783	}
784	if(mallocdString) {
785		free(urlStr);
786	}
787	ldap_free_urldesc(urlDesc);
788	rtn = ldap_unbind(ldap);
789	if(rtn) {
790		ocspdErrorLog("Error %d on ldap_unbind\n", rtn);
791		/* oh well */
792	}
793	return ourRtn;
794}
795
796#pragma mark ----- HTTP fetch via GET -----
797
798/* fetch via HTTP */
799static CSSM_RETURN httpFetch(
800	Allocator			&alloc,
801	const CSSM_DATA 	&url,
802	LF_Type				lfType,
803	CSSM_DATA			&fetched)	// mallocd in alloc space and RETURNED
804{
805	#pragma unused (lfType)
806	ocspdHttpDebug("httpFetch: start\n");
807
808	CSSM_RETURN result = CSSM_OK;
809	CFURLRef cfUrl = NULL;
810    SecURLLoader *urlLoader = nil;
811	CFAbsoluteTime startTime, stopTime;
812    CFIndex len;
813
814	/* trim off possible NULL terminator from incoming URL */
815	CSSM_DATA theUrl = url;
816	if(theUrl.Data[theUrl.Length - 1] == '\0') {
817		theUrl.Length--;
818	}
819
820	/* create URL with explicit path (see RFC 2616 3.2.2, 5.1.2) */
821	cfUrl = CFURLCreateWithBytes(NULL,
822                                 theUrl.Data, theUrl.Length,
823                                 kCFStringEncodingUTF8,
824                                 NULL);
825	if(cfUrl) {
826		CFStringRef pathStr = CFURLCopyLastPathComponent(cfUrl);
827		if(pathStr) {
828			if (CFStringGetLength(pathStr) == 0) {
829				CFURLRef tmpUrl = CFURLCreateCopyAppendingPathComponent(NULL,
830					cfUrl, CFSTR(""), FALSE);
831				CFRelease(cfUrl);
832				cfUrl = tmpUrl;
833			}
834			CFRelease(pathStr);
835		}
836	}
837	if(!cfUrl) {
838		ocspdErrorLog("httpFetch: CFURLCreateWithBytes returned NULL\n");
839		result = CSSMERR_APPLETP_CRL_BAD_URI;
840		goto cleanup;
841	}
842
843	#if OCSP_DEBUG
844	{
845		char *ustr = (char *)malloc(theUrl.Length + 1);
846		memmove(ustr, theUrl.Data, theUrl.Length);
847		ustr[theUrl.Length] = '\0';
848		ocspdDebug("httpFetch: GET URI %s\n", ustr);
849		free(ustr);
850	}
851	#endif
852
853@autoreleasepool {
854    /* set up the URL request */
855    urlLoader = [[SecURLLoader alloc] initWithURL:(NSURL*)cfUrl];
856    if (urlLoader) {
857        NSMutableURLRequest *request = [urlLoader request];
858        [request setHTTPMethod:@"GET"];
859        // User-Agent header
860        [request setValue:kAppUserAgent forHTTPHeaderField:kUserAgent];
861    }
862
863    /* load it! */
864    startTime = stopTime = CFAbsoluteTimeGetCurrent();
865    [urlLoader syncLoad];
866	stopTime = CFAbsoluteTimeGetCurrent();
867
868    /* return the data */
869    len = [[urlLoader data] length];
870    if (!len || [urlLoader error]) {
871        result = CSSMERR_APPLETP_NETWORK_FAILURE;
872    }
873	else {
874		ocspdDebug("httpFetch: total %lu bytes read in %f seconds\n",
875			       (unsigned long)len, stopTime-startTime);
876		fetched.Data = (uint8 *)alloc.malloc(len);
877		fetched.Length = len;
878		memmove(fetched.Data, [[urlLoader data] bytes], len);
879		result = CSSM_OK;
880		#if OCSP_DEBUG
881		writeBlob("/tmp/httpGetFile", "HTTP Fetch", fetched.Data, (unsigned int)fetched.Length);
882		#endif
883	}
884    [urlLoader release];
885}	/* end autorelease scope */
886cleanup:
887	CFReleaseSafe(cfUrl);
888
889	return result;
890}
891
892/* Fetch cert or CRL from net, we figure out the schema */
893CSSM_RETURN ocspdNetFetch(
894	Allocator			&alloc,
895	const CSSM_DATA 	&url,
896	LF_Type				lfType,
897	CSSM_DATA			&fetched)	// mallocd in alloc space and RETURNED
898{
899	#if OCSP_DEBUG
900	{
901		char *ustr = (char *)malloc(url.Length + 1);
902		memmove(ustr, url.Data, url.Length);
903		ustr[url.Length] = '\0';
904		ocspdDebug("ocspdNetFetch: fetching from URI %s\n", ustr);
905		free(ustr);
906	}
907	#endif
908
909	if(url.Length < 5) {
910		return CSSMERR_APPLETP_CRL_BAD_URI;
911	}
912	if(!strncmp((char *)url.Data, "ldap:", 5)) {
913		return ldapFetch(alloc, url, lfType, fetched);
914	}
915	if(!strncmp((char *)url.Data, "http:", 5) ||
916	   !strncmp((char *)url.Data, "https:", 6)) {
917		return httpFetch(alloc, url, lfType, fetched);
918	}
919	return CSSMERR_APPLETP_CRL_BAD_URI;
920}
921
922/* Maximum CRL length to consider putting in the cache db (128KB) */
923#define CRL_MAX_DATA_LENGTH (1024*128)
924
925/* Post-process network fetched data after finishing download. */
926CSSM_RETURN ocspdFinishNetFetch(
927	async_fetch_t *fetchParams)
928{
929	CSSM_RETURN crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
930	if(!fetchParams) {
931		return crtn;
932	}
933	StLock<Mutex> _(gParamsLock); /* lock before accessing parameters */
934	if(fetchParams->result != CSSM_OK) {
935		ocspdErrorLog("ocspdFinishNetFetch: CRL not found on net");
936		crtn = fetchParams->result;
937	}
938	else if(fetchParams->fetched.Length == 0) {
939		ocspdErrorLog("ocspdFinishNetFetch: no CRL data found");
940		crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
941	}
942	else if(fetchParams->fetched.Length > CRL_MAX_DATA_LENGTH) {
943		if (fetchParams->fetched.Data) {
944			/* Write oversize CRL data to file */
945			StLock<Mutex> w_(gFileWriteLock);
946			crlCheckCachePath();
947			int rtn = writeFile(fetchParams->outFile, fetchParams->fetched.Data,
948				(unsigned int)fetchParams->fetched.Length);
949			if(rtn) {
950				ocspdErrorLog("Error %d writing %s\n", rtn, fetchParams->outFile);
951			}
952			else {
953				ocspdCrlDebug("ocspdFinishNetFetch wrote %lu bytes to %s",
954					fetchParams->fetched.Length, fetchParams->outFile);
955
956				if(chmod(fetchParams->outFile, 0644)) {
957					ocspdErrorLog("ocspdFinishNetFetch: chmod error %d for %s",
958						errno, fetchParams->outFile);
959				}
960			}
961			(*(fetchParams->alloc)).free(fetchParams->fetched.Data);
962			fetchParams->fetched.Data = NULL;
963		}
964		crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
965	}
966	return crtn;
967}
968
969/* Fetch cert or CRL from net asynchronously. */
970static void ocspdNetFetchAsync(
971	void *context)
972{
973	async_fetch_t *params = (async_fetch_t *)context;
974	ocspdCrlDebug("ocspdNetFetchAsync with context %p", context);
975	CSSM_RETURN crtn = 0;
976	CFStringRef fileNameStr = NULL;
977	CFStringRef pemNameStr = NULL;
978	CFAbsoluteTime fetchTime, verifyTime;
979	CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
980	Boolean downloadInProgress = false;
981	Boolean wroteFile = false;
982	Boolean isCRL = false;
983
984	if(params) {
985		StLock<Mutex> _(gParamsLock); /* lock before accessing parameters */
986		params->finished = 0;
987		isCRL = (params->lfType == LT_Crl);
988		if(params->crlNames.pemFile) {
989			pemNameStr = CFStringCreateWithCString(kCFAllocatorDefault,
990				params->crlNames.pemFile, kCFStringEncodingUTF8);
991		}
992		if(params->outFile) {
993			fileNameStr = CFStringCreateWithCString(kCFAllocatorDefault,
994				params->outFile, kCFStringEncodingUTF8);
995		}
996		if(fileNameStr) {
997			/* make sure we aren't already downloading this file */
998			StLock<Mutex> _(gListLock); /* lock before examining list */
999			if(gDownloadList == NULL) {
1000				gDownloadList = CFArrayCreateMutable(kCFAllocatorDefault,
1001					0, &kCFTypeArrayCallBacks);
1002				crtn = (gDownloadList) ? crtn : CSSMERR_TP_INTERNAL_ERROR;
1003				params->result = crtn;
1004			}
1005			if(!crtn) {
1006				downloadInProgress = CFArrayContainsValue(gDownloadList,
1007					CFRangeMake(0, CFArrayGetCount(gDownloadList)), fileNameStr);
1008				if(!downloadInProgress) {
1009					/* add this filename to the global list which tells other
1010					 * callers of the crlStatus MIG function that we are
1011					 * already downloading this file.
1012					 */
1013					CFArrayAppendValue(gDownloadList, fileNameStr);
1014				} else {
1015					/* already downloading; indicate "busy, try later" status */
1016					crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
1017					params->result = crtn;
1018				}
1019			}
1020		}
1021	}
1022
1023	if(params && !crtn && !downloadInProgress) {
1024		/* fetch data into buffer */
1025		crtn = ocspdNetFetch(*(params->alloc),
1026			params->url, params->lfType, params->fetched);
1027		{
1028			StLock<Mutex> _(gParamsLock);
1029			params->result = crtn;
1030		}
1031		/* potentially write data to file */
1032		crtn = ocspdFinishNetFetch(params);
1033		{
1034			StLock<Mutex> _(gParamsLock);
1035			params->result = crtn;
1036			wroteFile = (!params->fetched.Data && params->fetched.Length > CRL_MAX_DATA_LENGTH);
1037		}
1038		fetchTime = CFAbsoluteTimeGetCurrent() - startTime;
1039		ocspdCrlDebug("%f seconds to download file", fetchTime);
1040
1041		if(isCRL && wroteFile) {
1042			/* write issuers to .pem file */
1043			StLock<Mutex> _(gListLock); /* lock before examining list */
1044			CFDataRef issuersData = NULL;
1045			if(gIssuersDict) {
1046				issuersData = (CFDataRef)CFDictionaryGetValue(gIssuersDict,
1047					pemNameStr);
1048			} else {
1049				ocspdCrlDebug("No issuers available for %s",
1050					params->crlNames.pemFile);
1051				gIssuersDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1052					&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1053			}
1054			if(!issuersData) {
1055				/* add the Apple issuers if we have nothing else */
1056				issuersData = CFDataCreate(kCFAllocatorDefault,
1057					(const UInt8 *)Apple_CRL_Issuers, (CFIndex)Apple_CRL_Issuers_Length);
1058				if(issuersData) {
1059					CFDictionarySetValue(gIssuersDict, pemNameStr, issuersData);
1060					CFRelease(issuersData);
1061				}
1062			}
1063			if(issuersData) {
1064				StLock<Mutex> _(gFileWriteLock); /* obtain lock before writing */
1065				crlCheckCachePath();
1066				int rtn = writeFile(params->crlNames.pemFile,
1067					(const unsigned char *)CFDataGetBytePtr(issuersData),
1068					(unsigned int)CFDataGetLength(issuersData));
1069				if(rtn) {
1070					ocspdErrorLog("Error %d writing %s\n",
1071						rtn, params->crlNames.pemFile);
1072				}
1073				else if(chmod(params->crlNames.pemFile, 0644)) {
1074					ocspdErrorLog("ocsp_server_crlStatus: chmod error %d for %s",
1075						errno, params->crlNames.pemFile);
1076				}
1077			}
1078		}
1079
1080		if(isCRL && wroteFile) {
1081			/* validate .crl signature (creates .update and .revoked files) */
1082			crlSignatureValid(params->crlNames.crlFile,
1083				params->crlNames.pemFile,
1084				params->crlNames.updateFile,
1085				params->crlNames.revokedFile);
1086			verifyTime = ( CFAbsoluteTimeGetCurrent() - startTime ) - fetchTime;
1087			ocspdCrlDebug("%f seconds to validate CRL", verifyTime);
1088		}
1089
1090		if(fileNameStr) {
1091			/* all finished downloading, so remove filename from global list */
1092			StLock<Mutex> _(gListLock);
1093			CFIndex idx =  CFArrayGetFirstIndexOfValue(gDownloadList,
1094				CFRangeMake(0, CFArrayGetCount(gDownloadList)), fileNameStr);
1095			if(idx >= 0) {
1096				CFArrayRemoveValueAtIndex(gDownloadList, idx);
1097			}
1098		}
1099	}
1100
1101	if(params) {
1102		StLock<Mutex> _(gParamsLock);
1103		params->finished = 1;
1104
1105		if(params->freeOnDone) {
1106			/* caller does not expect a reply; we must clean up everything. */
1107			if(params->url.Data) {
1108				free(params->url.Data);
1109			}
1110			if(params->outFile) {
1111				free(params->outFile);
1112			}
1113			if(params->crlNames.crlFile) {
1114				free(params->crlNames.crlFile);
1115			}
1116			if(params->crlNames.pemFile) {
1117				free(params->crlNames.pemFile);
1118			}
1119			if(params->crlNames.updateFile) {
1120				free(params->crlNames.updateFile);
1121			}
1122			if(params->crlNames.revokedFile) {
1123				free(params->crlNames.revokedFile);
1124			}
1125			if(params->fetched.Data) {
1126				(*(params->alloc)).free(params->fetched.Data);
1127			}
1128			free(params);
1129		}
1130	}
1131
1132	if(fileNameStr) {
1133		CFRelease(fileNameStr);
1134	}
1135	if(pemNameStr) {
1136		CFRelease(pemNameStr);
1137	}
1138}
1139
1140/*
1141 * This is a basic "we have an internet connection" check.
1142 * It tests whether IP 0.0.0.0 is routable.
1143 */
1144bool shouldAttemptNetFetch()
1145{
1146	bool result = true;
1147	SCNetworkReachabilityRef scnr;
1148	struct sockaddr_in addr;
1149	bzero(&addr, sizeof(addr));
1150	addr.sin_len = sizeof(addr);
1151	addr.sin_family = AF_INET;
1152
1153	scnr = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&addr);
1154	if (scnr) {
1155		SCNetworkReachabilityFlags flags = 0;
1156		if (SCNetworkReachabilityGetFlags(scnr, &flags)) {
1157			if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
1158				result = false;
1159		}
1160		CFRelease(scnr);
1161	}
1162	else { ocspdDebug("Failed to create reachability reference"); }
1163	ocspdDebug("Finished reachability check, result=%s", (result) ? "YES" :"NO");
1164
1165	return result;
1166}
1167
1168/* Kick off net fetch of a cert or a CRL and return immediately. */
1169CSSM_RETURN ocspdStartNetFetch(
1170	async_fetch_t		*fetchParams)
1171{
1172	if (!shouldAttemptNetFetch())
1173		return CSSMERR_APPLETP_NETWORK_FAILURE;
1174
1175	dispatch_queue_t queue = dispatch_get_global_queue(
1176		DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1177
1178	ocspdCrlDebug("ocspdStartNetFetch with context %p", (void*)fetchParams);
1179
1180	dispatch_async_f(queue, fetchParams, ocspdNetFetchAsync);
1181
1182	return 0;
1183}
1184
1185