/*
* 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 <pwd.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include <DirectoryService/DirServices.h>
#include <DirectoryService/DirServicesUtils.h>
#include <DirectoryService/DirServicesConst.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <DirectoryService/DirServicesConstPriv.h>
#include <OpenDirectory/OpenDirectory.h>
#include <netdb.h>
#include <ServerFoundation/ServerFoundation.h>
#include <Foundation/Foundation.h>
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 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);
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, "
syslog(priority, "}
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, " 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"), &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
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;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ODKSessionPriv *_session = ODK_SESSION_PRIV(session);
ODK_PARAM_ASSERT(session != 0);
if (_session->serverChallenge != NULL) {
ODK_LOG(LOG_DEBUG, "Server challenge already configured, skip");
retval = 0;
goto done;
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:@"/dev/random"];
if (fileHandle == nil) {
ODK_LOG(LOG_ERR, "Error opening /dev/random, cannot generate digest challenge");
goto done;
}
NSData *randomData = [fileHandle readDataOfLength:36];
[fileHandle closeFile];
NSString *nonce = [randomData base64Encoding];
NSString *realm = [XSAuthenticator defaultRealm];
NSString *chal = [NSString stringWithFormat:@"nonce=\" CFDataRef cfServerResp = (CFDataRef)[chal dataUsingEncoding: NSUTF8StringEncoding];
if (cfServerResp == NULL)
goto done;
_session->serverChallenge = CFRetain(cfServerResp);
retval = 0;
done:
[pool drain];
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;
}
CFStringRef authMethod = CFStringCreateWithCString(kCFAllocatorDefault, "AUTHENTICATE", kCFStringEncodingUTF8);
CFArrayAppendValue(cfArrayBuf, _session->userName);
CFArrayAppendValue(cfArrayBuf, _session->serverChallenge);
CFArrayAppendValue(cfArrayBuf, clientRequest);
CFArrayAppendValue(cfArrayBuf, authMethod);
success = ODRecordVerifyPasswordExtended(_session->userRecord,
kODAuthenticationTypeDIGEST_MD5, cfArrayBuf,
&cfOutItems, NULL, &cfOutError);
CF_SAFE_RELEASE(authMethod);
if (!success) {
if (ODKIsActiveDirectoryRecord(_session->userRecord))
ODKPossiblyResetADInfo(session, cfOutError);
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(" 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;
}