3 * Open Directory support from Apple Inc.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 only, as published by
7 * the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License version 2
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 * Copyright 2007 Apple Inc.
22 USES_APPLE_DEPRECATED_API
24 #include <freeradius-devel/radiusd.h>
25 #include <freeradius-devel/modules.h>
26 #include <freeradius-devel/rad_assert.h>
27 #include <freeradius-devel/md5.h>
33 #include <DirectoryService/DirectoryService.h>
35 #define kActiveDirLoc "/Active Directory/"
40 void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
41 char const* name, char const* value, int len);
44 * Only used by rlm_mschap.c
46 rlm_rcode_t od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair);
49 static rlm_rcode_t getUserNodeRef(REQUEST *request, char* inUserName, char **outUserName,
50 tDirNodeReference* userNodeRef, tDirReference dsRef)
52 tDataBuffer *tDataBuff = NULL;
53 tDirNodeReference nodeRef = 0;
54 long status = eDSNoErr;
55 char const *what = NULL;
56 char *status_name = NULL;
57 tContextData context = 0;
58 uint32_t nodeCount = 0;
59 uint32_t attrIndex = 0;
60 tDataList *nodeName = NULL;
61 tAttributeEntryPtr pAttrEntry = NULL;
62 tDataList *pRecName = NULL;
63 tDataList *pRecType = NULL;
64 tDataList *pAttrType = NULL;
65 uint32_t recCount = 0;
66 tRecordEntry *pRecEntry = NULL;
67 tAttributeListRef attrListRef = 0;
68 char *pUserLocation = NULL;
69 tAttributeValueListRef valueRef = 0;
70 tDataList *pUserNode = NULL;
71 rlm_rcode_t result = RLM_MODULE_FAIL;
74 ERROR("rlm_mschap: getUserNodeRef(): no username");
75 return RLM_MODULE_FAIL;
78 tDataBuff = dsDataBufferAllocate(dsRef, 4096);
80 RERROR("Failed allocating buffer");
81 return RLM_MODULE_FAIL;
85 /* find on search node */
86 status = dsFindDirNodes(dsRef, tDataBuff, NULL,
87 eDSAuthenticationSearchNodeName,
88 &nodeCount, &context);
89 #define OPEN_DIR_ERROR(_x) do if (status != eDSNoErr) { \
94 OPEN_DIR_ERROR("Failed to find directory");
97 what = "No directories found.";
101 status = dsGetDirNodeName(dsRef, tDataBuff, 1, &nodeName);
102 OPEN_DIR_ERROR("Failed getting directory name");
104 status = dsOpenDirNode(dsRef, nodeName, &nodeRef);
105 dsDataListDeallocate(dsRef, nodeName);
109 OPEN_DIR_ERROR("Failed opening directory");
111 pRecName = dsBuildListFromStrings(dsRef, inUserName, NULL);
112 pRecType = dsBuildListFromStrings(dsRef, kDSStdRecordTypeUsers,
114 pAttrType = dsBuildListFromStrings(dsRef,
115 kDSNAttrMetaNodeLocation,
116 kDSNAttrRecordName, NULL);
119 status = dsGetRecordList(nodeRef, tDataBuff, pRecName,
120 eDSExact, pRecType, pAttrType, 0,
121 &recCount, &context);
122 OPEN_DIR_ERROR("Failed getting record list");
125 what = "No user records returned";
129 status = dsGetRecordEntry(nodeRef, tDataBuff, 1,
130 &attrListRef, &pRecEntry);
131 OPEN_DIR_ERROR("Failed getting record entry");
133 for (attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++) {
134 status = dsGetAttributeEntry(nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry);
135 if (status == eDSNoErr && pAttrEntry != NULL) {
136 tAttributeValueEntry *pValueEntry = NULL;
138 if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation) == 0) {
139 status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry);
140 if (status == eDSNoErr && pValueEntry != NULL) {
141 pUserLocation = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1);
142 memcpy(pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength);
144 } else if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName) == 0) {
145 status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry);
146 if (status == eDSNoErr && pValueEntry != NULL) {
147 *outUserName = talloc_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1);
148 memcpy(*outUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength);
153 dsDeallocAttributeValueEntry(dsRef, pValueEntry);
157 dsDeallocAttributeEntry(dsRef, pAttrEntry);
159 dsCloseAttributeValueList(valueRef);
164 if (!pUserLocation) {
165 DEBUG2("[mschap] OpenDirectory has no user location");
166 result = RLM_MODULE_NOOP;
170 /* OpenDirectory doesn't support mschapv2 authentication against
171 * Active Directory. AD users need to be authenticated using the
172 * normal freeradius AD path (i.e. ntlm_auth).
174 if (strncmp(pUserLocation, kActiveDirLoc, strlen(kActiveDirLoc)) == 0) {
175 DEBUG2("[mschap] OpenDirectory authentication returning noop. OD doesn't support MSCHAPv2 for ActiveDirectory users");
176 result = RLM_MODULE_NOOP;
180 pUserNode = dsBuildFromPath(dsRef, pUserLocation, "/");
182 RERROR("Failed building user from path");
183 result = RLM_MODULE_FAIL;
187 status = dsOpenDirNode(dsRef, pUserNode, userNodeRef);
188 dsDataListDeallocate(dsRef, pUserNode);
191 if (status != eDSNoErr) {
193 status_name = dsCopyDirStatusName(status);
194 RERROR("%s: status = %s", what, status_name);
196 result = RLM_MODULE_FAIL;
200 result = RLM_MODULE_OK;
204 if (pRecEntry != NULL)
205 dsDeallocRecordEntry(dsRef, pRecEntry);
207 if (tDataBuff != NULL)
208 dsDataBufferDeAllocate(dsRef, tDataBuff);
210 if (pUserLocation != NULL)
211 talloc_free(pUserLocation);
213 if (pRecName != NULL) {
214 dsDataListDeallocate(dsRef, pRecName);
217 if (pRecType != NULL) {
218 dsDataListDeallocate(dsRef, pRecType);
221 if (pAttrType != NULL) {
222 dsDataListDeallocate(dsRef, pAttrType);
226 dsCloseDirNode(nodeRef);
231 rlm_rcode_t od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair)
233 rlm_rcode_t rcode = RLM_MODULE_OK;
234 tDirStatus status = eDSNoErr;
235 tDirReference dsRef = 0;
236 tDirNodeReference userNodeRef = 0;
237 tDataBuffer *tDataBuff = NULL;
238 tDataBuffer *pStepBuff = NULL;
239 tDataNode *pAuthType = NULL;
242 char *username_string = NULL;
243 char *shortUserName = NULL;
244 VALUE_PAIR *response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
249 username_string = talloc_array(request, char, usernamepair->vp_length + 1);
250 if (!username_string)
251 return RLM_MODULE_FAIL;
253 strlcpy(username_string, usernamepair->vp_strvalue, usernamepair->vp_length + 1);
255 status = dsOpenDirService(&dsRef);
256 if (status != eDSNoErr) {
257 talloc_free(username_string);
258 RERROR("Failed opening directory service");
259 return RLM_MODULE_FAIL;
262 rcode = getUserNodeRef(request, username_string, &shortUserName, &userNodeRef, dsRef);
263 if (rcode != RLM_MODULE_OK) {
264 if (rcode != RLM_MODULE_NOOP) {
265 RDEBUG2("od_mschap_auth: getUserNodeRef() failed");
267 if (username_string != NULL)
268 talloc_free(username_string);
270 dsCloseDirService(dsRef);
274 /* We got a node; fill the stepBuffer
276 MS-CHAPv2 authentication method. The Open Directory plug-in generates the reply data for the client.
277 The input buffer format consists of
278 a four byte length specifying the length of the user name that follows, the user name,
279 a four byte value specifying the length of the server challenge that follows, the server challenge,
280 a four byte value specifying the length of the peer challenge that follows, the peer challenge,
281 a four byte value specifying the length of the client's digest that follows, and the client's digest.
282 The output buffer consists of a four byte value specifying the length of the return digest for the client's challenge.
283 r = FillAuthBuff(pAuthBuff, 5,
284 strlen(inName), inName, // Directory Services long or short name
285 strlen(schal), schal, // server challenge
286 strlen(peerchal), peerchal, // client challenge
287 strlen(p24), p24, // P24 NT-Response
288 4, "User"); // must match the username that was used for the hash
290 inName = username_string
291 schal = challenge->vp_strvalue
292 peerchal = response->vp_strvalue + 2 (16 octets)
293 p24 = response->vp_strvalue + 26 (24 octets)
296 pStepBuff = dsDataBufferAllocate(dsRef, 4096);
297 tDataBuff = dsDataBufferAllocate(dsRef, 4096);
298 pAuthType = dsDataNodeAllocateString(dsRef, kDSStdAuthMSCHAP2);
301 RDEBUG2("OD username_string = %s, OD shortUserName=%s (length = %lu)\n", username_string, shortUserName, strlen(shortUserName));
303 /* User name length + username */
304 uiLen = (uint32_t)strlen(shortUserName);
305 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
306 uiCurr += sizeof(uiLen);
307 memcpy(&(tDataBuff->fBufferData[uiCurr]), shortUserName, uiLen);
311 RDEBUG2("Stepbuf server challenge : ");
312 for (t = 0; t < challenge->vp_length; t++) {
313 fprintf(stderr, "%02x", challenge->vp_strvalue[t]);
315 fprintf(stderr, "\n");
318 /* server challenge (ie. my (freeRADIUS) challenge) */
320 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
321 uiCurr += sizeof(uiLen);
322 memcpy(&(tDataBuff->fBufferData[uiCurr]), &(challenge->vp_strvalue[0]),
327 RDEBUG2("Stepbuf peer challenge : ");
328 for (t = 2; t < 18; t++) {
329 fprintf(stderr, "%02x", response->vp_strvalue[t]);
331 fprintf(stderr, "\n");
334 /* peer challenge (ie. the client-generated response) */
336 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
337 uiCurr += sizeof(uiLen);
338 memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->vp_strvalue[2]),
343 RDEBUG2("Stepbuf p24 : ");
345 for (t = 26; t < 50; t++) {
346 fprintf(stderr, "%02x", response->vp_strvalue[t]);
348 fprintf(stderr, "\n");
351 /* p24 (ie. second part of client-generated response) */
352 uiLen = 24; /* strlen(&(response->vp_strvalue[26])); may contain NULL byte in the middle. */
353 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
354 uiCurr += sizeof(uiLen);
355 memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->vp_strvalue[26]),
359 /* Client generated use name (short name?) */
360 uiLen = (uint32_t)strlen(username_string);
361 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
362 uiCurr += sizeof(uiLen);
363 memcpy(&(tDataBuff->fBufferData[uiCurr]), username_string, uiLen);
366 tDataBuff->fBufferLength = uiCurr;
368 status = dsDoDirNodeAuth(userNodeRef, pAuthType, 1, tDataBuff,
370 if (status == eDSNoErr) {
371 if (pStepBuff->fBufferLength > 4) {
374 memcpy(&len, pStepBuff->fBufferData, sizeof(len));
376 char mschap_reply[42] = { '\0' };
377 pStepBuff->fBufferData[len+4] = '\0';
378 mschap_reply[0] = 'S';
379 mschap_reply[1] = '=';
380 memcpy(&(mschap_reply[2]), &(pStepBuff->fBufferData[4]), len);
381 mschap_add_reply(request, &request->reply->vps,
382 *response->vp_strvalue,
384 mschap_reply, len+2);
385 RDEBUG2("dsDoDirNodeAuth returns stepbuff: %s (len=%zu)\n", mschap_reply, len);
391 if (username_string != NULL)
392 talloc_free(username_string);
393 if (shortUserName != NULL)
394 talloc_free(shortUserName);
396 if (tDataBuff != NULL)
397 dsDataBufferDeAllocate(dsRef, tDataBuff);
398 if (pStepBuff != NULL)
399 dsDataBufferDeAllocate(dsRef, pStepBuff);
400 if (pAuthType != NULL)
401 dsDataNodeDeAllocate(dsRef, pAuthType);
402 if (userNodeRef != 0)
403 dsCloseDirNode(userNodeRef);
405 dsCloseDirService(dsRef);
407 if (status != eDSNoErr) {
408 char *status_name = dsCopyDirStatusName(status);
409 RERROR("rlm_mschap: authentication failed - status = %s", status_name);
411 return RLM_MODULE_REJECT;
414 return RLM_MODULE_OK;
417 #endif /* __APPLE__ */