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.
21 #include <freeradius-devel/ident.h>
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/"
37 static int getUserNodeRef(char* inUserName, char **outUserName,
38 tDirNodeReference* userNodeRef, tDirReference dsRef)
40 tDataBuffer *tDataBuff = NULL;
41 tDirNodeReference nodeRef = 0;
42 long status = eDSNoErr;
43 tContextData context = 0;
44 unsigned long nodeCount = 0;
45 uint32_t attrIndex = 0;
46 tDataList *nodeName = NULL;
47 tAttributeEntryPtr pAttrEntry = NULL;
48 tDataList *pRecName = NULL;
49 tDataList *pRecType = NULL;
50 tDataList *pAttrType = NULL;
51 unsigned long recCount = 0;
52 tRecordEntry *pRecEntry = NULL;
53 tAttributeListRef attrListRef = 0;
54 char *pUserLocation = NULL;
55 tAttributeValueListRef valueRef = 0;
56 tAttributeValueEntry *pValueEntry = NULL;
57 tDataList *pUserNode = NULL;
58 int result = RLM_MODULE_FAIL;
60 if (inUserName == NULL) {
61 radlog(L_ERR, "rlm_mschap: getUserNodeRef(): no username");
62 return RLM_MODULE_FAIL;
65 tDataBuff = dsDataBufferAllocate(dsRef, 4096);
66 if (tDataBuff == NULL) {
67 radlog(L_ERR, "rlm_mschap: getUserNodeRef(): dsDataBufferAllocate() status = %ld", status);
68 return RLM_MODULE_FAIL;
72 /* find on search node */
73 status = dsFindDirNodes(dsRef, tDataBuff, NULL,
74 eDSAuthenticationSearchNodeName,
75 &nodeCount, &context);
76 if (status != eDSNoErr) {
77 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): no node found? status = %ld", status);
78 result = RLM_MODULE_FAIL;
82 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): nodeCount < 1, status = %ld", status);
83 result = RLM_MODULE_FAIL;
87 status = dsGetDirNodeName(dsRef, tDataBuff, 1, &nodeName);
88 if (status != eDSNoErr) {
89 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetDirNodeName() status = %ld", status);
90 result = RLM_MODULE_FAIL;
94 status = dsOpenDirNode(dsRef, nodeName, &nodeRef);
95 dsDataListDeallocate(dsRef, nodeName);
99 if (status != eDSNoErr) {
100 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status);
101 result = RLM_MODULE_FAIL;
105 pRecName = dsBuildListFromStrings(dsRef, inUserName, NULL);
106 pRecType = dsBuildListFromStrings(dsRef, kDSStdRecordTypeUsers,
108 pAttrType = dsBuildListFromStrings(dsRef,
109 kDSNAttrMetaNodeLocation,
110 kDSNAttrRecordName, NULL);
113 status = dsGetRecordList(nodeRef, tDataBuff, pRecName,
114 eDSExact, pRecType, pAttrType, 0,
115 &recCount, &context);
116 if (status != eDSNoErr || recCount == 0) {
117 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetRecordList() status = %ld, recCount=%lu", status, recCount);
118 result = RLM_MODULE_FAIL;
122 status = dsGetRecordEntry(nodeRef, tDataBuff, 1,
123 &attrListRef, &pRecEntry);
124 if (status != eDSNoErr) {
125 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetRecordEntry() status = %ld", status);
126 result = RLM_MODULE_FAIL;
130 for (attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++) {
131 status = dsGetAttributeEntry(nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry);
132 if (status == eDSNoErr && pAttrEntry != NULL) {
133 if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation) == 0) {
134 status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry);
135 if (status == eDSNoErr && pValueEntry != NULL) {
136 pUserLocation = (char *) calloc(pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char));
137 memcpy(pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength);
139 } else if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName) == 0) {
140 status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry);
141 if (status == eDSNoErr && pValueEntry != NULL) {
142 *outUserName = (char *) malloc(pValueEntry->fAttributeValueData.fBufferLength + 1);
143 bzero(*outUserName,pValueEntry->fAttributeValueData.fBufferLength + 1);
144 memcpy(*outUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength);
148 if (pValueEntry != NULL) {
149 dsDeallocAttributeValueEntry(dsRef, pValueEntry);
153 dsDeallocAttributeEntry(dsRef, pAttrEntry);
155 dsCloseAttributeValueList(valueRef);
160 /* OpenDirectory doesn't support mschapv2 authentication against
161 * Active Directory. AD users need to be authenticated using the
162 * normal freeradius AD path (i.e. ntlm_auth).
164 if (strncmp(pUserLocation, kActiveDirLoc, strlen(kActiveDirLoc)) == 0) {
165 DEBUG2("[mschap] OpenDirectory authentication returning noop. OD doesn't support MSCHAPv2 for ActiveDirectory users.");
166 result = RLM_MODULE_NOOP;
170 pUserNode = dsBuildFromPath(dsRef, pUserLocation, "/");
171 if (pUserNode == NULL) {
172 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsBuildFromPath() returned NULL");
173 result = RLM_MODULE_FAIL;
177 status = dsOpenDirNode(dsRef, pUserNode, userNodeRef);
178 dsDataListDeallocate(dsRef, pUserNode);
181 if (status != eDSNoErr) {
182 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status);
183 result = RLM_MODULE_FAIL;
187 result = RLM_MODULE_OK;
191 if (pRecEntry != NULL)
192 dsDeallocRecordEntry(dsRef, pRecEntry);
194 if (tDataBuff != NULL)
195 dsDataBufferDeAllocate(dsRef, tDataBuff);
197 if (pUserLocation != NULL)
200 if (pRecName != NULL) {
201 dsDataListDeallocate(dsRef, pRecName);
204 if (pRecType != NULL) {
205 dsDataListDeallocate(dsRef, pRecType);
208 if (pAttrType != NULL) {
209 dsDataListDeallocate(dsRef, pAttrType);
213 dsCloseDirNode(nodeRef);
219 int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge,
220 VALUE_PAIR * usernamepair)
222 tDirStatus status = eDSNoErr;
223 tDirReference dsRef = 0;
224 tDirNodeReference userNodeRef = 0;
225 tDataBuffer *tDataBuff = NULL;
226 tDataBuffer *pStepBuff = NULL;
227 tDataNode *pAuthType = NULL;
230 char *username_string = NULL;
231 char *shortUserName = NULL;
232 VALUE_PAIR *response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT);
237 username_string = (char *) malloc(usernamepair->length + 1);
238 if (username_string == NULL)
239 return RLM_MODULE_FAIL;
241 strlcpy(username_string, (char *)usernamepair->vp_strvalue,
242 usernamepair->length + 1);
244 status = dsOpenDirService(&dsRef);
245 if (status != eDSNoErr) {
246 free(username_string);
247 radlog(L_ERR,"rlm_mschap: od_mschap_auth(): dsOpenDirService = %d", status);
248 return RLM_MODULE_FAIL;
251 status = getUserNodeRef(username_string, &shortUserName, &userNodeRef, dsRef);
252 if(status != RLM_MODULE_OK) {
253 if (status != RLM_MODULE_NOOP) {
254 RDEBUG2("od_mschap_auth: getUserNodeRef() failed");
256 if (username_string != NULL)
257 free(username_string);
259 dsCloseDirService(dsRef);
263 /* We got a node; fill the stepBuffer
265 MS-CHAPv2 authentication method. The Open Directory plug-in generates the reply data for the client.
266 The input buffer format consists of
267 a four byte length specifying the length of the user name that follows, the user name,
268 a four byte value specifying the length of the server challenge that follows, the server challenge,
269 a four byte value specifying the length of the peer challenge that follows, the peer challenge,
270 a four byte value specifying the length of the client's digest that follows, and the client's digest.
271 The output buffer consists of a four byte value specifying the length of the return digest for the client's challenge.
272 r = FillAuthBuff(pAuthBuff, 5,
273 strlen(inName), inName, // Directory Services long or short name
274 strlen(schal), schal, // server challenge
275 strlen(peerchal), peerchal, // client challenge
276 strlen(p24), p24, // P24 NT-Response
277 4, "User"); // must match the username that was used for the hash
279 inName = username_string
280 schal = challenge->vp_strvalue
281 peerchal = response->vp_strvalue + 2 (16 octets)
282 p24 = response->vp_strvalue + 26 (24 octets)
285 pStepBuff = dsDataBufferAllocate(dsRef, 4096);
286 tDataBuff = dsDataBufferAllocate(dsRef, 4096);
287 pAuthType = dsDataNodeAllocateString(dsRef, kDSStdAuthMSCHAP2);
290 RDEBUG2("OD username_string = %s, OD shortUserName=%s (length = %lu)\n", username_string, shortUserName, strlen(shortUserName));
292 /* User name length + username */
293 uiLen = (uint32_t)strlen(shortUserName);
294 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
295 uiCurr += sizeof(uiLen);
296 memcpy(&(tDataBuff->fBufferData[uiCurr]), shortUserName, uiLen);
299 RDEBUG2(" stepbuf server challenge:\t");
300 for (t = 0; t < challenge->length; t++) {
301 fprintf(stderr, "%02x", challenge->vp_strvalue[t]);
303 fprintf(stderr, "\n");
306 /* server challenge (ie. my (freeRADIUS) challenge) */
308 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
309 uiCurr += sizeof(uiLen);
310 memcpy(&(tDataBuff->fBufferData[uiCurr]), &(challenge->vp_strvalue[0]),
315 RDEBUG2(" stepbuf peer challenge:\t\t");
316 for (t = 2; t < 18; t++) {
317 fprintf(stderr, "%02x", response->vp_strvalue[t]);
319 fprintf(stderr, "\n");
322 /* peer challenge (ie. the client-generated response) */
324 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
325 uiCurr += sizeof(uiLen);
326 memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->vp_strvalue[2]),
331 RDEBUG2(" stepbuf p24:\t\t");
332 for (t = 26; t < 50; t++) {
333 fprintf(stderr, "%02x", response->vp_strvalue[t]);
335 fprintf(stderr, "\n");
338 /* p24 (ie. second part of client-generated response) */
339 uiLen = 24; /* strlen(&(response->vp_strvalue[26])); may contain NULL byte in the middle. */
340 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
341 uiCurr += sizeof(uiLen);
342 memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->vp_strvalue[26]),
346 /* Client generated use name (short name?) */
347 uiLen = (uint32_t)strlen(username_string);
348 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
349 uiCurr += sizeof(uiLen);
350 memcpy(&(tDataBuff->fBufferData[uiCurr]), username_string, uiLen);
353 tDataBuff->fBufferLength = uiCurr;
355 status = dsDoDirNodeAuth(userNodeRef, pAuthType, 1, tDataBuff,
357 if (status == eDSNoErr) {
358 if (pStepBuff->fBufferLength > 4) {
361 memcpy(&len, pStepBuff->fBufferData, sizeof(len));
363 char mschap_reply[42] = { '\0' };
364 pStepBuff->fBufferData[len+4] = '\0';
365 mschap_reply[0] = 'S';
366 mschap_reply[1] = '=';
367 memcpy(&(mschap_reply[2]), &(pStepBuff->fBufferData[4]), len);
368 mschap_add_reply(request, &request->reply->vps,
369 *response->vp_strvalue,
371 mschap_reply, len+2);
372 RDEBUG2("dsDoDirNodeAuth returns stepbuff: %s (len=%ld)\n", mschap_reply, len);
378 if (username_string != NULL)
379 free(username_string);
380 if (shortUserName != NULL)
383 if (tDataBuff != NULL)
384 dsDataBufferDeAllocate(dsRef, tDataBuff);
385 if (pStepBuff != NULL)
386 dsDataBufferDeAllocate(dsRef, pStepBuff);
387 if (pAuthType != NULL)
388 dsDataNodeDeAllocate(dsRef, pAuthType);
389 if (userNodeRef != 0)
390 dsCloseDirNode(userNodeRef);
392 dsCloseDirService(dsRef);
394 if (status != eDSNoErr) {
396 radlog(L_ERR, "rlm_mschap: authentication failed %d", status); /* <-- returns -14091 (eDSAuthMethodNotSupported) -14090 */
397 return RLM_MODULE_REJECT;
400 return RLM_MODULE_OK;
403 #endif /* __APPLE__ */