diff options
Diffstat (limited to 'src/modules/rlm_mschap/opendir.c')
-rw-r--r-- | src/modules/rlm_mschap/opendir.c | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/src/modules/rlm_mschap/opendir.c b/src/modules/rlm_mschap/opendir.c new file mode 100644 index 0000000..b3fd9ff --- /dev/null +++ b/src/modules/rlm_mschap/opendir.c @@ -0,0 +1,418 @@ +#ifdef __APPLE__ +/* + * Open Directory support from Apple Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 only, as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License version 2 + * 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. + */ + +RCSID("$Id$") +USES_APPLE_DEPRECATED_API + +#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 "smbdes.h" + +#include <DirectoryService/DirectoryService.h> + +#define kActiveDirLoc "/Active Directory/" + +/* + * In rlm_mschap.c + */ +void mschap_add_reply(REQUEST *request, unsigned char ident, + char const *name, char const *value, size_t len); + +/* + * Only used by rlm_mschap.c + */ +rlm_rcode_t od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair); + + +static rlm_rcode_t getUserNodeRef(REQUEST *request, char* inUserName, char **outUserName, + tDirNodeReference* userNodeRef, tDirReference dsRef) +{ + tDataBuffer *tDataBuff = NULL; + tDirNodeReference nodeRef = 0; + long status = eDSNoErr; + char const *what = NULL; + char *status_name = NULL; + tContextData context = 0; + uint32_t nodeCount = 0; + uint32_t attrIndex = 0; + tDataList *nodeName = NULL; + tAttributeEntryPtr pAttrEntry = NULL; + tDataList *pRecName = NULL; + tDataList *pRecType = NULL; + tDataList *pAttrType = NULL; + uint32_t recCount = 0; + tRecordEntry *pRecEntry = NULL; + tAttributeListRef attrListRef = 0; + char *pUserLocation = NULL; + tAttributeValueListRef valueRef = 0; + tDataList *pUserNode = NULL; + rlm_rcode_t result = RLM_MODULE_FAIL; + + if (!inUserName) { + ERROR("rlm_mschap: getUserNodeRef(): no username"); + return RLM_MODULE_FAIL; + } + + tDataBuff = dsDataBufferAllocate(dsRef, 4096); + if (!tDataBuff) { + RERROR("Failed allocating buffer"); + return RLM_MODULE_FAIL; + } + + do { + /* find on search node */ + status = dsFindDirNodes(dsRef, tDataBuff, NULL, + eDSAuthenticationSearchNodeName, + &nodeCount, &context); +#define OPEN_DIR_ERROR(_x) do if (status != eDSNoErr) { \ + what = _x; \ + goto error; \ + } while (0) + + OPEN_DIR_ERROR("Failed to find directory"); + + if (nodeCount < 1) { + what = "No directories found."; + goto error; + } + + status = dsGetDirNodeName(dsRef, tDataBuff, 1, &nodeName); + OPEN_DIR_ERROR("Failed getting directory name"); + + status = dsOpenDirNode(dsRef, nodeName, &nodeRef); + dsDataListDeallocate(dsRef, nodeName); + free(nodeName); + nodeName = NULL; + + OPEN_DIR_ERROR("Failed opening directory"); + + 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); + OPEN_DIR_ERROR("Failed getting record list"); + + if (recCount == 0) { + what = "No user records returned"; + goto error; + } + + status = dsGetRecordEntry(nodeRef, tDataBuff, 1, + &attrListRef, &pRecEntry); + OPEN_DIR_ERROR("Failed getting record entry"); + + for (attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++) { + status = dsGetAttributeEntry(nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry); + if (status == eDSNoErr && pAttrEntry != NULL) { + tAttributeValueEntry *pValueEntry = NULL; + + if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation) == 0) { + status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry); + if (status == eDSNoErr && pValueEntry != NULL) { + pUserLocation = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1); + 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 = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1); + memcpy(*outUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength); + } + } + + if (pValueEntry) { + dsDeallocAttributeValueEntry(dsRef, pValueEntry); + pValueEntry = NULL; + } + + dsDeallocAttributeEntry(dsRef, pAttrEntry); + pAttrEntry = NULL; + dsCloseAttributeValueList(valueRef); + valueRef = 0; + } + } + + if (!pUserLocation) { + DEBUG2("[mschap] OpenDirectory has no user location"); + result = RLM_MODULE_NOOP; + break; + } + + /* 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) { + RERROR("Failed building user from path"); + result = RLM_MODULE_FAIL; + break; + } + + status = dsOpenDirNode(dsRef, pUserNode, userNodeRef); + dsDataListDeallocate(dsRef, pUserNode); + free(pUserNode); + + if (status != eDSNoErr) { + error: + status_name = dsCopyDirStatusName(status); + RERROR("%s: status = %s", what, status_name); + free(status_name); + 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) + talloc_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; +} + +rlm_rcode_t od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair) +{ + rlm_rcode_t rcode = RLM_MODULE_OK; + 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 = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY); +#ifndef NDEBUG + unsigned int t; +#endif + + username_string = talloc_array(request, char, usernamepair->vp_length + 1); + if (!username_string) + return RLM_MODULE_FAIL; + + strlcpy(username_string, usernamepair->vp_strvalue, usernamepair->vp_length + 1); + + status = dsOpenDirService(&dsRef); + if (status != eDSNoErr) { + talloc_free(username_string); + RERROR("Failed opening directory service"); + return RLM_MODULE_FAIL; + } + + rcode = getUserNodeRef(request, username_string, &shortUserName, &userNodeRef, dsRef); + if (rcode != RLM_MODULE_OK) { + if (rcode != RLM_MODULE_NOOP) { + RDEBUG2("od_mschap_auth: getUserNodeRef() failed"); + } + if (username_string != NULL) + talloc_free(username_string); + if (dsRef != 0) + dsCloseDirService(dsRef); + return rcode; + } + + /* 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; + + /* User name length + username */ + uiLen = (uint32_t)(shortUserName ? strlen(shortUserName) : 0); + + RDEBUG2("OD username_string = %s, OD shortUserName=%s (length = %d)\n", + username_string, shortUserName, uiLen); + + memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen)); + uiCurr += sizeof(uiLen); + memcpy(&(tDataBuff->fBufferData[uiCurr]), shortUserName, uiLen); + uiCurr += uiLen; +#ifndef NDEBUG + RINDENT(); + RDEBUG2("Stepbuf server challenge : "); + for (t = 0; t < challenge->vp_length; t++) { + fprintf(stderr, "%02x", (unsigned int) 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 : "); + for (t = 2; t < 18; t++) { + fprintf(stderr, "%02x", (unsigned int) 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 : "); + REXDENT(); + for (t = 26; t < 50; t++) { + fprintf(stderr, "%02x", (unsigned int) 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' }; + mschap_reply[0] = 'S'; + mschap_reply[1] = '='; + memcpy(&(mschap_reply[2]), &(pStepBuff->fBufferData[4]), len); + mschap_add_reply(request, + *response->vp_strvalue, + "MS-CHAP2-Success", + mschap_reply, len+2); + RDEBUG2("dsDoDirNodeAuth returns stepbuff: %s (len=%u)\n", mschap_reply, (unsigned int) len); + } + } + } + + /* clean up */ + if (username_string != NULL) + talloc_free(username_string); + if (shortUserName != NULL) + talloc_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) { + char *status_name = dsCopyDirStatusName(status); + RERROR("rlm_mschap: authentication failed - status = %s", status_name); + free(status_name); + return RLM_MODULE_REJECT; + } + + return RLM_MODULE_OK; +} + +#endif /* __APPLE__ */ |