#ifdef __APPLE__
#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <freeradius-devel/rad_assert.h>
#include <freeradius-devel/md5.h>
#include <ctype.h>
#include "mschap.h"
#include <OpenDirectory/OpenDirectory.h>
#include <nt/ntlm.h>
extern void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
const char* name, const char* value, int len);
static ODRecordRef od_find_rec(REQUEST* request, ODNodeRef node, CFStringRef recName, const char* recNameStr)
{
if (!node || !recName) return NULL;
ODRecordRef rec = NULL;
ODQueryRef query = ODQueryCreateWithNode(kCFAllocatorDefault,
node,
kODRecordTypeUsers,
kODAttributeTypeRecordName,
kODMatchEqualTo,
recName,
NULL,
0,
NULL);
if (!query) {
RDEBUG2("Unable to create OD query for %s", recNameStr);
} else {
CFArrayRef queryResults = ODQueryCopyResults(query, false, NULL);
if (queryResults == NULL || CFArrayGetCount(queryResults) == 0) {
RDEBUG2("Unable to find record %s in OD", recNameStr);
} else {
rec = (ODRecordRef)CFArrayGetValueAtIndex(queryResults, 0);
CFRetain(rec);
CFRelease(queryResults);
}
CFRelease(query);
}
return rec;
}
static CFErrorRef create_nt_error(uint32_t nt_status)
{
CFStringRef desc = NULL;
switch (nt_status) {
case 0xC000005E: desc = CFSTR("no logon servers");
break;
case 0xC0000064: desc = CFSTR("no such user");
break;
case 0xC000006A: desc = CFSTR("no wrong password");
break;
case 0xC000006D: desc = CFSTR("logon failure");
desc = CFStringCreateCopy(kCFAllocatorDefault, desc);
break;
case 0xC000006F: desc = CFSTR("invalid logon hours");
break;
case 0xC0000070: desc = CFSTR("invalid workstation");
break;
case 0xC0000071: desc = CFSTR("password expired");
break;
case 0xC0000072: desc = CFSTR("account disabled");
break;
case 0xC000000D: desc = CFSTR("invalid parameter");
break;
default:
desc = CFSTR("unknown error");
break;
}
return CFErrorCreateWithUserInfoKeysAndValues(kCFAllocatorDefault,
CFSTR("com.apple.netlogon.freeradius"),
nt_status,
(const void* const*)&kCFErrorDescriptionKey,
(const void* const*)&desc,
1);
}
static int od_nt_auth(REQUEST* request,
VALUE_PAIR* response,
VALUE_PAIR* challenge,
ODNodeRef node,
ODRecordRef rec,
CFStringRef recName,
const char* username_string,
CFErrorRef* error)
{
int status = RLM_MODULE_REJECT;
NTLM_LOGON_REQ logonReq = {
.Version = NTLM_LOGON_REQ_VERSION,
.LogonDomainName = NULL,
.UserName = username_string,
.Workstation = NULL,
.LmChallenge = { 0 },
.LmChallengeResponseLength = 0,
.LmChallengeResponse = NULL,
.NtChallengeResponseLength = 24,
.NtChallengeResponse = response->vp_octets + 26
};
char *accountName = NULL;
char *accountDomain = NULL;
uint32_t userFlags = 0;
uint8_t sessionKey[16] = { 0 };
CFDataRef serverChallenge = NULL;
if (challenge->length == 8) {
serverChallenge = CFDataCreate(kCFAllocatorDefault, (UInt8*)challenge->vp_strvalue, 8);
} else if (challenge->length == 16) {
uint8_t buffer[32];
mschap_challenge_hash(response->vp_octets + 2,
challenge->vp_octets,
username_string,
buffer);
serverChallenge = CFDataCreate(kCFAllocatorDefault, (UInt8*)buffer, 8);
}
if (serverChallenge) {
memcpy(logonReq.LmChallenge, CFDataGetBytePtr(serverChallenge), 8);
}
uint32_t auth_status = NTLMLogon(&logonReq, NULL, NULL, &accountName, &accountDomain, sessionKey, &userFlags);
if (auth_status != 0) {
*error = create_nt_error(auth_status);
} else {
char mschap_reply[42];
memset(mschap_reply, 0, sizeof(mschap_reply));
mschap_auth_response(username_string,
sessionKey,
response->vp_octets + 26,
response->vp_octets + 2,
challenge->vp_octets,
mschap_reply);
mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
"MS-CHAP2-Success", mschap_reply, 42);
status = RLM_MODULE_OK;
}
CFRelease(serverChallenge);
return status;
}
static int od_mschap_auth(REQUEST* request,
VALUE_PAIR* response,
VALUE_PAIR* challenge,
ODNodeRef node,
ODRecordRef rec,
CFStringRef recName,
CFErrorRef* error)
{
int status = RLM_MODULE_REJECT;
CFMutableArrayRef authItems = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if (authItems) {
CFArrayInsertValueAtIndex(authItems, 0, recName);
CFDataRef serverChallenge = CFDataCreate(kCFAllocatorDefault, (UInt8*)challenge->vp_strvalue, 16);
CFArrayInsertValueAtIndex(authItems, 1, serverChallenge);
CFRelease(serverChallenge);
CFDataRef peerChallenge = CFDataCreate(kCFAllocatorDefault, (UInt8*)&response->vp_strvalue[2], 16);
CFArrayInsertValueAtIndex(authItems, 2, peerChallenge);
CFRelease(peerChallenge);
CFDataRef p24 = CFDataCreate(kCFAllocatorDefault, (UInt8*)&response->vp_strvalue[26], 24);
CFArrayInsertValueAtIndex(authItems, 3, p24);
CFRelease(p24);
CFArrayInsertValueAtIndex(authItems, 4, recName);
CFArrayRef returnedItems = NULL;
if (ODRecordVerifyPasswordExtended(rec, kODAuthenticationTypeMSCHAP2, authItems, &returnedItems, NULL, error) &&
returnedItems && CFArrayGetCount(returnedItems) == 1)
{
unsigned char* respData = NULL;
size_t respDataLen = 0;
CFTypeRef cfRespData = CFArrayGetValueAtIndex(returnedItems, 0);
if (CFGetTypeID(cfRespData) == CFStringGetTypeID()) {
respDataLen = CFStringGetLength(cfRespData);
respData = malloc(respDataLen + 1);
CFStringGetCString(cfRespData, (char*)respData, respDataLen+1, kCFStringEncodingUTF8);
} else if (CFGetTypeID(cfRespData) == CFDataGetTypeID()) {
respDataLen = CFDataGetLength(cfRespData);
respData = malloc(respDataLen);
CFDataGetBytes(cfRespData, CFRangeMake(0, respDataLen), respData);
}
if (respData) {
if (respDataLen == 40) {
char mschap_reply[42];
memset(mschap_reply, 0, sizeof(mschap_reply));
mschap_reply[0] = 'S';
mschap_reply[1] = '=';
memcpy(&mschap_reply[2], respData, respDataLen);
mschap_add_reply(request,
&request->reply->vps,
*response->vp_strvalue,
"MS-CHAP2-Success",
mschap_reply,
42);
status = RLM_MODULE_OK;
}
free(respData);
}
}
if (returnedItems) CFRelease(returnedItems);
CFRelease(authItems);
}
return status;
}
int do_od_mschap(REQUEST* request,
VALUE_PAIR* response,
VALUE_PAIR* challenge,
const char* username_string)
{
RDEBUG2("Using OpenDirectory to authenticate");
ODNodeRef searchNode = ODNodeCreateWithName(kCFAllocatorDefault, kODSessionDefault, CFSTR("/Search"), NULL);
if (!searchNode) {
RDEBUG2("Unable to open OD search node");
return RLM_MODULE_FAIL;
}
int status = RLM_MODULE_FAIL;
CFErrorRef error = NULL;
CFStringRef recName = CFStringCreateWithCString(kCFAllocatorDefault, username_string, kCFStringEncodingUTF8);
ODRecordRef rec = od_find_rec(request, searchNode, recName, username_string);
if (rec) {
CFArrayRef vals = ODRecordCopyValues(rec, kODAttributeTypeMetaNodeLocation, NULL);
if (vals && CFArrayGetCount(vals) != 0) {
CFStringRef metaNodeLoc = CFArrayGetValueAtIndex(vals, 0);
if (CFStringFind(metaNodeLoc, CFSTR("/Active Directory/"), 0).location == kCFNotFound) {
RDEBUG2("Doing OD MSCHAPv2 auth");
status = od_mschap_auth(request, response, challenge, searchNode, rec, recName, &error);
} else {
RDEBUG2("Doing AD netlogon auth");
status = od_nt_auth(request, response, challenge, searchNode, rec, recName, username_string, &error);
}
}
if (vals) CFRelease(vals);
CFRelease(rec);
}
if (recName) CFRelease(recName);
CFRelease(searchNode);
if (status == RLM_MODULE_OK) {
RDEBUG2("Successful authentication for %s", username_string);
} else {
mschap_add_reply(request, &request->reply->vps,
*response->vp_octets,
"MS-CHAP-Error", "E=691 R=1", 9);
if (error == NULL) {
RDEBUG2("Authentication failed for %s", username_string);
} else {
char* desc_str = NULL;
CFDictionaryRef userInfo = CFErrorCopyUserInfo(error);
if (userInfo) {
CFStringRef desc = CFDictionaryGetValue(userInfo, kCFErrorDescriptionKey);
if (desc) {
size_t desc_str_size = CFStringGetLength(desc) + 1;
desc_str = malloc(desc_str_size);
if (desc_str) {
if (!CFStringGetCString(desc, desc_str, desc_str_size, kCFStringEncodingUTF8)) {
free(desc_str);
desc_str = NULL;
}
}
CFRelease(desc);
}
CFRelease(userInfo);
}
RDEBUG2("Authentication failed for %s: error %d (0x%x): %s",
username_string,
CFErrorGetCode(error),
CFErrorGetCode(error),
desc_str ? desc_str : "unknown error");
if (desc_str) free(desc_str);
CFRelease(error);
}
}
return status;
}
#endif