Make MSCHAP attribute lookups use Microsofts vendor-id
[freeradius.git] / src / modules / rlm_mschap / opendir.c
1 #ifdef __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 #define kActiveDirLoc "/Active Directory/"
36
37 static int getUserNodeRef(char* inUserName, char **outUserName,
38                           tDirNodeReference* userNodeRef, tDirReference dsRef)
39 {
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;
59         
60         if (inUserName == NULL) {
61                 radlog(L_ERR, "rlm_mschap: getUserNodeRef(): no username");
62                 return RLM_MODULE_FAIL;
63         }
64     
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;
69         }
70         
71         do {
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;
79                         break;
80                 }
81                 if (nodeCount < 1) {
82                         radlog(L_ERR,"rlm_mschap: getUserNodeRef(): nodeCount < 1, status = %ld", status);  
83                         result = RLM_MODULE_FAIL;
84                         break;
85                 }
86                 
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;
91                         break;
92                 }
93                 
94                 status = dsOpenDirNode(dsRef, nodeName, &nodeRef);
95                 dsDataListDeallocate(dsRef, nodeName);
96                 free(nodeName);
97                 nodeName = NULL;
98                 
99                 if (status != eDSNoErr) {
100                         radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status);  
101                         result = RLM_MODULE_FAIL;
102                         break;
103                 }
104                 
105                 pRecName = dsBuildListFromStrings(dsRef, inUserName, NULL);
106                 pRecType = dsBuildListFromStrings(dsRef, kDSStdRecordTypeUsers,
107                                                   NULL);
108                 pAttrType = dsBuildListFromStrings(dsRef,
109                                                    kDSNAttrMetaNodeLocation,
110                                                    kDSNAttrRecordName, NULL);
111                 
112                 recCount = 1;
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;
119                         break;
120                 }
121                 
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;
127                         break;  
128                 }
129                 
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);
138                                         }
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);
145                                         }
146                                 }
147                                 
148                                 if (pValueEntry != NULL) {
149                                         dsDeallocAttributeValueEntry(dsRef, pValueEntry);
150                                         pValueEntry = NULL;
151                                 }
152                                 
153                                 dsDeallocAttributeEntry(dsRef, pAttrEntry);
154                                 pAttrEntry = NULL;
155                                 dsCloseAttributeValueList(valueRef);
156                                 valueRef = 0;
157                         }
158                 }
159                 
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).
163                  */
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;
167                         break;
168                 }
169         
170                 pUserNode = dsBuildFromPath(dsRef, pUserLocation, "/");
171                 if (pUserNode == NULL) {
172                         radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsBuildFromPath() returned NULL");  
173                         result = RLM_MODULE_FAIL;
174                         break;
175                 }
176                 
177                 status = dsOpenDirNode(dsRef, pUserNode, userNodeRef);
178                 dsDataListDeallocate(dsRef, pUserNode);
179                 free(pUserNode);
180
181                 if (status != eDSNoErr) {
182                         radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status);  
183                         result = RLM_MODULE_FAIL;
184                         break;
185                 }
186                 
187                 result = RLM_MODULE_OK;
188         }
189         while (0);
190         
191         if (pRecEntry != NULL)
192                 dsDeallocRecordEntry(dsRef, pRecEntry);
193
194         if (tDataBuff != NULL)
195                 dsDataBufferDeAllocate(dsRef, tDataBuff);
196                 
197         if (pUserLocation != NULL)
198                 free(pUserLocation);
199                 
200         if (pRecName != NULL) {
201                 dsDataListDeallocate(dsRef, pRecName);
202                 free(pRecName);
203         }
204         if (pRecType != NULL) {
205                 dsDataListDeallocate(dsRef, pRecType);
206                 free(pRecType);
207         }
208         if (pAttrType != NULL) {
209                 dsDataListDeallocate(dsRef, pAttrType);
210                 free(pAttrType);
211         }
212         if (nodeRef != 0)
213                 dsCloseDirNode(nodeRef);
214         
215         return  result;
216 }
217
218
219 int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge,
220                    VALUE_PAIR * usernamepair)
221 {
222         tDirStatus              status           = eDSNoErr;
223         tDirReference           dsRef            = 0;
224         tDirNodeReference       userNodeRef      = 0;
225         tDataBuffer             *tDataBuff       = NULL;
226         tDataBuffer             *pStepBuff       = NULL;
227         tDataNode               *pAuthType       = NULL;
228         uint32_t                uiCurr           = 0;
229         uint32_t                uiLen            = 0;
230         char                    *username_string = NULL;
231         char                    *shortUserName   = NULL;
232         VALUE_PAIR              *response        = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT);
233 #ifndef NDEBUG
234         int t;
235 #endif
236         
237         username_string = (char *) malloc(usernamepair->length + 1);
238         if (username_string == NULL)
239                 return RLM_MODULE_FAIL;
240         
241         strlcpy(username_string, (char *)usernamepair->vp_strvalue,
242                 usernamepair->length + 1);
243         
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;
249         }
250     
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");
255                 }
256                 if (username_string != NULL)
257                         free(username_string);
258                 if (dsRef != 0)
259                         dsCloseDirService(dsRef);
260                 return status;
261         }
262         
263         /* We got a node; fill the stepBuffer 
264            kDSStdAuthMSCHAP2
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
278                 
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)
283         */
284
285         pStepBuff = dsDataBufferAllocate(dsRef, 4096);
286         tDataBuff = dsDataBufferAllocate(dsRef, 4096);
287         pAuthType = dsDataNodeAllocateString(dsRef, kDSStdAuthMSCHAP2);
288         uiCurr = 0;
289         
290         RDEBUG2("OD username_string = %s, OD shortUserName=%s (length = %lu)\n", username_string, shortUserName, strlen(shortUserName));
291         
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);
297         uiCurr += uiLen;
298 #ifndef NDEBUG
299         RDEBUG2("       stepbuf server challenge:\t");
300         for (t = 0; t < challenge->length; t++) {
301                 fprintf(stderr, "%02x", challenge->vp_strvalue[t]);
302         }
303         fprintf(stderr, "\n");
304 #endif
305         
306         /* server challenge (ie. my (freeRADIUS) challenge) */
307         uiLen = 16;
308         memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
309         uiCurr += sizeof(uiLen);
310         memcpy(&(tDataBuff->fBufferData[uiCurr]), &(challenge->vp_strvalue[0]),
311                uiLen);
312         uiCurr += uiLen;
313         
314 #ifndef NDEBUG
315         RDEBUG2("       stepbuf peer challenge:\t\t");
316         for (t = 2; t < 18; t++) {
317                 fprintf(stderr, "%02x", response->vp_strvalue[t]);
318         }
319         fprintf(stderr, "\n");
320 #endif
321         
322         /* peer challenge (ie. the client-generated response) */
323         uiLen = 16;
324         memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(uiLen));
325         uiCurr += sizeof(uiLen);
326         memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->vp_strvalue[2]),
327                uiLen);
328         uiCurr += uiLen;        
329         
330 #ifndef NDEBUG
331         RDEBUG2("       stepbuf p24:\t\t");
332         for (t = 26; t < 50; t++) {
333                 fprintf(stderr, "%02x", response->vp_strvalue[t]);
334         }
335         fprintf(stderr, "\n");
336 #endif
337         
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]),
343                uiLen);
344         uiCurr += uiLen;
345         
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);
351         uiCurr += uiLen;
352
353         tDataBuff->fBufferLength = uiCurr;
354         
355         status = dsDoDirNodeAuth(userNodeRef, pAuthType, 1, tDataBuff,
356                                  pStepBuff, NULL);
357         if (status == eDSNoErr) {
358                 if (pStepBuff->fBufferLength > 4) {
359                         uint32_t len;
360                         
361                         memcpy(&len, pStepBuff->fBufferData, sizeof(len));
362                         if (len == 40) {
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,
370                                                  "MS-CHAP2-Success",
371                                                  mschap_reply, len+2);
372                                 RDEBUG2("dsDoDirNodeAuth returns stepbuff: %s (len=%ld)\n", mschap_reply, len);
373                         }
374                 }
375         }
376
377         /* clean up */
378         if (username_string != NULL)
379                 free(username_string);
380         if (shortUserName != NULL)
381                 free(shortUserName);
382
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);
391         if (dsRef != 0)
392                 dsCloseDirService(dsRef);
393         
394         if (status != eDSNoErr) {
395                 errno = EACCES;
396                 radlog(L_ERR, "rlm_mschap: authentication failed %d", status); /* <-- returns -14091 (eDSAuthMethodNotSupported) -14090 */
397                 return RLM_MODULE_REJECT;
398         }
399         
400         return RLM_MODULE_OK;
401 }
402
403 #endif /* __APPLE__ */