/* * odkit.c * * korver@apple.com * * Copyright (c) 2009, Apple Inc. All rights reserved. */ #include "odkit.h" #include "dserr.h" #include "cyrus-sasl-digestmd5-parse.h" #include #include #include #include #include #include #include #include #include #include #include #include typedef enum { kODKActiveDirectoryUnknown = 0, kODKActiveDirectoryAbsent = 1, kODKActiveDirectoryPresent = 2 } ODKHasAD; typedef struct ODKSessionPriv { ODKHasAD useActiveDirectory; ODNodeRef adNode; CFStringRef authMethod; CFStringRef userName; ODRecordRef userRecord; CFDataRef serverChallenge; CFDataRef serverResponse; char buffer[1024]; char error[512]; } ODKSessionPriv; typedef struct ODKADInfo { ODNodeRef searchNode; CFArrayRef subNodeArray; ODKHasAD hasActiveDirectory; ODNodeRef adNode; } ODKADInfo; /* search nodes which are cached for performance */ static ODNodeRef gODSearchNode = NULL; static ODKADInfo gADInfo = { NULL, NULL, kODKActiveDirectoryUnknown, NULL }; #define ODK_SESSION_PRIV(session) \ ODKSessionOpaque(session) #define ODK_LOG(level, string) \ ODKLog(session, (level), __PRETTY_FUNCTION__, (string)) #define ODK_LOG_CFERROR(level, string, err) \ ODKLogCFError(session, (level), __PRETTY_FUNCTION__, (string), (err)) #define ODK_PARAM_ASSERT(expression) \ do { if (!(expression)) { ODK_LOG(LOG_ERR, "Bad parameter"); return -1; } } while(0) #define CF_SAFE_RELEASE(cfobj) \ do { if ((cfobj) != NULL) CFRelease((cfobj)); cfobj = NULL; } while (0) #pragma mark - #pragma mark Prototypes #pragma mark - static ODKSessionPriv *ODKSessionOpaque(ODKSession *session); static ODKADInfo *ODKGetADInfo(ODKSession *session); static int ODKIsSameString(CFStringRef str1, CFStringRef str2); static int ODKSearchPolicyHasChanged(ODKSession *session); static int ODKConfigureADInfo(ODKSession *session); static int ODKResetADInfo(ODKSession *session); #if 0 static int ODKGetHostFromSystemConfiguration( char *inOutHostStr, CFIndex maxHostStrLen ); static int ODKGetHostFromAnywhere( char *inOutHostStr, size_t maxHostStrLen ); #endif static CFStringRef ODKCopyAttrFromRecord(ODRecordRef record, CFStringRef attribute); static int ODKCreateFixedADServerChallenge(ODKSession *session, CFDataRef src, CFDataRef *dst); static int ODKConfigureADServerChallenge(ODKSession *session); static int ODKConfigureUserRecord(ODKSession *session); static int ODKConfigureServerChallenge(ODKSession *session); static int ODKVerifyClientRequestFixed(ODKSession *session, CFStringRef clientRequest); static int ODKUseActiveDirectory(ODKSession *session); static int ODKHasUserRecord(ODKSession *session); static int ODKUserRecordIsActiveDirectory(ODKSession *session); static int ODKIsActiveDirectoryRecord(ODRecordRef record); static int ODKParseUsername(ODKSession *session, CFStringRef client, CFStringRef *userNameOut); static void ODKLog(ODKSession *session, int priority, const char *function, const char *message); static void ODKLogCFError(ODKSession *session, int priority, const char *function, const char *message, CFErrorRef cfError); //TODO: CLEANUP use_my_od_record_verify static int use_my_od_record_verify = 1; static int ODKRecordVerifyPasswordExtended(ODKSession *session, ODRecordRef userRecord, CFStringRef authMethod, CFArrayRef inItems, CFArrayRef *outItems, ODContextRef *outContext, CFErrorRef *outError); #pragma mark - #pragma mark Errors #pragma mark - char * ODKGetError(ODKSession *session) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); if (_session == 0) return "Bad parameter"; if (*_session->error == 0) return 0; return _session->error; } #pragma mark - #pragma mark Logging #pragma mark - void ODKLog(ODKSession *session, int priority, const char *function, const char *message) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); if (_session != 0) snprintf(_session->error, sizeof(_session->error)-1, "%s: %s", function, message); syslog(priority, "%s: %s", function, message); } void ODKLogCFError(ODKSession *session, int priority, const char *function, const char *message, CFErrorRef cfError) { if (cfError == NULL) { ODKLog(session, priority, function, message); } else { char buf1[128]; char buf2[256]; CFStringRef cfErr = CFErrorCopyFailureReason(cfError); CFStringGetCString(cfErr, buf1, sizeof(buf1), kCFStringEncodingUTF8); snprintf(buf2, sizeof(buf2)-1, "%s: %s", message, buf1); ODKLog(session, priority, function, buf2); } } #pragma mark - #pragma mark Helpful Routines #pragma mark - ODKSessionPriv* ODKSessionOpaque(ODKSession *session) { ODKSessionPriv *priv = (ODKSessionPriv*)session; return priv; } int ODKMaybeCreateString(ODKSession *session, CFStringRef *dst, const char *src) { int retval = -1; if (src) { *dst = CFStringCreateWithCString(kCFAllocatorDefault, src, kCFStringEncodingUTF8); if (*dst == 0) { ODK_LOG(LOG_ERR, "Out of memory"); goto done; } } else { *dst = NULL; } retval = 0; done: return retval; } int ODKMaybeCreateData(ODKSession *session, CFDataRef *dst, const char *src, const unsigned int srclen) { int retval = -1; if (src) { *dst = CFDataCreate(kCFAllocatorDefault, (UInt8*)src, (CFIndex)srclen); if (*dst == 0) { ODK_LOG(LOG_ERR, "Out of memory"); goto done; } } else { *dst = NULL; } retval = 0; done: return retval; } int ODKNodeIsStale(ODNodeRef node) { return (ODNodeGetName(node) == NULL); } #pragma mark - #pragma mark Active Directory Node/User Status #pragma mark - ODKADInfo* ODKGetADInfo(ODKSession *session) { if (gADInfo.hasActiveDirectory == kODKActiveDirectoryUnknown || ODKSearchPolicyHasChanged(session) || ODKNodeIsStale(gADInfo.adNode)) { ODKResetADInfo(session); ODKConfigureADInfo(session); } return &gADInfo; } int ODKIsSameString(CFStringRef str1, CFStringRef str2) { if (CFStringGetLength(str1) != CFStringGetLength(str2)) return 0; return CFStringHasPrefix(str1, str2); } int ODKSearchPolicyHasChanged(ODKSession *session) { static time_t then = 0; time_t now; int hasChanged = 1; CFArrayRef cfSubNodeArray = NULL; CFErrorRef cfError = NULL; CFIndex i; /* This function polls, which is ugly, but it is the best solution for the moment. * These calls are really cheap (especially since we perform them rarely), and * jabberd doesn't have an event loop that can be easily hooked leveraged to * be notified of config changes */ if (gADInfo.subNodeArray == NULL || gADInfo.searchNode == NULL) goto done; /* don't bother talking to DirectoryService more than once every 30 seconds */ now = time(0); if (difftime(now, then) < 30.0) goto has_not_changed; /* returning "has not changed" */ then = now; /* keep track of the last time this check was attempted */ /* get search node's list of nodes */ cfSubNodeArray = ODNodeCopySubnodeNames(gADInfo.searchNode, &cfError); if (cfSubNodeArray == 0) { ODK_LOG_CFERROR(LOG_ERR, "Unable to get the list of subnodes on the search node", cfError); goto done; } if (CFArrayGetCount(gADInfo.subNodeArray) != CFArrayGetCount(cfSubNodeArray)) goto done; for (i = 0; i < CFArrayGetCount(cfSubNodeArray); ++i) { CFStringRef nodeName1 = CFArrayGetValueAtIndex(cfSubNodeArray, i); CFStringRef nodeName2 = CFArrayGetValueAtIndex(gADInfo.subNodeArray, i); if (! ODKIsSameString(nodeName1, nodeName2)) goto done; } has_not_changed: hasChanged = 0; done: CF_SAFE_RELEASE(cfSubNodeArray); CF_SAFE_RELEASE(cfError); return hasChanged; } int ODKResetADInfo(ODKSession *session) { int retval = -1; CF_SAFE_RELEASE(gADInfo.adNode); gADInfo.adNode = NULL; CF_SAFE_RELEASE(gADInfo.searchNode); gADInfo.searchNode = NULL; CF_SAFE_RELEASE(gADInfo.subNodeArray); gADInfo.subNodeArray = NULL; gADInfo.hasActiveDirectory = kODKActiveDirectoryUnknown; /* ignore the adNode in the session, because this session has failed and will * be cleaned up soon anyways */ retval = 0; //done: return retval; } int ODKPossiblyResetADInfo(ODKSession *session, CFErrorRef error) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); if (error != NULL && _session->adNode != NULL) { if (! IS_EXPECTED_DS_ERROR(CFErrorGetCode(error))) { /* this is an unexpected error, let's flush the search node */ ODK_LOG_CFERROR(LOG_DEBUG, "Flushing AD node because of unexpected error", error); ODKResetADInfo(session); } } return 0; } int ODKConfigureADInfo(ODKSession *session) { int retval = -1; int adNodeOnSearchPolicy = 0; CFErrorRef cfError = NULL; CFIndex i; assert(gADInfo.hasActiveDirectory == kODKActiveDirectoryUnknown); /* get search node */ gADInfo.searchNode = ODNodeCreateWithNodeType(kCFAllocatorDefault, kODSessionDefault, kODNodeTypeAuthentication, &cfError); if (gADInfo.searchNode == 0) { ODK_LOG_CFERROR(LOG_ERR, "Unable to get a reference to the search node", cfError); goto done; } /* get search node's list of nodes */ gADInfo.subNodeArray = ODNodeCopySubnodeNames(gADInfo.searchNode, &cfError); if (gADInfo.subNodeArray == 0) { ODK_LOG_CFERROR(LOG_ERR, "Unable to get the list of subnodes on the search node", cfError); goto done; } /* check for AD node on the search policy */ for (i = 0; i < CFArrayGetCount(gADInfo.subNodeArray); ++i) { CFStringRef nodeName = CFArrayGetValueAtIndex(gADInfo.subNodeArray, i); if (CFStringHasPrefix(nodeName, CFSTR("/Active Directory/"))) { adNodeOnSearchPolicy = 1; break; } } /* get AD node (if present) */ if (adNodeOnSearchPolicy) { gADInfo.adNode = ODNodeCreateWithName(kCFAllocatorDefault, kODSessionDefault, CFSTR("/Active Directory/All Domains"), &cfError); if (gADInfo.adNode == 0) { ODK_LOG_CFERROR(LOG_ERR, "Unable to get a reference to the AD node", cfError); goto done; } gADInfo.hasActiveDirectory = kODKActiveDirectoryPresent; } if (gADInfo.hasActiveDirectory != kODKActiveDirectoryPresent) gADInfo.hasActiveDirectory = kODKActiveDirectoryAbsent; retval = 0; done: if (retval != 0) ODKResetADInfo(session); CF_SAFE_RELEASE(cfError); return retval; } #pragma mark - #pragma mark Hostname Help #pragma mark - #if 0 //----------------------------------------------------------------------------- // GetHostFromSystemConfiguration //----------------------------------------------------------------------------- int ODKGetHostFromSystemConfiguration( char *inOutHostStr, CFIndex maxHostStrLen ) { int result = -1; SCPreferencesRef scpRef = NULL; do { scpRef = SCPreferencesCreate( NULL, CFSTR("DirectoryService"), 0 ); if ( scpRef == NULL ) break; CFDictionaryRef sysDict = (CFDictionaryRef) SCPreferencesGetValue( scpRef, CFSTR("System") ); if ( sysDict == NULL ) break; CFDictionaryRef sys2Dict = (CFDictionaryRef) CFDictionaryGetValue( sysDict, CFSTR("System") ); if ( sys2Dict == NULL ) break; CFStringRef hostString = (CFStringRef) CFDictionaryGetValue( sys2Dict, CFSTR("HostName") ); if ( hostString == NULL ) break; if ( CFStringGetCString(hostString, inOutHostStr, maxHostStrLen, kCFStringEncodingUTF8) ) result = 0; } while (0); if ( scpRef != NULL ) CFRelease( scpRef ); return result; } int ODKGetHostFromAnywhere( char *inOutHostStr, size_t maxHostStrLen ) { int idx = 0; int result = ODKGetHostFromSystemConfiguration( inOutHostStr, (CFIndex)maxHostStrLen ); if ( result != 0 ) { // try DNS in_addr_t *ipList = NULL; struct hostent *hostEnt = NULL; struct sockaddr_in addr = { sizeof(struct sockaddr_in), AF_INET, 0 }; int error_num = 0; if ( pwsf_LocalIPList(&ipList) == kCPSUtilOK ) { inOutHostStr[0] = 0; for ( idx = 0; ipList[idx] != 0 && inOutHostStr[0] == 0; idx++ ) { addr.sin_addr.s_addr = htonl( ipList[idx] ); hostEnt = getipnodebyaddr( &addr, sizeof(struct sockaddr_in), AF_INET, &error_num ); if ( hostEnt != NULL ) { if ( hostEnt->h_name != NULL ) { strlcpy( inOutHostStr, hostEnt->h_name, maxHostStrLen ); result = 0; } freehostent( hostEnt ); } } } } // last resort if ( result != 0 ) result = gethostname( inOutHostStr, maxHostStrLen ); return result; } #endif #pragma mark - #pragma mark DIGEST-MD5 Server Challenge #pragma mark - CFStringRef ODKCopyAttrFromRecord(ODRecordRef record, CFStringRef attribute) { CFErrorRef error = NULL; CFArrayRef values = ODRecordCopyValues(record, attribute, &error); CFStringRef result = NULL; if (values) { if (CFArrayGetCount(values) == 1) { result = CFArrayGetValueAtIndex(values, 0); CFRetain(result); } CFRelease(values); } CF_SAFE_RELEASE(error); return result; } int ODKCreateSession(/*CFStringRef applicationName, CFStringRef serviceName, CFStringRef hostName,*/ ODKSession **out) { ODKSessionPriv *session = 0; #if 0 ODK_PARAM_ASSERT(applicationName != NULL); ODK_PARAM_ASSERT(serviceName != NULL); #endif if (out == 0) return -1; *out = 0; session = (void*)calloc(1, sizeof(*session)); if (session == 0) { /* Out of memory */ return -1; } else { #if 0 session->applicationName = CFRetain(applicationName); session->serviceName = CFRetain(serviceName); if (hostName != NULL) session->hostName = CFRetain(hostName); #endif session->useActiveDirectory = kODKActiveDirectoryUnknown; session->adNode = ODKGetADInfo((ODKSession*)session)->adNode; if (session->adNode) CFRetain(session->adNode); *out = (ODKSession*)session; } return 0; } int ODKFlushSession(ODKSession *session) { int retval = -1; ODKSessionPriv *_session = ODK_SESSION_PRIV(session); ODK_PARAM_ASSERT(session != 0); CF_SAFE_RELEASE(_session->userRecord); assert(_session->userRecord == 0); CF_SAFE_RELEASE(_session->adNode); assert(_session->adNode == 0); retval = 0; done: return retval; } int ODKDeleteSession(ODKSession **out) { assert(out != 0); if (out != 0) { ODKSessionPriv *session = *(ODKSessionPriv**)out; if (session != 0) { #if 0 CF_SAFE_RELEASE(session->applicationName); CF_SAFE_RELEASE(session->serviceName); CF_SAFE_RELEASE(session->hostName); #endif CF_SAFE_RELEASE(session->authMethod); CF_SAFE_RELEASE(session->userName); CF_SAFE_RELEASE(session->userRecord); CF_SAFE_RELEASE(session->serverChallenge); CF_SAFE_RELEASE(session->serverResponse); CF_SAFE_RELEASE(session->adNode); memset(session, 0, sizeof(*session)); free(session); } *out = 0; } return 0; } int ODKResetODSearchNode(ODKSession *session) { CF_SAFE_RELEASE(gODSearchNode); gODSearchNode = NULL; return 0; } int ODKPossiblyResetODSearchNode(ODKSession *session, CFErrorRef error) { if (error != NULL && gODSearchNode != NULL) { if (! IS_EXPECTED_DS_ERROR(CFErrorGetCode(error))) { /* this is an unexpected error, let's flush the search node */ ODK_LOG_CFERROR(LOG_DEBUG, "Flushing OD search node because of unexpected error", error); ODKResetODSearchNode(session); } } return 0; } int ODKConfigureUserRecord(ODKSession *session) { static CFTypeRef vals[1]; static CFArrayRef reqAttrs = NULL; ODKSessionPriv *_session = ODK_SESSION_PRIV(session); int retval = -1; CFErrorRef cfError = NULL; ODK_PARAM_ASSERT(session != 0); ODK_PARAM_ASSERT(_session->userName != NULL); if (_session->userRecord != NULL) { ODK_LOG(LOG_DEBUG, "User record already configured"); goto done; } if (reqAttrs == NULL) { vals[0] = CFSTR(kDSAttributesStandardAll); reqAttrs = CFArrayCreate(NULL, vals, 1, &kCFTypeArrayCallBacks); if (reqAttrs == 0) { ODK_LOG(LOG_ERR, "Out of memory"); goto done; } } if (gODSearchNode != NULL) { /* check that the cached node is still good, that DirectoryService hasn't crashed */ if (ODKNodeIsStale(gODSearchNode)) { ODK_LOG(LOG_DEBUG, "Resetting invalid OD search node"); ODKResetODSearchNode(session); /* gODSearchNode will be re-created below */ } } if (gODSearchNode == NULL) { gODSearchNode = ODNodeCreateWithNodeType(kCFAllocatorDefault, kODSessionDefault, kODNodeTypeAuthentication, &cfError); if (gODSearchNode == NULL) { ODK_LOG(LOG_ERR, "Failed to obtain search node"); goto done; } } _session->userRecord = ODNodeCopyRecord(gODSearchNode, CFSTR(kDSStdRecordTypeUsers), _session->userName, reqAttrs, &cfError); if (cfError != NULL) { ODKPossiblyResetODSearchNode(session, cfError); ODK_LOG_CFERROR(LOG_NOTICE, "Failed to find user", cfError); goto done; } else if (_session->userRecord == NULL) { ODK_LOG(LOG_NOTICE, "Failed to find user"); goto done; } retval = 0; done: CF_SAFE_RELEASE(cfError); return retval; } int ODKCreateFixedADServerChallenge(ODKSession *session, CFDataRef src, CFDataRef *dst) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); const char qopString[] = "qop=\"auth\""; int retval = -1; char *in = 0; CFMutableDataRef out = NULL; int value_was_quoted; _session->buffer[0] = '\0'; *dst = NULL; if (sizeof(_session->buffer)-1 <= CFDataGetLength(src)) { ODK_LOG(LOG_ERR, "Illegal encoding"); goto done; } memcpy(_session->buffer, CFDataGetBytePtr(src), CFDataGetLength(src)); _session->buffer[CFDataGetLength(src)] = '\0'; out = CFDataCreateMutable(kCFAllocatorDefault, 0); if (out == NULL) { ODK_LOG(LOG_ERR, "Failed to allocate data object"); goto done; } CFDataAppendBytes(out, (UInt8*)qopString, (CFIndex)strlen(qopString)); in = _session->buffer; /* parse what we got */ while (in[0] != '\0') { char *name = 0; char *value = 0; ODKGetPair(&in, &name, &value); if (name == 0) break; if (value == 0) continue; if (strcasecmp(name, "qop") != 0 && strcasecmp(name, "cipher") != 0) { value_was_quoted = (value[-1] == '"'); CFDataAppendBytes(out, (UInt8*)",", (CFIndex)1); CFDataAppendBytes(out, (UInt8*)name, (CFIndex)strlen(name)); if (value_was_quoted) CFDataAppendBytes(out, (UInt8*)"=\"", (CFIndex)2); else CFDataAppendBytes(out, (UInt8*)"=", (CFIndex)1); CFDataAppendBytes(out, (UInt8*)value, (CFIndex)strlen(value)); if (value_was_quoted) CFDataAppendBytes(out, (UInt8*)"\"", (CFIndex)1); } } *dst = CFDataCreate(kCFAllocatorDefault, CFDataGetMutableBytePtr(out), CFDataGetLength(out)); if (*dst == NULL) { ODK_LOG(LOG_ERR, "Out of memory"); goto done; } retval = 0; done: CF_SAFE_RELEASE(out); return retval; } int ODKConfigureADServerChallenge(ODKSession *session) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); int retval = -1; int success; CFStringRef userName = NULL; CFMutableArrayRef cfArrayBuf = NULL; CFErrorRef cfOutError = NULL; CFArrayRef cfOutItems = NULL; ODK_PARAM_ASSERT(session != 0); ODK_PARAM_ASSERT(_session->authMethod != NULL); if (_session->serverChallenge != NULL) { ODK_LOG(LOG_DEBUG, "Server challenge already configured"); goto done; } if (ODKHasUserRecord(session)) { assert(_session->userName != NULL); userName = _session->userName; } else if (ODKUseActiveDirectory(session)) { assert(_session->adNode != NULL); userName = CFSTR("anonymous"); } else { ODK_LOG(LOG_DEBUG, "Missing username and anonymous capability"); goto done; } cfArrayBuf = CFArrayCreateMutable(NULL, 3, &kCFTypeArrayCallBacks); if (cfArrayBuf == 0) { ODK_LOG(LOG_ERR, "Out of memory"); goto done; } CFArrayAppendValue(cfArrayBuf, userName); CFArrayAppendValue(cfArrayBuf, _session->authMethod); CFArrayAppendValue(cfArrayBuf, CFSTR("")); if (ODKHasUserRecord(session)) { success = ODRecordVerifyPasswordExtended(_session->userRecord, CFSTR(kDSStdAuthSASLProxy), cfArrayBuf, &cfOutItems, NULL, &cfOutError); } else { success = ODNodeSetCredentialsExtended(_session->adNode, CFSTR(kDSStdRecordTypeUsers), CFSTR(kDSStdAuthSASLProxy), cfArrayBuf, &cfOutItems, NULL, &cfOutError); if (! success) ODKPossiblyResetADInfo(session, cfOutError); } if (!success) { if (cfOutError) ODK_LOG_CFERROR(LOG_INFO, "Unable to obtain server challenge", cfOutError); else ODK_LOG(LOG_INFO, "Unable to obtain server challenge"); goto done; } CFDataRef cfServerResp = CFArrayGetValueAtIndex(cfOutItems, 0); if (cfServerResp == 0) { ODK_LOG(LOG_ERR, "Missing server challenge"); goto done; } if (ODKCreateFixedADServerChallenge(session, cfServerResp, &_session->serverChallenge) != 0) { ODK_LOG(LOG_ERR, "Unable to fix server challenge"); /* well, just keep what we got and hope for the best */ _session->serverChallenge = CFRetain(cfServerResp); } retval = 0; done: CF_SAFE_RELEASE(cfOutError); CF_SAFE_RELEASE(cfOutItems); CF_SAFE_RELEASE(cfArrayBuf); return retval; } int ODKIsActiveDirectoryRecord(ODRecordRef record) { int isActiveDirectory = 0; if (record != 0) { CFStringRef metaNodeValue = ODKCopyAttrFromRecord(record, CFSTR(kDSNAttrMetaNodeLocation)); if (metaNodeValue != NULL) { isActiveDirectory = CFStringHasPrefix(metaNodeValue, CFSTR("/Active Directory/")); CFRelease(metaNodeValue); } } return isActiveDirectory; } int ODKUseActiveDirectory(ODKSession *session) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); ODK_PARAM_ASSERT(session != 0); if (_session->useActiveDirectory == kODKActiveDirectoryUnknown) { if (ODKHasUserRecord(session) && ODKUserRecordIsActiveDirectory(session)) { _session->useActiveDirectory = kODKActiveDirectoryPresent; } else if (_session->adNode != NULL) { _session->useActiveDirectory = kODKActiveDirectoryPresent; } else { _session->useActiveDirectory = kODKActiveDirectoryAbsent; } } return _session->useActiveDirectory == kODKActiveDirectoryPresent; } int ODKHasUserRecord(ODKSession *session) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); return _session->userRecord != NULL; } int ODKUserRecordIsActiveDirectory(ODKSession *session) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); assert(_session->userRecord != NULL); if (ODKIsActiveDirectoryRecord(_session->userRecord)) { _session->useActiveDirectory = kODKActiveDirectoryPresent; return 1; } return 0; } int ODKConfigureServerChallenge(ODKSession *session) { int retval = -1; ODKSessionPriv *_session = ODK_SESSION_PRIV(session); ODK_PARAM_ASSERT(session != 0); if (_session->serverChallenge != NULL) { ODK_LOG(LOG_DEBUG, "Server challenge already configured, skip"); return 0; } if (ODKHasUserRecord(session) && ODKIsActiveDirectoryRecord(_session->userRecord)) { if (ODKConfigureADServerChallenge(session)) goto done; } else if ((!ODKHasUserRecord(session)) && ODKUseActiveDirectory(session)) { if (ODKConfigureADServerChallenge(session)) goto done; } else { /* this is where to use a non-AD method to configure the challenge, * but the OD API doesn't currently support that */ goto done; } retval = 0; done: return retval; } int ODKCopyServerChallenge(ODKSession *session, CFStringRef authMethod, CFStringRef userName, CFDataRef *out) { int retval = -1; ODKSessionPriv *_session = ODK_SESSION_PRIV(session); ODK_PARAM_ASSERT(session != 0); ODK_PARAM_ASSERT(authMethod != NULL); ODK_PARAM_ASSERT(out != 0); *out = 0; CF_SAFE_RELEASE(_session->authMethod); /* possibly already set */ _session->authMethod = CFRetain(authMethod); if (userName != NULL) { CF_SAFE_RELEASE(_session->userName); /* possibly already set */ _session->userName = CFRetain(userName); if (ODKConfigureUserRecord(session) != 0) goto done; } if (ODKConfigureServerChallenge(session)) goto done; *out = CFRetain(_session->serverChallenge); retval = 0; done: return retval; } int ODKSetServerChallenge(ODKSession *session, CFStringRef authMethod, CFStringRef userName, CFDataRef serverChallenge) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); int retval = -1; ODK_PARAM_ASSERT(session != 0); ODK_PARAM_ASSERT(authMethod != NULL); ODK_PARAM_ASSERT(serverChallenge != NULL); CF_SAFE_RELEASE(_session->authMethod); /* possibly already set */ _session->authMethod = CFRetain(authMethod); if (userName) { CF_SAFE_RELEASE(_session->userName); /* possibly already set */ _session->userName = CFRetain(userName); if (ODKConfigureUserRecord(session) != 0) goto done; } CF_SAFE_RELEASE(_session->serverChallenge); /* possibly already set */ _session->serverChallenge = CFRetain(serverChallenge); retval = 0; done: return retval; } int ODKParseUsername(ODKSession *session, CFStringRef client, CFStringRef *userNameOut) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); int retval = -1; char *in = 0; *userNameOut = 0; _session->buffer[0] = '\0'; if (!CFStringGetCString(client, _session->buffer, sizeof(_session->buffer), kCFStringEncodingUTF8)) { ODK_LOG(LOG_ERR, "Parse failure"); goto done; } in = _session->buffer; /* parse what we got */ while (in[0] != '\0') { char *name = 0; char *value = 0; ODKGetPair(&in, &name, &value); if (name == 0) break; if (value == 0) continue; if (strcasecmp(name, "username") == 0) { *userNameOut = CFStringCreateWithCString(kCFAllocatorDefault, value, kCFStringEncodingUTF8); if (*userNameOut == NULL) { ODK_LOG(LOG_ERR, "Re-encoding failure"); goto done; } #if 1 break; #else } else if (strcasecmp(name, "authzid") == 0) { *authzidOut = strdup(value); } else if (strcasecmp(name, "algorithm") == 0) { *algorithmOut = strdup(value); #endif } } retval = 0; done: return retval; } int ODKRecordVerifyPasswordExtended(ODKSession *session, ODRecordRef userRecord, CFStringRef authMethod, CFArrayRef inItems, CFArrayRef *outItems, ODContextRef *outContext, CFErrorRef *outError) { int success = 0; CFStringRef cfNodeName = NULL; char nodename[512]; char username[512]; char serverchall[512]; char clientresp[512]; char *serverrespstr = 0; cfNodeName = ODKCopyAttrFromRecord(userRecord, CFSTR(kDSNAttrMetaNodeLocation)); if (cfNodeName == NULL) { // shouldn't happen, but use the normal ODRecordVerifyPassword if // the node name isn't available return ODRecordVerifyPasswordExtended(userRecord, authMethod, inItems, outItems, outContext, outError); } if (outItems) *outItems = NULL; if (outContext) *outContext = NULL; if (outError) *outError = NULL; CFStringGetCString(cfNodeName, nodename, sizeof(nodename)-1, kCFStringEncodingUTF8); CFStringGetCString(CFArrayGetValueAtIndex(inItems, 0), username, sizeof(username)-1, kCFStringEncodingUTF8); strlcpy(serverchall, (char*)CFDataGetBytePtr(CFArrayGetValueAtIndex(inItems, 1)), sizeof(serverchall)); CFStringGetCString(CFArrayGetValueAtIndex(inItems, 2), clientresp, sizeof(clientresp)-1, kCFStringEncodingUTF8); if (FasterAuthentication(nodename, username, serverchall, clientresp, &serverrespstr) != 0) goto done; CFMutableArrayRef results = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks); CFDataRef serverresp = NULL; ODKMaybeCreateData(session, &serverresp, serverrespstr, strlen(serverrespstr)); CFArrayAppendValue(results, serverresp); CFRelease(serverresp); *outItems = results; success = 1; done: CF_SAFE_RELEASE(cfNodeName); if (serverrespstr) free(serverrespstr); return success; } int ODKVerifyClientRequestFixed(ODKSession *session, CFStringRef clientRequest) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); int retval = -1; int wasAnonymous; CFMutableArrayRef cfArrayBuf = NULL; CFErrorRef cfOutError = NULL; CFArrayRef cfOutItems = NULL; int success; ODK_PARAM_ASSERT(session != 0); ODK_PARAM_ASSERT(clientRequest != NULL); ODK_PARAM_ASSERT(_session->authMethod != NULL); ODK_PARAM_ASSERT(_session->serverChallenge != NULL); if (_session->serverResponse != NULL) { ODK_LOG(LOG_DEBUG, "Server response already configured"); goto done; } wasAnonymous = ! ODKHasUserRecord(session); if (wasAnonymous) { if (ODKParseUsername(session, clientRequest, &_session->userName) != 0) goto done; if (_session->userName == NULL) { ODK_LOG(LOG_ERR, "Missing username information"); goto done; } if (ODKConfigureUserRecord(session) != 0) goto done; } cfArrayBuf = CFArrayCreateMutable(NULL, 3, &kCFTypeArrayCallBacks); if (cfArrayBuf == 0) { ODK_LOG(LOG_ERR, "Out of memory"); goto done; } if (ODKIsActiveDirectoryRecord(_session->userRecord)) { CFArrayAppendValue(cfArrayBuf, _session->userName); CFArrayAppendValue(cfArrayBuf, _session->authMethod); CFArrayAppendValue(cfArrayBuf, clientRequest); if (wasAnonymous) { success = ODNodeSetCredentialsExtended(_session->adNode, CFSTR(kDSStdRecordTypeUsers), CFSTR(kDSStdAuthSASLProxy), cfArrayBuf, &cfOutItems, NULL, &cfOutError); if (! success) ODKPossiblyResetADInfo(session, cfOutError); } else { success = ODRecordVerifyPasswordExtended(_session->userRecord, CFSTR(kDSStdAuthSASLProxy), cfArrayBuf, &cfOutItems, NULL, &cfOutError); } } else { CFArrayAppendValue(cfArrayBuf, _session->userName); CFArrayAppendValue(cfArrayBuf, _session->serverChallenge); CFArrayAppendValue(cfArrayBuf, clientRequest); // TODO: CLEANUP #if 1 if (use_my_od_record_verify) { success = ODKRecordVerifyPasswordExtended(session, _session->userRecord, CFSTR(kDSStdAuthDIGEST_MD5), cfArrayBuf, &cfOutItems, NULL, &cfOutError); } else #endif success = ODRecordVerifyPasswordExtended(_session->userRecord, CFSTR(kDSStdAuthDIGEST_MD5), cfArrayBuf, &cfOutItems, NULL, &cfOutError); } if (!success) { if (cfOutError) ODK_LOG_CFERROR(LOG_INFO, "Unable to authenticate", cfOutError); else ODK_LOG(LOG_INFO, "Unable to authenticate"); goto done; } CFDataRef cfServerResp = CFArrayGetValueAtIndex(cfOutItems, 0); if (cfServerResp == 0) { ODK_LOG(LOG_ERR, "Missing server response"); goto done; } _session->serverResponse = CFRetain(cfServerResp); retval = 0; done: CF_SAFE_RELEASE(cfOutError); CF_SAFE_RELEASE(cfOutItems); CF_SAFE_RELEASE(cfArrayBuf); return retval; } int ODKVerifyClientRequest(ODKSession *session, CFDataRef clientRequest) { int retval = -1; CFRange range = { 0 }; CFStringRef fixed = NULL; CFStringRef request = CFStringCreateWithBytes(kCFAllocatorDefault, CFDataGetBytePtr(clientRequest), CFDataGetLength(clientRequest), kCFStringEncodingUTF8, (Boolean)false); if (request == NULL) { ODK_LOG(LOG_ERR, "Unable to create string from request"); goto done; } /* our OD needs "algorithm=md5-sess" present, so make sure it's there */ range = CFStringFind(request, CFSTR("algorithm="), kCFCompareCaseInsensitive); if (range.location == kCFNotFound) { fixed = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@,algorithm=md5-sess"), request); if (fixed == NULL) { ODK_LOG(LOG_ERR, "Unable to create mutable string from request"); goto done; } retval = ODKVerifyClientRequestFixed(session, fixed); } else { retval = ODKVerifyClientRequestFixed(session, request); } done: CF_SAFE_RELEASE(fixed); CF_SAFE_RELEASE(request); return retval; } int ODKCopyServerResponse(ODKSession *session, CFStringRef *userNameOut, CFDataRef *serverResponseOut) { ODKSessionPriv *_session = ODK_SESSION_PRIV(session); ODK_PARAM_ASSERT(session != 0); ODK_PARAM_ASSERT(userNameOut != 0); ODK_PARAM_ASSERT(serverResponseOut != 0); *userNameOut = 0; *serverResponseOut = 0; if (_session->userName == 0) { ODK_LOG(LOG_ERR, "Missing user name"); return -1; } if (_session->serverResponse == 0) { ODK_LOG(LOG_ERR, "Missing server response"); return -1; } *userNameOut = CFRetain(_session->userName); *serverResponseOut = CFRetain(_session->serverResponse); return 0; }