From 6040566cfa969da1bce085ee48b4cd3e433e87d8 Mon Sep 17 00:00:00 2001 From: Bryan Duncan Date: Tue, 21 Jun 2011 14:11:11 -0700 Subject: [PATCH] Switch to OpenDirectory & NetLogon frameworks. --- src/modules/rlm_mschap/Makefile | 5 +- src/modules/rlm_mschap/opendir.c | 691 ++++++++---------- src/modules/rlm_mschap/rlm_mschap.c | 25 +- src/modules/rlm_opendirectory/configure | 2 +- src/modules/rlm_opendirectory/configure.in | 2 +- .../rlm_opendirectory/rlm_opendirectory.c | 415 ++++------- 6 files changed, 481 insertions(+), 659 deletions(-) diff --git a/src/modules/rlm_mschap/Makefile b/src/modules/rlm_mschap/Makefile index d2d5813bee..63148a7afc 100644 --- a/src/modules/rlm_mschap/Makefile +++ b/src/modules/rlm_mschap/Makefile @@ -7,9 +7,10 @@ TARGET = rlm_mschap SRCS = rlm_mschap.c mschap.c smbdes.c opendir.c HEADERS = mschap.h smbdes.h -RLM_CFLAGS = +RLM_CFLAGS = -F /System/Library/PrivateFrameworks RLM_LDFLAGS = -RLM_LIBS = +# libtool doesn't seem to pass the -F option to the linker so provide the full path to nt framework +RLM_LIBS = -framework OpenDirectory /System/Library/PrivateFrameworks/nt.framework/nt RLM_UTILS = smbencrypt RLM_INSTALL = smbencrypt-install diff --git a/src/modules/rlm_mschap/opendir.c b/src/modules/rlm_mschap/opendir.c index f9481b113e..34628f7df8 100644 --- a/src/modules/rlm_mschap/opendir.c +++ b/src/modules/rlm_mschap/opendir.c @@ -15,389 +15,346 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - * Copyright 2007 Apple Inc. + * Copyright 2007-2010 Apple Inc. All rights reserved. */ -#include +#include RCSID("$Id$") -#include -#include -#include +#include +#include +#include #include -#include +#include -#include "smbdes.h" +#include "mschap.h" -#include +#include +#include -#define kActiveDirLoc "/Active Directory/" -static int getUserNodeRef(char* inUserName, char **outUserName, - tDirNodeReference* userNodeRef, tDirReference dsRef) +extern void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident, + const char* name, const char* value, int len); + +/* + * Finds the record in given node. + * + * Can return NULL. If non-NULL is returned, caller must CFRelease() the + * returned value. + */ +static ODRecordRef od_find_rec(REQUEST* request, ODNodeRef node, CFStringRef recName, const char* recNameStr) { - tDataBuffer *tDataBuff = NULL; - tDirNodeReference nodeRef = 0; - long status = eDSNoErr; - tContextData context = 0; - unsigned long nodeCount = 0; - uint32_t attrIndex = 0; - tDataList *nodeName = NULL; - tAttributeEntryPtr pAttrEntry = NULL; - tDataList *pRecName = NULL; - tDataList *pRecType = NULL; - tDataList *pAttrType = NULL; - unsigned long recCount = 0; - tRecordEntry *pRecEntry = NULL; - tAttributeListRef attrListRef = 0; - char *pUserLocation = NULL; - tAttributeValueListRef valueRef = 0; - tAttributeValueEntry *pValueEntry = NULL; - tDataList *pUserNode = NULL; - int result = RLM_MODULE_FAIL; - - if (inUserName == NULL) { - radlog(L_ERR, "rlm_mschap: getUserNodeRef(): no username"); - return RLM_MODULE_FAIL; - } - - tDataBuff = dsDataBufferAllocate(dsRef, 4096); - if (tDataBuff == NULL) { - radlog(L_ERR, "rlm_mschap: getUserNodeRef(): dsDataBufferAllocate() status = %ld", status); - return RLM_MODULE_FAIL; - } - - do { - /* find on search node */ - status = dsFindDirNodes(dsRef, tDataBuff, NULL, - eDSAuthenticationSearchNodeName, - &nodeCount, &context); - if (status != eDSNoErr) { - radlog(L_ERR,"rlm_mschap: getUserNodeRef(): no node found? status = %ld", status); - result = RLM_MODULE_FAIL; - break; - } - if (nodeCount < 1) { - radlog(L_ERR,"rlm_mschap: getUserNodeRef(): nodeCount < 1, status = %ld", status); - result = RLM_MODULE_FAIL; - break; - } - - status = dsGetDirNodeName(dsRef, tDataBuff, 1, &nodeName); - if (status != eDSNoErr) { - radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetDirNodeName() status = %ld", status); - result = RLM_MODULE_FAIL; - break; - } - - status = dsOpenDirNode(dsRef, nodeName, &nodeRef); - dsDataListDeallocate(dsRef, nodeName); - free(nodeName); - nodeName = NULL; - - if (status != eDSNoErr) { - radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status); - result = RLM_MODULE_FAIL; - break; - } - - pRecName = dsBuildListFromStrings(dsRef, inUserName, NULL); - pRecType = dsBuildListFromStrings(dsRef, kDSStdRecordTypeUsers, - NULL); - pAttrType = dsBuildListFromStrings(dsRef, - kDSNAttrMetaNodeLocation, - kDSNAttrRecordName, NULL); - - recCount = 1; - status = dsGetRecordList(nodeRef, tDataBuff, pRecName, - eDSExact, pRecType, pAttrType, 0, - &recCount, &context); - if (status != eDSNoErr || recCount == 0) { - radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetRecordList() status = %ld, recCount=%lu", status, recCount); - result = RLM_MODULE_FAIL; - break; - } - - status = dsGetRecordEntry(nodeRef, tDataBuff, 1, - &attrListRef, &pRecEntry); - if (status != eDSNoErr) { - radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetRecordEntry() status = %ld", status); - result = RLM_MODULE_FAIL; - break; - } - - for (attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++) { - status = dsGetAttributeEntry(nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry); - if (status == eDSNoErr && pAttrEntry != NULL) { - if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation) == 0) { - status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry); - if (status == eDSNoErr && pValueEntry != NULL) { - pUserLocation = (char *) calloc(pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char)); - memcpy(pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength); - } - } else if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName) == 0) { - status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry); - if (status == eDSNoErr && pValueEntry != NULL) { - *outUserName = (char *) malloc(pValueEntry->fAttributeValueData.fBufferLength + 1); - bzero(*outUserName,pValueEntry->fAttributeValueData.fBufferLength + 1); - memcpy(*outUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength); - } - } - - if (pValueEntry != NULL) { - dsDeallocAttributeValueEntry(dsRef, pValueEntry); - pValueEntry = NULL; - } - - dsDeallocAttributeEntry(dsRef, pAttrEntry); - pAttrEntry = NULL; - dsCloseAttributeValueList(valueRef); - valueRef = 0; - } - } - - /* OpenDirectory doesn't support mschapv2 authentication against - * Active Directory. AD users need to be authenticated using the - * normal freeradius AD path (i.e. ntlm_auth). - */ - if (strncmp(pUserLocation, kActiveDirLoc, strlen(kActiveDirLoc)) == 0) { - DEBUG2("[mschap] OpenDirectory authentication returning noop. OD doesn't support MSCHAPv2 for ActiveDirectory users."); - result = RLM_MODULE_NOOP; - break; - } - - pUserNode = dsBuildFromPath(dsRef, pUserLocation, "/"); - if (pUserNode == NULL) { - radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsBuildFromPath() returned NULL"); - result = RLM_MODULE_FAIL; - break; - } - - status = dsOpenDirNode(dsRef, pUserNode, userNodeRef); - dsDataListDeallocate(dsRef, pUserNode); - free(pUserNode); - - if (status != eDSNoErr) { - radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status); - result = RLM_MODULE_FAIL; - break; - } - - result = RLM_MODULE_OK; - } - while (0); - - if (pRecEntry != NULL) - dsDeallocRecordEntry(dsRef, pRecEntry); - - if (tDataBuff != NULL) - dsDataBufferDeAllocate(dsRef, tDataBuff); - - if (pUserLocation != NULL) - free(pUserLocation); - - if (pRecName != NULL) { - dsDataListDeallocate(dsRef, pRecName); - free(pRecName); - } - if (pRecType != NULL) { - dsDataListDeallocate(dsRef, pRecType); - free(pRecType); - } - if (pAttrType != NULL) { - dsDataListDeallocate(dsRef, pAttrType); - free(pAttrType); - } - if (nodeRef != 0) - dsCloseDirNode(nodeRef); - - return result; + 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; } -int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, - VALUE_PAIR * usernamepair) +static CFErrorRef create_nt_error(uint32_t nt_status) { - tDirStatus status = eDSNoErr; - tDirReference dsRef = 0; - tDirNodeReference userNodeRef = 0; - tDataBuffer *tDataBuff = NULL; - tDataBuffer *pStepBuff = NULL; - tDataNode *pAuthType = NULL; - uint32_t uiCurr = 0; - uint32_t uiLen = 0; - char *username_string = NULL; - char *shortUserName = NULL; - VALUE_PAIR *response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE); -#ifndef NDEBUG - int t; -#endif - - username_string = (char *) malloc(usernamepair->length + 1); - if (username_string == NULL) - return RLM_MODULE_FAIL; - - strlcpy(username_string, (char *)usernamepair->vp_strvalue, - usernamepair->length + 1); - - status = dsOpenDirService(&dsRef); - if (status != eDSNoErr) { - free(username_string); - radlog(L_ERR,"rlm_mschap: od_mschap_auth(): dsOpenDirService = %d", status); - return RLM_MODULE_FAIL; - } + CFStringRef desc = NULL; + switch (nt_status) { + case 0xC000005E: // STATUS_NO_LOGON_SERVERS + desc = CFSTR("no logon servers"); + break; + case 0xC0000064: // STATUS_NO_SUCH_USER + desc = CFSTR("no such user"); + break; + case 0xC000006A: // STATUS_WRONG_PASSWORD + desc = CFSTR("no wrong password"); + break; + case 0xC000006D: // STATUS_LOGON_FAILURE + desc = CFSTR("logon failure"); + desc = CFStringCreateCopy(kCFAllocatorDefault, desc); + break; + case 0xC000006F: // STATUS_INVALID_LOGON_HOURS + desc = CFSTR("invalid logon hours"); + break; + case 0xC0000070: // STATUS_INVALID_WORKSTATION + desc = CFSTR("invalid workstation"); + break; + case 0xC0000071: // STATUS_PASSWORD_EXPIRED + desc = CFSTR("password expired"); + break; + case 0xC0000072: // STATUS_ACCOUNT_DISABLED + desc = CFSTR("account disabled"); + break; + case 0xC000000D: // STATUS_INVALID_PARAMETER + 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); +} + +/* + * Handles NT auth for AD users. + */ +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 }; - status = getUserNodeRef(username_string, &shortUserName, &userNodeRef, dsRef); - if(status != RLM_MODULE_OK) { - if (status != RLM_MODULE_NOOP) { - RDEBUG2("od_mschap_auth: getUserNodeRef() failed"); - } - if (username_string != NULL) - free(username_string); - if (dsRef != 0) - dsCloseDirService(dsRef); - return status; - } - - /* We got a node; fill the stepBuffer - kDSStdAuthMSCHAP2 - MS-CHAPv2 authentication method. The Open Directory plug-in generates the reply data for the client. - The input buffer format consists of - a four byte length specifying the length of the user name that follows, the user name, - a four byte value specifying the length of the server challenge that follows, the server challenge, - a four byte value specifying the length of the peer challenge that follows, the peer challenge, - a four byte value specifying the length of the client's digest that follows, and the client's digest. - The output buffer consists of a four byte value specifying the length of the return digest for the client's challenge. - r = FillAuthBuff(pAuthBuff, 5, - strlen(inName), inName, // Directory Services long or short name - strlen(schal), schal, // server challenge - strlen(peerchal), peerchal, // client challenge - strlen(p24), p24, // P24 NT-Response - 4, "User"); // must match the username that was used for the hash - - inName = username_string - schal = challenge->vp_strvalue - peerchal = response->vp_strvalue + 2 (16 octets) - p24 = response->vp_strvalue + 26 (24 octets) - */ - - pStepBuff = dsDataBufferAllocate(dsRef, 4096); - tDataBuff = dsDataBufferAllocate(dsRef, 4096); - pAuthType = dsDataNodeAllocateString(dsRef, kDSStdAuthMSCHAP2); - uiCurr = 0; - - RDEBUG2("OD username_string = %s, OD shortUserName=%s (length = %lu)\n", username_string, shortUserName, strlen(shortUserName)); - - /* User name length + username */ - uiLen = (uint32_t)strlen(shortUserName); - memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen)); - uiCurr += sizeof(uiLen); - memcpy(&(tDataBuff->fBufferData[uiCurr]), shortUserName, uiLen); - uiCurr += uiLen; -#ifndef NDEBUG - RDEBUG2(" stepbuf server challenge:\t"); - for (t = 0; t < challenge->length; t++) { - fprintf(stderr, "%02x", challenge->vp_strvalue[t]); - } - fprintf(stderr, "\n"); -#endif - - /* server challenge (ie. my (freeRADIUS) challenge) */ - uiLen = 16; - memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen)); - uiCurr += sizeof(uiLen); - memcpy(&(tDataBuff->fBufferData[uiCurr]), &(challenge->vp_strvalue[0]), - uiLen); - uiCurr += uiLen; - -#ifndef NDEBUG - RDEBUG2(" stepbuf peer challenge:\t\t"); - for (t = 2; t < 18; t++) { - fprintf(stderr, "%02x", response->vp_strvalue[t]); - } - fprintf(stderr, "\n"); -#endif - - /* peer challenge (ie. the client-generated response) */ - uiLen = 16; - memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen)); - uiCurr += sizeof(uiLen); - memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->vp_strvalue[2]), - uiLen); - uiCurr += uiLen; - -#ifndef NDEBUG - RDEBUG2(" stepbuf p24:\t\t"); - for (t = 26; t < 50; t++) { - fprintf(stderr, "%02x", response->vp_strvalue[t]); - } - fprintf(stderr, "\n"); -#endif - - /* p24 (ie. second part of client-generated response) */ - uiLen = 24; /* strlen(&(response->vp_strvalue[26])); may contain NULL byte in the middle. */ - memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen)); - uiCurr += sizeof(uiLen); - memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->vp_strvalue[26]), - uiLen); - uiCurr += uiLen; - - /* Client generated use name (short name?) */ - uiLen = (uint32_t)strlen(username_string); - memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen)); - uiCurr += sizeof(uiLen); - memcpy(&(tDataBuff->fBufferData[uiCurr]), username_string, uiLen); - uiCurr += uiLen; - - tDataBuff->fBufferLength = uiCurr; - - status = dsDoDirNodeAuth(userNodeRef, pAuthType, 1, tDataBuff, - pStepBuff, NULL); - if (status == eDSNoErr) { - if (pStepBuff->fBufferLength > 4) { - uint32_t len; - - memcpy(&len, pStepBuff->fBufferData, sizeof(len)); - if (len == 40) { - char mschap_reply[42] = { '\0' }; - pStepBuff->fBufferData[len+4] = '\0'; - mschap_reply[0] = 'S'; - mschap_reply[1] = '='; - memcpy(&(mschap_reply[2]), &(pStepBuff->fBufferData[4]), len); - mschap_add_reply(request, &request->reply->vps, - *response->vp_strvalue, - "MS-CHAP2-Success", - mschap_reply, len+2); - RDEBUG2("dsDoDirNodeAuth returns stepbuff: %s (len=%ld)\n", mschap_reply, len); - } - } - } - - /* clean up */ - if (username_string != NULL) - free(username_string); - if (shortUserName != NULL) - free(shortUserName); - - if (tDataBuff != NULL) - dsDataBufferDeAllocate(dsRef, tDataBuff); - if (pStepBuff != NULL) - dsDataBufferDeAllocate(dsRef, pStepBuff); - if (pAuthType != NULL) - dsDataNodeDeAllocate(dsRef, pAuthType); - if (userNodeRef != 0) - dsCloseDirNode(userNodeRef); - if (dsRef != 0) - dsCloseDirService(dsRef); - - if (status != eDSNoErr) { - errno = EACCES; - radlog(L_ERR, "rlm_mschap: authentication failed %d", status); /* <-- returns -14091 (eDSAuthMethodNotSupported) -14090 */ - return RLM_MODULE_REJECT; - } - - return RLM_MODULE_OK; + 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, /* without the domain */ + sessionKey, /* nt-hash-hash */ + response->vp_octets + 26, /* peer response */ + response->vp_octets + 2, /* peer challenge */ + challenge->vp_octets, /* our challenge */ + mschap_reply); /* calculated MPPE key */ + mschap_add_reply(request, &request->reply->vps, *response->vp_octets, + "MS-CHAP2-Success", mschap_reply, 42); + + status = RLM_MODULE_OK; + } + + CFRelease(serverChallenge); + return status; +} + +/* + * Handles MSCHAPv2 auths for OD users. + */ +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; + + /* Create the array of auth-specific data to pass to OD and do the auth. */ + 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) + { + /* Extract the data from OD and create the reply. */ + 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; +} + + +/* + * Handles auths for both AD & OD users. + */ +int do_od_mschap(REQUEST* request, + VALUE_PAIR* response, + VALUE_PAIR* challenge, + const char* username_string) +{ + RDEBUG2("Using OpenDirectory to authenticate"); + + /* Open Search node for querying. */ + ODNodeRef searchNode = ODNodeCreateWithName(kCFAllocatorDefault, kODSessionDefault, CFSTR("/Search"), NULL); + if (!searchNode) { + RDEBUG2("Unable to open OD search node"); + return RLM_MODULE_FAIL; + } + + /* Find the record to be used for the auth attempt. */ + 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) { + /* opendirectoryd supports MSCHAPv2 for OD users but not for AD + * users. Use netlogon for AD users. + */ + 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); + } + } + CFRelease(rec); + } + + if (recName) CFRelease(recName); + CFRelease(searchNode); + + /* On success the auth functions have already created the response + * data since the work differs for AD & OD. Handle the error response + * here since it's common. + */ + 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(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 /* __APPLE__ */ diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c index ea70004711..f099572f17 100644 --- a/src/modules/rlm_mschap/rlm_mschap.c +++ b/src/modules/rlm_mschap/rlm_mschap.c @@ -37,7 +37,7 @@ RCSID("$Id$") #include "smbdes.h" #ifdef __APPLE__ -extern int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair); +extern int do_od_mschap(REQUEST* request, VALUE_PAIR* response, VALUE_PAIR* challenge, const char* username_string); #endif /* Allowable account control bits */ @@ -1203,23 +1203,6 @@ static int mschap_authenticate(void * instance, REQUEST *request) return RLM_MODULE_REJECT; } -#ifdef __APPLE__ - /* - * No "known good" NT-Password attribute. Try to do - * OpenDirectory authentication. - * - * If OD determines the user is an AD user it will return noop, which - * indicates the auth process should continue directly to AD. - * Otherwise OD will determine auth success/fail. - */ - if (!nt_password && inst->open_directory) { - RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication."); - int odStatus = od_mschap_auth(request, challenge, username); - if (odStatus != RLM_MODULE_NOOP) { - return odStatus; - } - } -#endif /* * The old "mschapv2" function has been moved to * here. @@ -1237,6 +1220,12 @@ static int mschap_authenticate(void * instance, REQUEST *request) RDEBUG2("Told to do MS-CHAPv2 for %s with NT-Password", username_string); +#ifdef __APPLE__ + if (inst->open_directory) { + return do_od_mschap(request, response, challenge, username_string); + } +#endif + if (do_mschap(inst, request, nt_password, mschapv1_challenge, response->vp_octets + 26, nthashhash, do_ntlm_auth) < 0) { diff --git a/src/modules/rlm_opendirectory/configure b/src/modules/rlm_opendirectory/configure index 25a9bd9fec..542fb7a253 100755 --- a/src/modules/rlm_opendirectory/configure +++ b/src/modules/rlm_opendirectory/configure @@ -2790,7 +2790,7 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $ ac_compiler_gnu=$ac_cv_c_compiler_gnu - od_ldflags="${od_ldflags} -framework DirectoryService" + od_ldflags="${od_ldflags} -framework OpenDirectory" diff --git a/src/modules/rlm_opendirectory/configure.in b/src/modules/rlm_opendirectory/configure.in index b315f5e83f..1b623c9316 100644 --- a/src/modules/rlm_opendirectory/configure.in +++ b/src/modules/rlm_opendirectory/configure.in @@ -7,7 +7,7 @@ if test x$with_[]modname != xno; then AC_PROG_CC AC_PROG_CPP - od_ldflags="${od_ldflags} -framework DirectoryService" + od_ldflags="${od_ldflags} -framework OpenDirectory" FR_SMART_CHECK_INCLUDE(membership.h) if test "$ac_cv_header_membership_h" != "yes"; then diff --git a/src/modules/rlm_opendirectory/rlm_opendirectory.c b/src/modules/rlm_opendirectory/rlm_opendirectory.c index a160b81030..156909686b 100644 --- a/src/modules/rlm_opendirectory/rlm_opendirectory.c +++ b/src/modules/rlm_opendirectory/rlm_opendirectory.c @@ -18,12 +18,12 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - * Copyright 2007 Apple Inc. + * Copyright 2007-2010 Apple Inc. All rights reserved. */ /* * For a typical Makefile, add linker flag like this: - * LDFLAGS = -framework DirectoryService + * LDFLAGS = -framework OpenDirectory */ #include @@ -37,8 +37,9 @@ #include #include #include +#include -#include +#include #include #if HAVE_APPLE_SPI @@ -52,235 +53,59 @@ int mbr_check_membership_refresh(const uuid_t user, uuid_t group, int *ismember) #define kRadiusSACLName "com.apple.access_radius" #define kRadiusServiceName "radius" -#define kAuthType "opendirectory" +#define kAuthType "opendirectory" /* - * od_check_passwd + * Finds the record in given node. * - * Returns: ds err + * Can return NULL. If non-NULL is returned, caller must CFRelease() the + * returned value. */ - -static long od_check_passwd(const char *uname, const char *password) +static ODRecordRef od_find_rec(REQUEST* request, ODNodeRef node, CFStringRef recName, const char* recNameStr) { - long result = eDSAuthFailed; - tDirReference dsRef = 0; - tDataBuffer *tDataBuff = NULL; - tDirNodeReference nodeRef = 0; - long status = eDSNoErr; - tContextData context = 0; - unsigned long nodeCount = 0; - uint32_t attrIndex = 0; - tDataList *nodeName = NULL; - tAttributeEntryPtr pAttrEntry = NULL; - tDataList *pRecName = NULL; - tDataList *pRecType = NULL; - tDataList *pAttrType = NULL; - unsigned long recCount = 0; - tRecordEntry *pRecEntry = NULL; - tAttributeListRef attrListRef = 0; - char *pUserLocation = NULL; - char *pUserName = NULL; - tAttributeValueListRef valueRef = 0; - tAttributeValueEntry *pValueEntry = NULL; - tDataList *pUserNode = NULL; - tDirNodeReference userNodeRef = 0; - tDataBuffer *pStepBuff = NULL; - tDataNode *pAuthType = NULL; - tAttributeValueEntry *pRecordType = NULL; - uint32_t uiCurr = 0; - uint32_t uiLen = 0; - uint32_t pwLen = 0; - - if (uname == NULL || password == NULL) - return result; - - do - { - status = dsOpenDirService( &dsRef ); - if ( status != eDSNoErr ) - return result; - - tDataBuff = dsDataBufferAllocate( dsRef, 4096 ); - if (tDataBuff == NULL) - break; - - /* find user on search node */ - status = dsFindDirNodes( dsRef, tDataBuff, NULL, eDSSearchNodeName, &nodeCount, &context ); - if (status != eDSNoErr || nodeCount < 1) - break; - - status = dsGetDirNodeName( dsRef, tDataBuff, 1, &nodeName ); - if (status != eDSNoErr) - break; - - status = dsOpenDirNode( dsRef, nodeName, &nodeRef ); - dsDataListDeallocate( dsRef, nodeName ); - free( nodeName ); - nodeName = NULL; - if (status != eDSNoErr) - break; - - pRecName = dsBuildListFromStrings( dsRef, uname, NULL ); - pRecType = dsBuildListFromStrings( dsRef, kDSStdRecordTypeUsers, kDSStdRecordTypeComputers, kDSStdRecordTypeMachines, NULL ); - pAttrType = dsBuildListFromStrings( dsRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, kDSNAttrRecordType, NULL ); - - recCount = 1; - status = dsGetRecordList( nodeRef, tDataBuff, pRecName, eDSExact, pRecType, - pAttrType, 0, &recCount, &context ); - if ( status != eDSNoErr || recCount == 0 ) - break; - - status = dsGetRecordEntry( nodeRef, tDataBuff, 1, &attrListRef, &pRecEntry ); - if ( status != eDSNoErr ) - break; - - for ( attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++ ) - { - status = dsGetAttributeEntry( nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry ); - if ( status == eDSNoErr && pAttrEntry != NULL ) - { - if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation ) == 0 ) - { - status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry ); - if ( status == eDSNoErr && pValueEntry != NULL ) - { - pUserLocation = (char *) calloc( pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char) ); - memcpy( pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength ); - } - } - else - if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName ) == 0 ) - { - status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry ); - if ( status == eDSNoErr && pValueEntry != NULL ) - { - pUserName = (char *) calloc( pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char) ); - memcpy( pUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength ); - } - } - else - if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordType ) == 0 ) - { - status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry ); - if ( status == eDSNoErr && pValueEntry != NULL ) - { - pRecordType = pValueEntry; - pValueEntry = NULL; - } - } - - if ( pValueEntry != NULL ) { - dsDeallocAttributeValueEntry( dsRef, pValueEntry ); - pValueEntry = NULL; - } - if ( pAttrEntry != NULL ) { - dsDeallocAttributeEntry( dsRef, pAttrEntry ); - pAttrEntry = NULL; - } - dsCloseAttributeValueList( valueRef ); - valueRef = 0; - } - } - - pUserNode = dsBuildFromPath( dsRef, pUserLocation, "/" ); - status = dsOpenDirNode( dsRef, pUserNode, &userNodeRef ); - dsDataListDeallocate( dsRef, pUserNode ); - free( pUserNode ); - pUserNode = NULL; - if ( status != eDSNoErr ) - break; - - pStepBuff = dsDataBufferAllocate( dsRef, 128 ); - - pAuthType = dsDataNodeAllocateString( dsRef, kDSStdAuthNodeNativeClearTextOK ); - uiCurr = 0; - - /* User name */ - uiLen = (uint32_t)strlen( pUserName ); - memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &uiLen, sizeof(uiLen) ); - uiCurr += (uint32_t)sizeof( uiLen ); - memcpy( &(tDataBuff->fBufferData[ uiCurr ]), pUserName, uiLen ); - uiCurr += uiLen; - - /* pw */ - pwLen = (uint32_t)strlen( password ); - memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &pwLen, sizeof(pwLen) ); - uiCurr += (uint32_t)sizeof( pwLen ); - memcpy( &(tDataBuff->fBufferData[ uiCurr ]), password, pwLen ); - uiCurr += pwLen; - - tDataBuff->fBufferLength = uiCurr; - - result = dsDoDirNodeAuthOnRecordType( userNodeRef, pAuthType, 1, tDataBuff, pStepBuff, NULL, &pRecordType->fAttributeValueData ); - } - while ( 0 ); - - /* clean up */ - if (pAuthType != NULL) { - dsDataNodeDeAllocate( dsRef, pAuthType ); - pAuthType = NULL; - } - if (pRecordType != NULL) { - dsDeallocAttributeValueEntry( dsRef, pRecordType ); - pRecordType = NULL; - } - if (tDataBuff != NULL) { - bzero( tDataBuff, tDataBuff->fBufferSize ); - dsDataBufferDeAllocate( dsRef, tDataBuff ); - tDataBuff = NULL; - } - if (pStepBuff != NULL) { - dsDataBufferDeAllocate( dsRef, pStepBuff ); - pStepBuff = NULL; - } - if (pUserLocation != NULL) { - free(pUserLocation); - pUserLocation = NULL; - } - if (pRecName != NULL) { - dsDataListDeallocate( dsRef, pRecName ); - free( pRecName ); - pRecName = NULL; - } - if (pRecType != NULL) { - dsDataListDeallocate( dsRef, pRecType ); - free( pRecType ); - pRecType = NULL; - } - if (pAttrType != NULL) { - dsDataListDeallocate( dsRef, pAttrType ); - free( pAttrType ); - pAttrType = NULL; - } - if (nodeRef != 0) { - dsCloseDirNode(nodeRef); - nodeRef = 0; - } - if (dsRef != 0) { - dsCloseDirService(dsRef); - dsRef = 0; - } - - return result; -} + 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; +} /* - * Check the users password against the standard UNIX - * password table. + * Check the users password against OD. */ static int od_authenticate(UNUSED void *instance, REQUEST *request) { - char *name, *passwd; - int ret; - long odResult = eDSAuthFailed; - /* * We can only authenticate user requests which HAVE * a User-Name attribute. */ if (!request->username) { - RDEBUG("ERROR: You set 'Auth-Type = OpenDirectory' for a request that does not contain a User-Name attribute!"); + RDEBUG("ERROR: Request does not contain a User-Name attribute!"); return RLM_MODULE_INVALID; } @@ -289,43 +114,64 @@ static int od_authenticate(UNUSED void *instance, REQUEST *request) */ if (!request->password || (request->password->attribute != PW_PASSWORD)) { - RDEBUG("ERROR: You set 'Auth-Type = OpenDirectory' for a request that does not contain a User-Password attribute!"); + RDEBUG("ERROR: Request does not contain a User-Password attribute!"); return RLM_MODULE_INVALID; } - name = (char *)request->username->vp_strvalue; - passwd = (char *)request->password->vp_strvalue; - - odResult = od_check_passwd(name, passwd); - switch(odResult) - { - case eDSNoErr: - ret = RLM_MODULE_OK; - break; - - case eDSAuthUnknownUser: - case eDSAuthInvalidUserName: - case eDSAuthNewPasswordRequired: - case eDSAuthPasswordExpired: - case eDSAuthAccountDisabled: - case eDSAuthAccountExpired: - case eDSAuthAccountInactive: - case eDSAuthInvalidLogonHours: - case eDSAuthInvalidComputer: - ret = RLM_MODULE_USERLOCK; - break; - - default: - ret = RLM_MODULE_REJECT; - break; + /* Open Search node for querying. */ + ODNodeRef searchNode = ODNodeCreateWithName(kCFAllocatorDefault, kODSessionDefault, CFSTR("/Search"), NULL); + if (!searchNode) { + RDEBUG2("Unable to open OD search node"); + return RLM_MODULE_FAIL; } - - if (ret != RLM_MODULE_OK) { - RDEBUG("[%s]: Invalid password", name); - return ret; + + CFStringRef username = CFStringCreateWithCString(kCFAllocatorDefault, + request->username->vp_strvalue, + kCFStringEncodingUTF8); + + CFStringRef password = CFStringCreateWithCString(kCFAllocatorDefault, + request->password->vp_strvalue, + kCFStringEncodingUTF8); + + int status = RLM_MODULE_REJECT; + CFErrorRef error = NULL; + ODRecordRef rec = od_find_rec(request, searchNode, username, request->username->vp_strvalue); + if (rec) { + if (ODRecordVerifyPassword(rec, password, &error)) { + status = RLM_MODULE_OK; + } else { + if (error == NULL) { + RDEBUG2("Authentication failed for %s", request->username->vp_strvalue); + } else { + char* desc_str = NULL; + CFStringRef desc = CFErrorCopyDescription(error); + if (desc) { + size_t desc_str_size = CFStringGetLength(desc) + 1; + desc_str = malloc(desc_str_size); + if (desc_str) { + CFStringGetCString(desc, + desc_str, + desc_str_size, + kCFStringEncodingUTF8); + } + } + + RDEBUG2("Authentication failed for %s: error %d: %s", + request->username->vp_strvalue, CFErrorGetCode(error), + desc_str ? desc_str : "unknown error"); + + CFRelease(error); + } + } + + CFRelease(rec); } - - return RLM_MODULE_OK; + + if (username) CFRelease(username); + if (password) CFRelease(password); + CFRelease(searchNode); + + return status; } @@ -334,8 +180,6 @@ static int od_authenticate(UNUSED void *instance, REQUEST *request) */ static int od_authorize(UNUSED void *instance, REQUEST *request) { - char *name = NULL; - struct passwd *userdata = NULL; struct group *groupdata = NULL; int ismember = 0; RADCLIENT *rad_client = NULL; @@ -404,27 +248,48 @@ static int od_authorize(UNUSED void *instance, REQUEST *request) } } - if (uuid_is_null(guid_sacl) && uuid_is_null(guid_nasgroup)) { - RDEBUG("no access control groups, all users allowed."); - if (pairfind(request->config_items, PW_AUTH_TYPE) == NULL) { - pairadd(&request->config_items, pairmake("Auth-Type", kAuthType, T_OP_EQ)); - RDEBUG("Setting Auth-Type = %s", kAuthType); - } - return RLM_MODULE_OK; - } - /* resolve user */ uuid_clear(uuid); - name = (char *)request->username->vp_strvalue; - rad_assert(name != NULL); - - userdata = getpwnam(name); - if (userdata != NULL) { - err = mbr_uid_to_uuid(userdata->pw_uid, uuid); - if (err != 0) - uuid_clear(uuid); + + ODNodeRef searchNode = ODNodeCreateWithName(kCFAllocatorDefault, kODSessionDefault, CFSTR("/Search"), NULL); + if (!searchNode) { + RDEBUG2("Unable to open OD search node"); + return RLM_MODULE_FAIL; } - + + CFStringRef username = CFStringCreateWithCString(kCFAllocatorDefault, + request->username->vp_strvalue, + kCFStringEncodingUTF8); + + ODRecordRef rec = od_find_rec(request, searchNode, username, request->username->vp_strvalue); + if (!rec) { + RDEBUG("User %s does not exist in OD", request->username->vp_strvalue); + } else { + RDEBUG("User %s exists in OD", request->username->vp_strvalue); + CFArrayRef vals = ODRecordCopyValues(rec, kODAttributeTypeGUID, NULL); + if (!vals || CFArrayGetCount(vals) == 0) { + RDEBUG("Could not find GUID for user %s", request->username->vp_strvalue); + } else { + CFTypeRef user_guid = CFArrayGetValueAtIndex(vals, 0); + if (CFGetTypeID(user_guid) == CFStringGetTypeID()) { + size_t len = CFStringGetLength(user_guid) + 1; + char* user_guid_str = malloc(len); + if (user_guid_str) { + CFStringGetCString(user_guid, user_guid_str, len, kCFStringEncodingUTF8); + uuid_parse(user_guid_str, uuid); + } + } + CFRelease(vals); + } + CFRelease(rec); + } + if (username) CFRelease(username); + CFRelease(searchNode); + + /* + * Check the user membership in the access groups (if they exist). + */ + if (uuid_is_null(uuid)) { radius_pairmake(request, &request->packet->vps, "Module-Failure-Message", "Could not get the user's uuid", T_OP_EQ); @@ -440,10 +305,13 @@ static int od_authorize(UNUSED void *instance, REQUEST *request) } if (ismember == 0) { + RDEBUG("User %s is not a member of the RADUIS SACL", request->username->vp_strvalue); radius_pairmake(request, &request->packet->vps, "Module-Failure-Message", "User is not authorized", T_OP_EQ); - return RLM_MODULE_USERLOCK; + return RLM_MODULE_REJECT; } + + RDEBUG("User %s is a member of the RADUIS SACL", request->username->vp_strvalue); } if (!uuid_is_null(guid_nasgroup)) { @@ -455,12 +323,19 @@ static int od_authorize(UNUSED void *instance, REQUEST *request) } if (ismember == 0) { + RDEBUG("User %s is not a member of the host access group", request->username->vp_strvalue); radius_pairmake(request, &request->packet->vps, "Module-Failure-Message", "User is not authorized", T_OP_EQ); - return RLM_MODULE_USERLOCK; + return RLM_MODULE_REJECT; } + + RDEBUG("User %s is a member of the hostaccess group", request->username->vp_strvalue); } + if (uuid_is_null(guid_sacl) && uuid_is_null(guid_nasgroup)) { + RDEBUG("no access control groups, all OD users allowed."); + } + if (pairfind(request->config_items, PW_AUTH_TYPE) == NULL) { pairadd(&request->config_items, pairmake("Auth-Type", kAuthType, T_OP_EQ)); RDEBUG("Setting Auth-Type = %s", kAuthType); -- 2.47.2