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 static int getUserNodeRef(char* inUserName, char** outUserName, tDirNodeReference* userNodeRef, tDirReference dsRef)
37 tDataBuffer *tDataBuff = NULL;
38 tDirNodeReference nodeRef = 0;
39 long status = eDSNoErr;
40 tContextData context = NULL;
41 unsigned long nodeCount = 0;
42 unsigned long attrIndex = 0;
43 tDataList *nodeName = NULL;
44 tAttributeEntryPtr pAttrEntry = NULL;
45 tDataList *pRecName = NULL;
46 tDataList *pRecType = NULL;
47 tDataList *pAttrType = NULL;
48 unsigned long recCount = 0;
49 tRecordEntry *pRecEntry = NULL;
50 tAttributeListRef attrListRef = 0;
51 char *pUserLocation = NULL;
52 tAttributeValueListRef valueRef = 0;
53 tAttributeValueEntry *pValueEntry = NULL;
54 tDataList *pUserNode = NULL;
55 int result = RLM_MODULE_FAIL;
57 if (inUserName == NULL) {
58 radlog(L_ERR, "rlm_mschap: getUserNodeRef(): no username");
59 return RLM_MODULE_FAIL;
62 tDataBuff = dsDataBufferAllocate(dsRef, 4096);
63 if (tDataBuff == NULL) {
64 radlog(L_ERR, "rlm_mschap: getUserNodeRef(): dsDataBufferAllocate() status = %ld", status);
65 return RLM_MODULE_FAIL;
69 // find on search node
70 status = dsFindDirNodes(dsRef, tDataBuff, NULL, eDSAuthenticationSearchNodeName, &nodeCount, &context);
71 if (status != eDSNoErr) {
72 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): no node found? status = %ld", status);
73 result = RLM_MODULE_FAIL;
77 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): nodeCount < 1, status = %ld", status);
78 result = RLM_MODULE_FAIL;
82 status = dsGetDirNodeName(dsRef, tDataBuff, 1, &nodeName);
83 if (status != eDSNoErr) {
84 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetDirNodeName() status = %ld", status);
85 result = RLM_MODULE_FAIL;
89 status = dsOpenDirNode(dsRef, nodeName, &nodeRef);
90 dsDataListDeallocate(dsRef, nodeName);
94 if (status != eDSNoErr) {
95 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status);
96 result = RLM_MODULE_FAIL;
100 pRecName = dsBuildListFromStrings(dsRef, inUserName, NULL);
101 pRecType = dsBuildListFromStrings(dsRef, kDSStdRecordTypeUsers, NULL);
102 pAttrType = dsBuildListFromStrings(dsRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, NULL);
105 status = dsGetRecordList(nodeRef, tDataBuff, pRecName, eDSExact, pRecType,
106 pAttrType , 0, &recCount, &context);
107 if (status != eDSNoErr || recCount == 0) {
108 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetRecordList() status = %ld, recCount=%lu", status, recCount);
109 result = RLM_MODULE_FAIL;
113 status = dsGetRecordEntry(nodeRef, tDataBuff, 1, &attrListRef, &pRecEntry);
114 if (status != eDSNoErr) {
115 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetRecordEntry() status = %ld", status);
116 result = RLM_MODULE_FAIL;
120 for (attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++)
122 status = dsGetAttributeEntry(nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry);
123 if (status == eDSNoErr && pAttrEntry != NULL)
125 if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation) == 0) {
126 status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry);
127 if (status == eDSNoErr && pValueEntry != NULL)
129 pUserLocation = (char *) calloc(pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char));
130 memcpy(pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength);
133 else if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName) == 0) {
134 status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry);
135 if (status == eDSNoErr && pValueEntry != NULL)
137 *outUserName = (char *) malloc(pValueEntry->fAttributeValueData.fBufferLength + 1);
138 bzero(*outUserName,pValueEntry->fAttributeValueData.fBufferLength + 1);
139 memcpy(*outUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength);
143 if (pValueEntry != NULL) {
144 dsDeallocAttributeValueEntry(dsRef, pValueEntry);
148 dsDeallocAttributeEntry(dsRef, pAttrEntry);
150 dsCloseAttributeValueList(valueRef);
155 pUserNode = dsBuildFromPath(dsRef, pUserLocation, "/");
156 status = dsOpenDirNode(dsRef, pUserNode, userNodeRef);
157 if (status != eDSNoErr) {
158 radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status);
159 result = RLM_MODULE_FAIL;
163 result = RLM_MODULE_OK;
167 if (pRecEntry != NULL)
168 dsDeallocRecordEntry(dsRef, pRecEntry);
170 if (tDataBuff != NULL)
171 dsDataBufferDeAllocate(dsRef, tDataBuff);
173 if (pUserLocation != NULL)
176 if (pRecName != NULL) {
177 dsDataListDeallocate(dsRef, pRecName);
180 if (pRecType != NULL) {
181 dsDataListDeallocate(dsRef, pRecType);
184 if (pAttrType != NULL) {
185 dsDataListDeallocate(dsRef, pAttrType);
189 dsCloseDirNode(nodeRef);
195 int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair)
197 tDirStatus status = eDSNoErr;
198 tDirReference dsRef = 0;
199 tDirNodeReference userNodeRef = 0;
200 tDataBuffer *tDataBuff = NULL;
201 tDataBuffer *pStepBuff = NULL;
202 tDataNode *pAuthType = NULL;
203 unsigned long uiCurr = 0;
204 unsigned long uiLen = 0;
205 char *username_string = NULL;
206 char *shortUserName = NULL;
207 VALUE_PAIR *response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
209 username_string = (char *) malloc(usernamepair->length + 1);
210 if (username_string == NULL)
211 return RLM_MODULE_FAIL;
213 strlcpy(username_string, (char *)usernamepair->strvalue, usernamepair->length + 1);
215 status = dsOpenDirService(&dsRef);
216 if (status != eDSNoErr) {
217 free(username_string);
218 radlog(L_ERR,"rlm_mschap: od_mschap_auth(): dsOpenDirService = %d", status);
219 return RLM_MODULE_FAIL;
222 status = getUserNodeRef(username_string, &shortUserName, &userNodeRef, dsRef);
223 if(status != RLM_MODULE_OK) {
224 DEBUG2("rlm_osx_od: ds_mschap_auth: getUserNodeRef failed");
225 if (username_string != NULL)
226 free(username_string);
230 /* We got a node; fill the stepBuffer
232 MS-CHAPv2 authentication method. The Open Directory plug-in generates the reply data for the client.
233 The input buffer format consists of
234 a four byte length specifying the length of the user name that follows, the user name,
235 a four byte value specifying the length of the server challenge that follows, the server challenge,
236 a four byte value specifying the length of the peer challenge that follows, the peer challenge,
237 a four byte value specifying the length of the client's digest that follows, and the client's digest.
238 The output buffer consists of a four byte value specifying the length of the return digest for the client's challenge.
239 r = FillAuthBuff(pAuthBuff, 5,
240 strlen(inName), inName, // Directory Services long or short name
241 strlen(schal), schal, // server challenge
242 strlen(peerchal), peerchal, // client challenge
243 strlen(p24), p24, // P24 NT-Response
244 4, "User"); // must match the username that was used for the hash
246 inName = username_string
247 schal = challenge->strvalue
248 peerchal = response->strvalue + 2 (16 octets)
249 p24 = response->strvalue + 26 (24 octets)
252 pStepBuff = dsDataBufferAllocate(dsRef, 4096);
253 tDataBuff = dsDataBufferAllocate(dsRef, 4096);
254 pAuthType = dsDataNodeAllocateString(dsRef, kDSStdAuthMSCHAP2);
257 DEBUG2(" rlm_mschap:username_string = %s, shortUserName=%s (length = %lu)\n", username_string, shortUserName, strlen(shortUserName));
259 // User name length + username
260 uiLen = strlen(shortUserName);
261 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
262 uiCurr += sizeof(size_t);
263 memcpy(&(tDataBuff->fBufferData[uiCurr]), shortUserName, uiLen);
267 DEBUG2(" rlm_mschap: stepbuf server challenge:\t");
268 for (t = 0; t < challenge->length; t++) {
269 fprintf(stderr, "%02x", challenge->strvalue[t]);
271 fprintf(stderr, "\n");
275 // server challenge (ie. my (freeRADIUS) challenge)
277 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
278 uiCurr += sizeof(size_t);
279 memcpy(&(tDataBuff->fBufferData[uiCurr]), &(challenge->strvalue[0]), uiLen);
283 DEBUG2(" rlm_mschap: stepbuf peer challenge:\t\t");
284 for (t = 2; t < 18; t++) {
285 fprintf(stderr, "%02x", response->strvalue[t]);
287 fprintf(stderr, "\n");
291 // peer challenge (ie. the client-generated response)
293 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
294 uiCurr += sizeof(size_t);
295 memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->strvalue[2]), uiLen);
299 DEBUG2(" rlm_mschap stepbuf p24:\t\t");
300 for (t = 26; t < 50; t++) {
301 fprintf(stderr, "%02x", response->strvalue[t]);
303 fprintf(stderr, "\n");
307 // p24 (ie. second part of client-generated response)
308 uiLen = 24; //strlen(&(response->strvalue[26])); may contain NULL byte in the middle.
309 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
310 uiCurr += sizeof(size_t);
311 memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->strvalue[26]) , uiLen);
314 // Client generated use name (short name?)
315 uiLen = strlen(username_string);
316 memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
317 uiCurr += sizeof(size_t);
318 memcpy(&(tDataBuff->fBufferData[uiCurr]), username_string, uiLen);
323 tDataBuff->fBufferLength = uiCurr;
325 status = dsDoDirNodeAuth(userNodeRef, pAuthType, 1, tDataBuff, pStepBuff, NULL);
326 if (status == eDSNoErr)
328 if (pStepBuff->fBufferLength > 4)
332 memcpy(&len, pStepBuff->fBufferData, 4);
334 char mschap_reply[41] = { '\0' };
335 pStepBuff->fBufferData[len+4] = '\0';
336 mschap_reply[0] = 'S';
337 mschap_reply[1] = '=';
338 memcpy(&(mschap_reply[2]), &(pStepBuff->fBufferData[4]), len);
339 add_reply(&request->reply->vps, *response->strvalue,
340 "MS-CHAP2-Success", mschap_reply, len+2);
341 DEBUG2("rlm_mschap: dsDoDirNodeAuth returns stepbuff: %s (len=%ld)\n", mschap_reply, len);
347 if (username_string != NULL)
348 free(username_string);
349 if (shortUserName != NULL)
352 if (tDataBuff != NULL)
353 dsDataBufferDeAllocate(dsRef, tDataBuff);
354 if (pStepBuff != NULL)
355 dsDataBufferDeAllocate(dsRef, pStepBuff);
356 if (pAuthType != NULL)
357 dsDataNodeDeAllocate(dsRef, pAuthType);
358 if (userNodeRef != 0)
359 dsCloseDirNode(userNodeRef);
361 dsCloseDirService(dsRef);
363 if (status != eDSNoErr) {
365 radlog(L_ERR, "rlm_mschap: authentication failed %d", status); // <-- returns -14091 (eDSAuthMethodNotSupported) -14090
366 return RLM_MODULE_REJECT;
369 return RLM_MODULE_OK;
372 #endif /* __APPLE__ */