OpenDirectory support from Apple Inc. Their patch has it in
[freeradius.git] / src / modules / rlm_mschap / opendir.c
1 #if __APPLE__
2 /*
3  * Open Directory support from Apple Inc.
4  *
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.
8  *
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.
13  *
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
17  *
18  * Copyright 2007 Apple Inc.
19  */
20
21 #include        <freeradius-devel/ident.h>
22 RCSID("$Id$")
23
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>
28
29 #include        <ctype.h>
30
31 #include        "smbdes.h"
32
33 #include <DirectoryService/DirectoryService.h>
34
35 static int getUserNodeRef(char* inUserName, char** outUserName, tDirNodeReference* userNodeRef, tDirReference dsRef)
36 {
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;
56         
57         if (inUserName == NULL) {
58                 radlog(L_ERR, "rlm_mschap: getUserNodeRef(): no username");
59                 return RLM_MODULE_FAIL;
60         }
61     
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;
66         }
67         
68         do {
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;
74                         break;
75                 }
76                 if (nodeCount < 1) {
77                         radlog(L_ERR,"rlm_mschap: getUserNodeRef(): nodeCount < 1, status = %ld", status);  
78                         result = RLM_MODULE_FAIL;
79                         break;
80                 }
81                 
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;
86                         break;
87                 }
88                 
89                 status = dsOpenDirNode(dsRef, nodeName, &nodeRef);
90                 dsDataListDeallocate(dsRef, nodeName);
91                 free(nodeName);
92                 nodeName = NULL;
93                 
94                 if (status != eDSNoErr) {
95                         radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status);  
96                         result = RLM_MODULE_FAIL;
97                         break;
98                 }
99                 
100                 pRecName = dsBuildListFromStrings(dsRef, inUserName, NULL);
101                 pRecType = dsBuildListFromStrings(dsRef, kDSStdRecordTypeUsers, NULL);
102                 pAttrType = dsBuildListFromStrings(dsRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, NULL);
103                 
104                 recCount = 1;
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;
110                         break;
111                 }
112                 
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;
117                         break;  
118                 }
119                 
120                 for (attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++)
121                 {
122                         status = dsGetAttributeEntry(nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry);
123                         if (status == eDSNoErr && pAttrEntry != NULL)
124                         {
125                                 if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation) == 0) {
126                                         status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry);
127                                         if (status == eDSNoErr && pValueEntry != NULL)
128                                         {
129                                                 pUserLocation = (char *) calloc(pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char));
130                                                 memcpy(pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength);
131                                         }
132                                 }
133                                 else if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName) == 0) {
134                                         status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry);
135                                         if (status == eDSNoErr && pValueEntry != NULL)
136                                         {
137                                                 *outUserName = (char *) malloc(pValueEntry->fAttributeValueData.fBufferLength + 1);
138                                                 bzero(*outUserName,pValueEntry->fAttributeValueData.fBufferLength + 1);
139                                                 memcpy(*outUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength);
140                                         }
141                                 }
142                                 
143                                 if (pValueEntry != NULL) {
144                                         dsDeallocAttributeValueEntry(dsRef, pValueEntry);
145                                         pValueEntry = NULL;
146                                 }
147                                 
148                                 dsDeallocAttributeEntry(dsRef, pAttrEntry);
149                                 pAttrEntry = NULL;
150                                 dsCloseAttributeValueList(valueRef);
151                                 valueRef = 0;
152                         }
153                 }
154                 
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;
160                         break;
161                 }
162                 
163                 result = RLM_MODULE_OK;
164         }
165         while (0);
166         
167         if (pRecEntry != NULL)
168                 dsDeallocRecordEntry(dsRef, pRecEntry);
169
170         if (tDataBuff != NULL)
171                 dsDataBufferDeAllocate(dsRef, tDataBuff);
172                 
173         if (pUserLocation != NULL)
174                 free(pUserLocation);
175                 
176         if (pRecName != NULL) {
177                 dsDataListDeallocate(dsRef, pRecName);
178                 free(pRecName);
179         }
180         if (pRecType != NULL) {
181                 dsDataListDeallocate(dsRef, pRecType);
182                 free(pRecType);
183         }
184         if (pAttrType != NULL) {
185                 dsDataListDeallocate(dsRef, pAttrType);
186                 free(pAttrType);
187         }
188         if (nodeRef != 0)
189                 dsCloseDirNode(nodeRef);
190         
191         return  result;
192 }
193
194
195 int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair)
196 {
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);
208         
209         username_string = (char *) malloc(usernamepair->length + 1);
210         if (username_string == NULL)
211                 return RLM_MODULE_FAIL;
212         
213         strlcpy(username_string, (char *)usernamepair->strvalue, usernamepair->length + 1);
214         
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;
220         }
221     
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);
227                 return status;
228         }
229         
230         /* We got a node; fill the stepBuffer 
231                 kDSStdAuthMSCHAP2
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
245                 
246                 inName          =       username_string
247                 schal           =   challenge->strvalue
248                 peerchal        =   response->strvalue + 2 (16 octets)
249                 p24                     =   response->strvalue + 26 (24 octets)
250         */
251
252         pStepBuff = dsDataBufferAllocate(dsRef, 4096);
253         tDataBuff = dsDataBufferAllocate(dsRef, 4096);
254         pAuthType = dsDataNodeAllocateString(dsRef, kDSStdAuthMSCHAP2);
255         uiCurr = 0;
256         
257         DEBUG2("        rlm_mschap:username_string = %s, shortUserName=%s (length = %lu)\n", username_string, shortUserName, strlen(shortUserName));
258         
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);
264         uiCurr += uiLen;
265 #ifndef NDEBUG
266         int t;
267         DEBUG2("        rlm_mschap: stepbuf server challenge:\t");
268         for (t = 0; t < challenge->length; t++) {
269                 fprintf(stderr, "%02x", challenge->strvalue[t]);
270         }
271         fprintf(stderr, "\n");
272         fflush(stderr);
273 #endif
274         
275         // server challenge (ie. my (freeRADIUS) challenge)
276         uiLen = 16;
277         memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
278         uiCurr += sizeof(size_t);
279         memcpy(&(tDataBuff->fBufferData[uiCurr]), &(challenge->strvalue[0]), uiLen);
280         uiCurr += uiLen;
281         
282 #ifndef NDEBUG
283         DEBUG2("        rlm_mschap: stepbuf peer challenge:\t\t");
284         for (t = 2; t < 18; t++) {
285                 fprintf(stderr, "%02x", response->strvalue[t]);
286         }
287         fprintf(stderr, "\n");
288         fflush(stderr);
289 #endif
290         
291         // peer challenge (ie. the client-generated response)
292         uiLen = 16;
293         memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
294         uiCurr += sizeof(size_t);
295         memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->strvalue[2]), uiLen);
296         uiCurr += uiLen;        
297         
298 #ifndef NDEBUG
299         DEBUG2("        rlm_mschap stepbuf p24:\t\t");
300         for (t = 26; t < 50; t++) {
301                 fprintf(stderr, "%02x", response->strvalue[t]);
302         }
303         fprintf(stderr, "\n");
304         fflush(stderr);
305 #endif
306         
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);
312         uiCurr += uiLen;
313         
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);
319         uiCurr += uiLen;
320
321         fflush(stdout); 
322                 
323         tDataBuff->fBufferLength = uiCurr;
324         
325         status = dsDoDirNodeAuth(userNodeRef, pAuthType, 1, tDataBuff, pStepBuff, NULL);
326         if (status == eDSNoErr)
327         {
328                 if (pStepBuff->fBufferLength > 4)
329                 {
330                         unsigned long len;
331                         
332                         memcpy(&len, pStepBuff->fBufferData, 4);
333                         if (len == 40) {
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);
342                         }
343                 }
344         }
345
346         /* clean up */
347         if (username_string != NULL)
348                 free(username_string);
349         if (shortUserName != NULL)
350                 free(shortUserName);
351
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);
360         if (dsRef != 0)
361                 dsCloseDirService(dsRef);
362         
363         if (status != eDSNoErr) {
364                 errno = EACCES;
365                 radlog(L_ERR, "rlm_mschap: authentication failed %d", status); // <-- returns -14091 (eDSAuthMethodNotSupported) -14090
366                 return RLM_MODULE_REJECT;
367     }
368         
369         return RLM_MODULE_OK;
370 }
371
372 #endif /* __APPLE__ */