#if OCSP_DEBUG
#define OCSP_USE_SYSLOG 1
#endif
#include <security_ocspd/ocspdDebug.h>
#include "appleCrlIssuers.h"
#include "ocspdNetwork.h"
#include <security_ocspd/ocspdUtils.h>
#include <CoreFoundation/CoreFoundation.h>
#include <security_cdsa_utils/cuEnc64.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <dispatch/dispatch.h>
#include <Security/cssmapple.h>
#include <security_utilities/cfutilities.h>
#include <CoreServices/CoreServices.h>
#include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
#ifndef LDAP_DEPRECATED
#define LDAP_DEPRECATED 1
#endif
#include <LDAP/ldap.h>
#define CFReleaseSafe(CF) { CFTypeRef _cf = (CF); if (_cf) CFRelease(_cf); }
#define CFReleaseNull(CF) { CFTypeRef _cf = (CF); \
if (_cf) { (CF) = NULL; CFRelease(_cf); } }
extern Mutex gParamsLock;
extern Mutex gFileWriteLock;
extern Mutex gListLock;
extern CFMutableArrayRef gDownloadList;
extern CFMutableDictionaryRef gIssuersDict;
extern bool crlSignatureValid(
const char *crlFileName,
const char *issuersFileName,
const char *updateFileName,
const char *revokedFileName);
extern int crlCheckCachePath();
#pragma mark ----- async HTTP -----
#define READ_STREAM_TIMEOUT 7.0
#define READ_BUFFER_SIZE 4096
#define POST_BUFFER_SIZE 1024
typedef struct asynchttp_s {
CFHTTPMessageRef request;
CFHTTPMessageRef response;
CFMutableDataRef data;
CFIndex increment;
size_t responseLength;
size_t previousLength;
CFReadStreamRef stream;
CFRunLoopTimerRef timer;
int finished;
} asynchttp_t;
static void asynchttp_complete(
asynchttp_t *http)
{
if (http->stream) {
CFReadStreamSetClient(http->stream, kCFStreamEventNone, NULL, NULL);
CFReadStreamUnscheduleFromRunLoop(http->stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
CFReadStreamClose(http->stream);
CFReleaseNull(http->stream);
}
if (http->timer) {
CFRunLoopTimerInvalidate(http->timer);
CFReleaseNull(http->timer);
}
http->finished = 1;
}
static void asynchttp_free(
asynchttp_t *http)
{
if(http == NULL) return;
CFReleaseNull(http->request);
CFReleaseNull(http->response);
CFReleaseNull(http->data);
CFReleaseNull(http->stream);
CFReleaseNull(http->timer);
}
static void asynchttp_timer_proc(
CFRunLoopTimerRef timer,
void *info)
{
asynchttp_t *http = (asynchttp_t *)info;
#if HTTP_DEBUG
CFStringRef req_meth = http->request ? CFHTTPMessageCopyRequestMethod(http->request) : NULL;
CFURLRef req_url = http->request ? CFHTTPMessageCopyRequestURL(http->request) : NULL;
if(req_url) CFRelease(req_url);
if(req_meth) CFRelease(req_meth);
#endif
bool hasNewData = (http->responseLength > http->previousLength);
http->previousLength = http->responseLength;
if(hasNewData) {
CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL };
timerContext.info = http;
CFRunLoopTimerInvalidate(http->timer);
CFReleaseNull(http->timer);
http->timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
CFAbsoluteTimeGetCurrent() + READ_STREAM_TIMEOUT,
0, 0, 0, asynchttp_timer_proc, &timerContext);
if (http->timer) {
CFRunLoopAddTimer(CFRunLoopGetCurrent(), http->timer, kCFRunLoopDefaultMode);
return;
}
}
asynchttp_complete(http);
}
static void handle_server_response(
CFReadStreamRef stream,
CFStreamEventType type,
void *info)
{
asynchttp_t *http = (asynchttp_t *)info;
switch (type)
{
case kCFStreamEventHasBytesAvailable:
{
size_t len = (http->increment) ? http->increment : READ_BUFFER_SIZE;
UInt8 *buf = (UInt8 *) malloc(len);
if (!buf) {
ocspdErrorLog("http stream: failed to malloc %ld bytes\n", len);
asynchttp_complete(http);
break;
}
if (!http->data) {
http->data = CFDataCreateMutable(kCFAllocatorDefault, 0);
http->responseLength = 0;
}
do {
CFIndex bytesRead = CFReadStreamRead(stream, buf, len);
if (bytesRead < 0) {
asynchttp_complete(http);
break;
} else if (bytesRead == 0) {
ocspdDebug("http stream: transfer complete, moved %ld bytes\n",
http->responseLength);
asynchttp_complete(http);
break;
} else {
CFDataAppendBytes(http->data, buf, bytesRead);
http->responseLength += bytesRead;
}
} while (CFReadStreamHasBytesAvailable(stream));
free(buf);
break;
}
case kCFStreamEventErrorOccurred:
{
CFStreamError error = CFReadStreamGetError(stream);
ocspdErrorLog("http stream: %p kCFStreamEventErrorOccurred, domain: %ld error: %ld\n",
stream, error.domain, (long int)error.error);
if (error.domain == kCFStreamErrorDomainPOSIX) {
ocspdErrorLog("CFReadStream POSIX error: %s\n", strerror(error.error));
} else if (error.domain == kCFStreamErrorDomainMacOSStatus) {
ocspdErrorLog("CFReadStream OSStatus error: %ld\n", (long int)error.error);
} else {
ocspdErrorLog("CFReadStream domain: %ld error: %ld\n", error.domain, (long int)error.error);
}
asynchttp_complete(http);
break;
}
case kCFStreamEventEndEncountered:
{
http->response = (CFHTTPMessageRef)CFReadStreamCopyProperty(
stream, kCFStreamPropertyHTTPResponseHeader);
ocspdErrorLog("http stream: %p kCFStreamEventEndEncountered hdr: %p\n",
stream, http->response);
CFHTTPMessageSetBody(http->response, http->data);
asynchttp_complete(http);
break;
}
default:
{
ocspdErrorLog("handle_server_response: unexpected event type: %lu\n", type);
break;
}
}
}
#pragma mark ----- OCSP support -----
static CFStringRef kContentType = CFSTR("Content-Type");
static CFStringRef kAppOcspRequest = CFSTR("application/ocsp-request");
static CFStringRef kUserAgent = CFSTR("User-Agent");
static CFStringRef kAppUserAgent = CFSTR("ocspd/1.0");
#if OCSP_DEBUG
#define DUMP_BLOBS 1
#endif
#define OCSP_GET_FILE "/tmp/ocspGet"
#define OCSP_RESP_FILE "/tmp/ocspResp"
#if DUMP_BLOBS
static void writeBlob(
const char *fileName,
const char *whatIsIt,
const unsigned char *data,
unsigned dataLen)
{
if(writeFile(fileName, data, dataLen)) {
printf("***Error writing %s to %s\n", whatIsIt, fileName);
}
else {
printf("...wrote %u bytes of %s to %s\n", dataLen, whatIsIt, fileName);
}
}
#else
#define writeBlob(f,w,d,l)
#endif
#if ENABLE_OCSP_VIA_GET
CSSM_RETURN ocspdHttpGet(
SecAsn1CoderRef coder,
const CSSM_DATA &url,
const CSSM_DATA &ocspReq, CSSM_DATA &fetched) {
ocspdHttpDebug("ocspdHttpGet: start\n");
CSSM_RETURN result = CSSM_OK;
CFDataRef urlData = NULL;
SInt32 errorCode;
unsigned char *endp = NULL;
bool done = false;
size_t totalLen;
unsigned char *fullUrl = NULL;
CFURLRef cfUrl = NULL;
Boolean brtn;
CFIndex len;
size_t urlLen = url.Length;
if(url.Data[urlLen - 1] == '\0') {
urlLen--;
}
#if OCSP_DEBUG
{
char *ustr = (char *)malloc(urlLen + 1);
memmove(ustr, url.Data, urlLen);
ustr[urlLen] = '\0';
ocspdDebug("ocspdHttpGet: fetching from URI %s\n", ustr);
free(ustr);
}
#endif
unsigned char *req64 = NULL;
unsigned req64Len = 0;
req64 = cuEnc64(ocspReq.Data, ocspReq.Length, &req64Len);
if(req64 == NULL) {
ocspdErrorLog("ocspdHttpGet: error base64-encoding request\n");
result = CSSMERR_TP_INTERNAL_ERROR;
goto cleanup;
}
writeBlob(OCSP_GET_FILE, "OCSP Request as URL", req64, req64Len);
endp = req64 + req64Len - 1;
for(;;) {
switch(*endp) {
case '\0':
case '\n':
case '\r':
endp--;
req64Len--;
break;
default:
done = true;
break;
}
if(done) {
break;
}
}
if( (req64Len >= INT_MAX) || (urlLen > (INT_MAX - (1 + req64Len))) ) {
result = CSSMERR_TP_INVALID_DATA;
goto cleanup;
}
totalLen = urlLen + 1 + req64Len;
fullUrl = (unsigned char *)malloc(totalLen);
memmove(fullUrl, url.Data, urlLen);
fullUrl[urlLen] = '/';
memmove(fullUrl + urlLen + 1, req64, req64Len);
cfUrl = CFURLCreateWithBytes(NULL,
fullUrl, totalLen,
kCFStringEncodingUTF8, NULL); if(!cfUrl) {
ocspdErrorLog("ocspdHttpGet: CFURLCreateWithBytes returned NULL\n");
result = CSSMERR_APPLETP_CRL_BAD_URI;
goto cleanup;
}
brtn = CFURLCreateDataAndPropertiesFromResource(NULL,
cfUrl,
&urlData,
NULL, NULL,
&errorCode);
if(!brtn) {
ocspdErrorLog("ocspdHttpGet: CFURLCreateDataAndPropertiesFromResource err: %d\n",
(int)errorCode);
result = CSSMERR_APPLETP_CRL_BAD_URI;
goto cleanup;
}
if(urlData == NULL) {
ocspdErrorLog("ocspdHttpGet: CFURLCreateDataAndPropertiesFromResource: no data\n");
result = CSSMERR_APPLETP_CRL_BAD_URI;
goto cleanup;
}
len = CFDataGetLength(urlData);
fetched.Data = (uint8 *)SecAsn1Malloc(coder, len);
fetched.Length = len;
memmove(fetched.Data, CFDataGetBytePtr(urlData), len);
writeBlob(OCSP_RESP_FILE, "OCSP Response", fetched.Data, fetched.Length);
cleanup:
CFReleaseSafe(cfUrl);
CFReleaseSafe(urlData);
if(fullUrl) {
free(fullUrl);
}
if(req64) {
free(req64);
}
return result;
}
#endif
CSSM_RETURN ocspdHttpPost(
SecAsn1CoderRef coder,
const CSSM_DATA &url,
const CSSM_DATA &ocspReq, CSSM_DATA &fetched) {
ocspdHttpDebug("ocspdHttpPost: start\n");
CSSM_RETURN result = CSSM_OK;
CFURLRef cfUrl = NULL;
CFDictionaryRef proxyDict = NULL;
CFStreamClientContext clientContext = { 0, NULL, NULL, NULL, NULL };
CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL };
CFAbsoluteTime startTime, stopTime;
asynchttp_t *httpContext = NULL;
CFDataRef postData = NULL;
uint32 urlLen = url.Length;
if(url.Data[urlLen - 1] == '\0') {
urlLen--;
}
cfUrl = CFURLCreateWithBytes(NULL,
url.Data, urlLen,
kCFStringEncodingUTF8, NULL); if(cfUrl) {
CFStringRef pathStr = CFURLCopyLastPathComponent(cfUrl);
if(pathStr) {
if (CFStringGetLength(pathStr) == 0) {
CFURLRef tmpUrl = CFURLCreateCopyAppendingPathComponent(NULL,
cfUrl, CFSTR(""), FALSE);
CFRelease(cfUrl);
cfUrl = tmpUrl;
}
CFRelease(pathStr);
}
}
if(!cfUrl) {
ocspdErrorLog("ocspdHttpPost: CFURLCreateWithBytes returned NULL\n");
result = CSSMERR_APPLETP_CRL_BAD_URI;
goto cleanup;
}
#if OCSP_DEBUG
{
char *ustr = (char *)malloc(urlLen + 1);
memmove(ustr, url.Data, urlLen);
ustr[urlLen] = '\0';
ocspdDebug("ocspdHttpPost: posting to URI %s\n", ustr);
free(ustr);
}
#endif
writeBlob(OCSP_GET_FILE, "OCSP Request as POST data", ocspReq.Data, ocspReq.Length);
postData = CFDataCreate(NULL, ocspReq.Data, ocspReq.Length);
httpContext = (asynchttp_t *) malloc(sizeof(asynchttp_t));
if(!httpContext) {
result = memFullErr;
goto cleanup;
}
memset(httpContext, 0, sizeof(asynchttp_t));
httpContext->increment = POST_BUFFER_SIZE;
httpContext->request = CFHTTPMessageCreateRequest(kCFAllocatorDefault,
CFSTR("POST"), cfUrl, kCFHTTPVersion1_1);
if(!httpContext->request) {
ocspdErrorLog("ocspdHttpPost: error creating CFHTTPMessage\n");
result = CSSMERR_TP_INTERNAL_ERROR;
goto cleanup;
}
CFHTTPMessageSetBody(httpContext->request, postData);
CFHTTPMessageSetHeaderFieldValue(httpContext->request,
kContentType, kAppOcspRequest);
CFHTTPMessageSetHeaderFieldValue(httpContext->request,
kUserAgent, kAppUserAgent);
httpContext->stream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault,
httpContext->request);
if(!httpContext->stream) {
ocspdErrorLog("ocspdHttpPost: error creating CFReadStream\n");
result = CSSMERR_TP_INTERNAL_ERROR;
goto cleanup;
}
if(!CFReadStreamSetProperty(httpContext->stream,
kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue)) {
ocspdErrorLog("ocspdHttpPost: error setting autoredirect property\n");
}
proxyDict = SCDynamicStoreCopyProxies(NULL);
if(proxyDict) {
ocspdDebug("ocspdHttpPost: setting proxy dict\n");
CFReadStreamSetProperty(httpContext->stream, kCFStreamPropertyHTTPProxy, proxyDict);
CFReleaseNull(proxyDict);
}
timerContext.info = httpContext;
httpContext->timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
CFAbsoluteTimeGetCurrent() + READ_STREAM_TIMEOUT,
0, 0, 0, asynchttp_timer_proc, &timerContext);
if (httpContext->timer == NULL) {
ocspdErrorLog("ocspdHttpPost: error setting kCFStreamPropertyReadTimeout\n");
} else {
CFRunLoopAddTimer(CFRunLoopGetCurrent(), httpContext->timer,
kCFRunLoopDefaultMode);
}
httpContext->finished = 0;
clientContext.info = httpContext;
CFReadStreamSetClient(httpContext->stream,
(kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered),
handle_server_response, &clientContext);
CFReadStreamScheduleWithRunLoop(httpContext->stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
if(CFReadStreamOpen(httpContext->stream) == false) {
ocspdErrorLog("ocspdHttpPost: error opening CFReadStream\n");
result = CSSMERR_APPLETP_NETWORK_FAILURE;
goto cleanup;
}
startTime = stopTime = CFAbsoluteTimeGetCurrent();
while (!httpContext->finished) {
(void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, TRUE);
CFAbsoluteTime curTime = CFAbsoluteTimeGetCurrent();
if (curTime != stopTime) {
stopTime = curTime;
}
}
if(!httpContext->data || httpContext->responseLength == 0) {
ocspdErrorLog("CFReadStreamRead: no data was read after %f seconds\n",
stopTime-startTime);
result = CSSMERR_APPLETP_NETWORK_FAILURE;
goto cleanup;
}
ocspdDebug("ocspdHttpPost: total %lu bytes read in %f seconds\n",
(unsigned long)httpContext->responseLength, stopTime-startTime);
SecAsn1AllocCopy(coder, CFDataGetBytePtr(httpContext->data),
CFDataGetLength(httpContext->data), &fetched);
writeBlob(OCSP_RESP_FILE, "OCSP Response", fetched.Data, fetched.Length);
result = CSSM_OK;
cleanup:
CFReleaseSafe(postData);
CFReleaseSafe(cfUrl);
asynchttp_free(httpContext);
return result;
}
#pragma mark ----- LDAP fetch -----
#define LDAP_ATTR_CERT "cacertificate;binary"
#define LDAP_ATTR_CRL "certificaterevocationlist;binary"
#define LDAP_REFERRAL_DEFAULT LDAP_OPT_ON
static CSSM_RETURN ldapRtnToCssm(
int rtn)
{
switch(rtn) {
case LDAP_SERVER_DOWN:
case LDAP_TIMEOUT:
case LDAP_CONNECT_ERROR:
return CSSMERR_APPLETP_CRL_SERVER_DOWN;
case LDAP_PARAM_ERROR:
case LDAP_FILTER_ERROR:
return CSSMERR_APPLETP_CRL_BAD_URI;
default:
return CSSMERR_APPLETP_CRL_NOT_FOUND;
}
}
static CSSM_RETURN ldapFetch(
Allocator &alloc,
const CSSM_DATA &url,
LF_Type lfType,
CSSM_DATA &fetched) {
BerValue **value = NULL;
LDAPURLDesc *urlDesc = NULL;
int rtn;
LDAPMessage *msg = NULL;
LDAP *ldap = NULL;
LDAPMessage *entry = NULL;
bool mallocdString = false;
char *urlStr;
int numEntries;
CSSM_RETURN ourRtn = CSSM_OK;
char *attrArray[2];
char **attrArrayP = NULL;
if(url.Data[url.Length - 1] == '\0') {
urlStr = (char *)url.Data;
}
else {
urlStr = (char *)malloc(url.Length + 1);
memmove(urlStr, url.Data, url.Length);
urlStr[url.Length] = '\0';
mallocdString = true;
}
rtn = ldap_url_parse(urlStr, &urlDesc);
if(rtn) {
ocspdErrorLog("ldap_url_parse returned %d", rtn);
return CSSMERR_APPLETP_CRL_BAD_URI;
}
if((urlDesc->lud_attrs != NULL) && (urlDesc->lud_attrs[0] != NULL) && (urlDesc->lud_attrs[1] == NULL)) {
attrArrayP = &urlDesc->lud_attrs[0];
}
else {
switch(lfType) {
case LT_Crl:
attrArray[0] = (char *)LDAP_ATTR_CRL;
break;
case LT_Cert:
attrArray[0] = (char *)LDAP_ATTR_CERT;
break;
default:
printf("***ldapFetch screwup: bogus lfType (%d)\n",
(int)lfType);
return CSSMERR_CSSM_INTERNAL_ERROR;
}
attrArray[1] = NULL;
attrArrayP = &attrArray[0];
}
rtn = ldap_initialize(&ldap, urlStr);
if(rtn) {
ocspdErrorLog("ldap_initialize returned %d\n", rtn);
return ldapRtnToCssm(rtn);
}
rtn = ldap_simple_bind_s(ldap, NULL, NULL);
if(rtn) {
ocspdErrorLog("ldap_simple_bind_s returned %d\n", rtn);
ourRtn = ldapRtnToCssm(rtn);
goto cleanup;
}
rtn = ldap_set_option(ldap, LDAP_OPT_REFERRALS, LDAP_REFERRAL_DEFAULT);
if(rtn) {
ocspdErrorLog("ldap_set_option(referrals) returned %d\n", rtn);
ourRtn = ldapRtnToCssm(rtn);
goto cleanup;
}
rtn = ldap_search_s(
ldap,
urlDesc->lud_dn,
LDAP_SCOPE_SUBTREE,
urlDesc->lud_filter,
urlDesc->lud_attrs,
0, &msg);
if(rtn) {
ocspdErrorLog("ldap_search_s returned %d\n", rtn);
ourRtn = ldapRtnToCssm(rtn);
goto cleanup;
}
numEntries = ldap_count_entries(ldap, msg);
if(numEntries != 1) {
ocspdErrorLog("tpCrlViaLdap: numEntries %d\n", numEntries);
ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND;
goto cleanup;
}
entry = ldap_first_entry(ldap, msg);
value = ldap_get_values_len(ldap, msg, attrArrayP[0]);
if(value == NULL) {
ocspdErrorLog("Error on ldap_get_values_len\n");
ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND;
goto cleanup;
}
fetched.Length = value[0]->bv_len;
fetched.Data = (uint8 *)alloc.malloc(fetched.Length);
memmove(fetched.Data, value[0]->bv_val, fetched.Length);
ldap_value_free_len(value);
ourRtn = CSSM_OK;
cleanup:
if(msg) {
ldap_msgfree(msg);
}
if(mallocdString) {
free(urlStr);
}
ldap_free_urldesc(urlDesc);
rtn = ldap_unbind(ldap);
if(rtn) {
ocspdErrorLog("Error %d on ldap_unbind\n", rtn);
}
return ourRtn;
}
#pragma mark ----- HTTP fetch via GET -----
static CSSM_RETURN httpFetch(
Allocator &alloc,
const CSSM_DATA &url,
LF_Type lfType,
CSSM_DATA &fetched) {
#pragma unused (lfType)
ocspdHttpDebug("httpFetch: start\n");
CSSM_RETURN result = CSSM_OK;
CFURLRef cfUrl = NULL;
CFDictionaryRef proxyDict = NULL;
CFStreamClientContext clientContext = { 0, NULL, NULL, NULL, NULL };
CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL };
CFAbsoluteTime startTime, stopTime;
asynchttp_t *httpContext = NULL;
CSSM_DATA theUrl = url;
if(theUrl.Data[theUrl.Length - 1] == '\0') {
theUrl.Length--;
}
cfUrl = CFURLCreateWithBytes(NULL,
theUrl.Data, theUrl.Length,
kCFStringEncodingUTF8, NULL); if(cfUrl) {
CFStringRef pathStr = CFURLCopyLastPathComponent(cfUrl);
if(pathStr) {
if (CFStringGetLength(pathStr) == 0) {
CFURLRef tmpUrl = CFURLCreateCopyAppendingPathComponent(NULL,
cfUrl, CFSTR(""), FALSE);
CFRelease(cfUrl);
cfUrl = tmpUrl;
}
CFRelease(pathStr);
}
}
if(!cfUrl) {
ocspdErrorLog("httpFetch: CFURLCreateWithBytes returned NULL\n");
result = CSSMERR_APPLETP_CRL_BAD_URI;
goto cleanup;
}
#if OCSP_DEBUG
{
char *ustr = (char *)malloc(theUrl.Length + 1);
memmove(ustr, theUrl.Data, theUrl.Length);
ustr[theUrl.Length] = '\0';
ocspdDebug("httpFetch: GET URI %s\n", ustr);
free(ustr);
}
#endif
httpContext = (asynchttp_t *) malloc(sizeof(asynchttp_t));
if(!httpContext) {
result = memFullErr;
goto cleanup;
}
memset(httpContext, 0, sizeof(asynchttp_t));
httpContext->increment = READ_BUFFER_SIZE;
httpContext->request = CFHTTPMessageCreateRequest(NULL,
CFSTR("GET"), cfUrl, kCFHTTPVersion1_1);
if(!httpContext->request) {
ocspdErrorLog("httpFetch: error creating CFHTTPMessage\n");
result = CSSMERR_TP_INTERNAL_ERROR;
goto cleanup;
}
CFHTTPMessageSetHeaderFieldValue(httpContext->request,
kUserAgent, kAppUserAgent);
httpContext->stream = CFReadStreamCreateForHTTPRequest(NULL,
httpContext->request);
if(!httpContext->stream) {
ocspdErrorLog("httpFetch: error creating CFReadStream\n");
result = CSSMERR_TP_INTERNAL_ERROR;
goto cleanup;
}
if(!CFReadStreamSetProperty(httpContext->stream,
kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue)) {
ocspdErrorLog("httpFetch: error setting autoredirect property\n");
}
proxyDict = SCDynamicStoreCopyProxies(NULL);
if(proxyDict) {
ocspdDebug("httpFetch: setting proxy dict\n");
CFReadStreamSetProperty(httpContext->stream, kCFStreamPropertyHTTPProxy, proxyDict);
CFReleaseNull(proxyDict);
}
timerContext.info = httpContext;
httpContext->timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
CFAbsoluteTimeGetCurrent() + READ_STREAM_TIMEOUT,
0, 0, 0, asynchttp_timer_proc, &timerContext);
if (httpContext->timer == NULL) {
ocspdErrorLog("httpFetch: error creating timer\n");
} else {
CFRunLoopAddTimer(CFRunLoopGetCurrent(), httpContext->timer,
kCFRunLoopDefaultMode);
}
httpContext->finished = 0;
clientContext.info = httpContext;
CFReadStreamSetClient(httpContext->stream,
(kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered),
handle_server_response, &clientContext);
CFReadStreamScheduleWithRunLoop(httpContext->stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
if(CFReadStreamOpen(httpContext->stream) == false) {
ocspdErrorLog("httpFetch: error opening CFReadStream\n");
result = CSSMERR_APPLETP_NETWORK_FAILURE;
goto cleanup;
}
startTime = stopTime = CFAbsoluteTimeGetCurrent();
while (!httpContext->finished) {
(void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, TRUE);
CFAbsoluteTime curTime = CFAbsoluteTimeGetCurrent();
if (curTime != stopTime) {
stopTime = curTime;
}
}
if(httpContext->responseLength == 0) {
ocspdErrorLog("httpFetch: CFReadStreamRead: no data was read after %f seconds\n",
stopTime-startTime);
result = CSSMERR_APPLETP_NETWORK_FAILURE;
goto cleanup;
}
ocspdDebug("httpFetch: total %lu bytes read in %f seconds\n",
(unsigned long)httpContext->responseLength, stopTime-startTime);
if(httpContext->data) {
CFIndex len = CFDataGetLength(httpContext->data);
fetched.Data = (uint8 *)alloc.malloc(len);
fetched.Length = len;
memmove(fetched.Data, CFDataGetBytePtr(httpContext->data), len);
} else {
result = CSSMERR_TP_INTERNAL_ERROR;
}
cleanup:
CFReleaseSafe(cfUrl);
asynchttp_free(httpContext);
return result;
}
CSSM_RETURN ocspdNetFetch(
Allocator &alloc,
const CSSM_DATA &url,
LF_Type lfType,
CSSM_DATA &fetched) {
#if OCSP_DEBUG
{
char *ustr = (char *)malloc(url.Length + 1);
memmove(ustr, url.Data, url.Length);
ustr[url.Length] = '\0';
ocspdDebug("ocspdNetFetch: fetching from URI %s\n", ustr);
free(ustr);
}
#endif
if(url.Length < 5) {
return CSSMERR_APPLETP_CRL_BAD_URI;
}
if(!strncmp((char *)url.Data, "ldap:", 5)) {
return ldapFetch(alloc, url, lfType, fetched);
}
if(!strncmp((char *)url.Data, "http:", 5) ||
!strncmp((char *)url.Data, "https:", 6)) {
return httpFetch(alloc, url, lfType, fetched);
}
return CSSMERR_APPLETP_CRL_BAD_URI;
}
#define CRL_MAX_DATA_LENGTH (1024*128)
CSSM_RETURN ocspdFinishNetFetch(
async_fetch_t *fetchParams)
{
CSSM_RETURN crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
if(!fetchParams) {
return crtn;
}
StLock<Mutex> _(gParamsLock);
if(fetchParams->result != CSSM_OK) {
ocspdErrorLog("ocspdFinishNetFetch: CRL not found on net");
crtn = fetchParams->result;
}
else if(fetchParams->fetched.Length == 0) {
ocspdErrorLog("ocspdFinishNetFetch: no CRL data found");
crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
}
else if(fetchParams->fetched.Length > CRL_MAX_DATA_LENGTH) {
if (fetchParams->fetched.Data) {
StLock<Mutex> w_(gFileWriteLock);
crlCheckCachePath();
int rtn = writeFile(fetchParams->outFile, fetchParams->fetched.Data,
fetchParams->fetched.Length);
if(rtn) {
ocspdErrorLog("Error %d writing %s\n", rtn, fetchParams->outFile);
}
else {
ocspdCrlDebug("ocspdFinishNetFetch wrote %lu bytes to %s",
fetchParams->fetched.Length, fetchParams->outFile);
if(chmod(fetchParams->outFile, 0644)) {
ocspdErrorLog("ocspdFinishNetFetch: chmod error %d for %s",
errno, fetchParams->outFile);
}
}
(*(fetchParams->alloc)).free(fetchParams->fetched.Data);
fetchParams->fetched.Data = NULL;
}
crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
}
return crtn;
}
void ocspdNetFetchAsync(
void *context)
{
async_fetch_t *params = (async_fetch_t *)context;
ocspdCrlDebug("ocspdNetFetchAsync with context %p", context);
CSSM_RETURN crtn = 0;
CFStringRef fileNameStr = NULL;
CFStringRef pemNameStr = NULL;
CFAbsoluteTime fetchTime, verifyTime;
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
Boolean downloadInProgress = false;
Boolean wroteFile = false;
Boolean isCRL = false;
if(params) {
StLock<Mutex> _(gParamsLock);
params->finished = 0;
isCRL = (params->lfType == LT_Crl);
if(params->crlNames.pemFile) {
pemNameStr = CFStringCreateWithCString(kCFAllocatorDefault,
params->crlNames.pemFile, kCFStringEncodingUTF8);
}
if(params->outFile) {
fileNameStr = CFStringCreateWithCString(kCFAllocatorDefault,
params->outFile, kCFStringEncodingUTF8);
}
if(fileNameStr) {
StLock<Mutex> _(gListLock);
if(gDownloadList == NULL) {
gDownloadList = CFArrayCreateMutable(kCFAllocatorDefault,
0, &kCFTypeArrayCallBacks);
crtn = (gDownloadList) ? crtn : CSSMERR_TP_INTERNAL_ERROR;
params->result = crtn;
}
if(!crtn) {
downloadInProgress = CFArrayContainsValue(gDownloadList,
CFRangeMake(0, CFArrayGetCount(gDownloadList)), fileNameStr);
if(!downloadInProgress) {
CFArrayAppendValue(gDownloadList, fileNameStr);
} else {
crtn = CSSMERR_APPLETP_NETWORK_FAILURE;
params->result = crtn;
}
}
}
}
if(params && !crtn && !downloadInProgress) {
crtn = ocspdNetFetch(*(params->alloc),
params->url, params->lfType, params->fetched);
{
StLock<Mutex> _(gParamsLock);
params->result = crtn;
}
crtn = ocspdFinishNetFetch(params);
{
StLock<Mutex> _(gParamsLock);
params->result = crtn;
wroteFile = (!params->fetched.Data && params->fetched.Length > CRL_MAX_DATA_LENGTH);
}
fetchTime = CFAbsoluteTimeGetCurrent() - startTime;
ocspdCrlDebug("%f seconds to download file", fetchTime);
if(isCRL && wroteFile) {
StLock<Mutex> _(gListLock);
CFDataRef issuersData = NULL;
if(gIssuersDict) {
issuersData = (CFDataRef)CFDictionaryGetValue(gIssuersDict,
pemNameStr);
} else {
ocspdCrlDebug("No issuers available for %s",
params->crlNames.pemFile);
gIssuersDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
if(!issuersData) {
issuersData = CFDataCreate(kCFAllocatorDefault,
(const UInt8 *)Apple_CRL_Issuers, (CFIndex)Apple_CRL_Issuers_Length);
if(issuersData) {
CFDictionarySetValue(gIssuersDict, pemNameStr, issuersData);
CFRelease(issuersData);
}
}
if(issuersData) {
StLock<Mutex> _(gFileWriteLock);
crlCheckCachePath();
int rtn = writeFile(params->crlNames.pemFile,
(const unsigned char *)CFDataGetBytePtr(issuersData),
CFDataGetLength(issuersData));
if(rtn) {
ocspdErrorLog("Error %d writing %s\n",
rtn, params->crlNames.pemFile);
}
else if(chmod(params->crlNames.pemFile, 0644)) {
ocspdErrorLog("ocsp_server_crlStatus: chmod error %d for %s",
errno, params->crlNames.pemFile);
}
}
}
if(isCRL && wroteFile) {
crlSignatureValid(params->crlNames.crlFile,
params->crlNames.pemFile,
params->crlNames.updateFile,
params->crlNames.revokedFile);
verifyTime = ( CFAbsoluteTimeGetCurrent() - startTime ) - fetchTime;
ocspdCrlDebug("%f seconds to validate CRL", verifyTime);
}
if(fileNameStr) {
StLock<Mutex> _(gListLock);
CFIndex idx = CFArrayGetFirstIndexOfValue(gDownloadList,
CFRangeMake(0, CFArrayGetCount(gDownloadList)), fileNameStr);
if(idx >= 0) {
CFArrayRemoveValueAtIndex(gDownloadList, idx);
}
}
}
if(params) {
StLock<Mutex> _(gParamsLock);
params->finished = 1;
if(params->freeOnDone) {
if(params->url.Data) {
free(params->url.Data);
}
if(params->outFile) {
free(params->outFile);
}
if(params->crlNames.crlFile) {
free(params->crlNames.crlFile);
}
if(params->crlNames.pemFile) {
free(params->crlNames.pemFile);
}
if(params->crlNames.updateFile) {
free(params->crlNames.updateFile);
}
if(params->crlNames.revokedFile) {
free(params->crlNames.revokedFile);
}
if(params->fetched.Data) {
(*(params->alloc)).free(params->fetched.Data);
}
free(params);
}
}
if(fileNameStr) {
CFRelease(fileNameStr);
}
if(pemNameStr) {
CFRelease(pemNameStr);
}
}
CSSM_RETURN ocspdStartNetFetch(
async_fetch_t *fetchParams)
{
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
ocspdCrlDebug("ocspdStartNetFetch with context %p", (void*)fetchParams);
dispatch_async_f(queue, fetchParams, ocspdNetFetchAsync);
return 0;
}