OpenDirectory support from Apple Inc. Their patch has it in
authoraland <aland>
Fri, 3 Aug 2007 19:04:35 +0000 (19:04 +0000)
committeraland <aland>
Fri, 3 Aug 2007 19:04:35 +0000 (19:04 +0000)
rlm_mschap.c.  We've placed it in a separate file to keep
license and copyright issues clear.

src/modules/rlm_mschap/Makefile
src/modules/rlm_mschap/opendir.c [new file with mode: 0644]
src/modules/rlm_mschap/rlm_mschap.c

index d46cd81..8933e81 100644 (file)
@@ -5,7 +5,7 @@
 #
 
 TARGET         = rlm_mschap
-SRCS           = rlm_mschap.c smbdes.c
+SRCS           = rlm_mschap.c smbdes.c opendir.c
 HEADERS                =
 RLM_CFLAGS     =
 RLM_LDFLAGS    =
diff --git a/src/modules/rlm_mschap/opendir.c b/src/modules/rlm_mschap/opendir.c
new file mode 100644 (file)
index 0000000..5df791c
--- /dev/null
@@ -0,0 +1,372 @@
+#if __APPLE__
+/*
+ * Open Directory support from Apple Inc.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2 only, as published by
+ *   the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License version 2
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2007 Apple Inc.
+ */
+
+#include       <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include       <freeradius-devel/radiusd.h>
+#include       <freeradius-devel/modules.h>
+#include       <freeradius-devel/rad_assert.h>
+#include        <freeradius-devel/md5.h>
+
+#include       <ctype.h>
+
+#include       "smbdes.h"
+
+#include <DirectoryService/DirectoryService.h>
+
+static int getUserNodeRef(char* inUserName, char** outUserName, tDirNodeReference* userNodeRef, tDirReference dsRef)
+{
+       tDataBuffer                                     *tDataBuff                      = NULL;
+       tDirNodeReference                       nodeRef                         = 0;
+       long                                            status                          = eDSNoErr;
+       tContextData                            context                         = NULL;
+       unsigned long                           nodeCount                       = 0;
+       unsigned long                           attrIndex                       = 0;
+       tDataList                                  *nodeName                    = NULL;
+       tAttributeEntryPtr                      pAttrEntry                      = NULL;
+       tDataList                                  *pRecName                    = NULL;
+       tDataList                                  *pRecType                    = NULL;
+       tDataList                                  *pAttrType                   = NULL;
+       unsigned long                           recCount                        = 0;
+       tRecordEntry                            *pRecEntry                      = NULL;
+       tAttributeListRef                       attrListRef                     = 0;
+       char                                       *pUserLocation               = NULL;
+       tAttributeValueListRef          valueRef                        = 0;
+       tAttributeValueEntry            *pValueEntry            = NULL;
+       tDataList                                       *pUserNode                      = NULL;
+       int                                                     result                          = RLM_MODULE_FAIL;
+       
+       if (inUserName == NULL) {
+               radlog(L_ERR, "rlm_mschap: getUserNodeRef(): no username");
+               return RLM_MODULE_FAIL;
+       }
+    
+       tDataBuff = dsDataBufferAllocate(dsRef, 4096);
+       if (tDataBuff == NULL) {
+               radlog(L_ERR, "rlm_mschap: getUserNodeRef(): dsDataBufferAllocate() status = %ld", status);  
+               return RLM_MODULE_FAIL;
+       }
+       
+       do {
+               // find on search node
+               status = dsFindDirNodes(dsRef, tDataBuff, NULL, eDSAuthenticationSearchNodeName, &nodeCount, &context);
+               if (status != eDSNoErr) {
+                       radlog(L_ERR,"rlm_mschap: getUserNodeRef(): no node found? status = %ld", status);  
+                       result = RLM_MODULE_FAIL;
+                       break;
+               }
+               if (nodeCount < 1) {
+                       radlog(L_ERR,"rlm_mschap: getUserNodeRef(): nodeCount < 1, status = %ld", status);  
+                       result = RLM_MODULE_FAIL;
+                       break;
+               }
+               
+               status = dsGetDirNodeName(dsRef, tDataBuff, 1, &nodeName);
+               if (status != eDSNoErr) {
+                       radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetDirNodeName() status = %ld", status);  
+                       result = RLM_MODULE_FAIL;
+                       break;
+               }
+               
+               status = dsOpenDirNode(dsRef, nodeName, &nodeRef);
+               dsDataListDeallocate(dsRef, nodeName);
+               free(nodeName);
+               nodeName = NULL;
+               
+               if (status != eDSNoErr) {
+                       radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status);  
+                       result = RLM_MODULE_FAIL;
+                       break;
+               }
+               
+               pRecName = dsBuildListFromStrings(dsRef, inUserName, NULL);
+               pRecType = dsBuildListFromStrings(dsRef, kDSStdRecordTypeUsers, NULL);
+               pAttrType = dsBuildListFromStrings(dsRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, NULL);
+               
+               recCount = 1;
+               status = dsGetRecordList(nodeRef, tDataBuff, pRecName, eDSExact, pRecType,
+                                                                 pAttrType , 0, &recCount, &context);
+               if (status != eDSNoErr || recCount == 0) {
+                       radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetRecordList() status = %ld, recCount=%lu", status, recCount);  
+                       result = RLM_MODULE_FAIL;
+                       break;
+               }
+               
+               status = dsGetRecordEntry(nodeRef, tDataBuff, 1, &attrListRef, &pRecEntry);
+               if (status != eDSNoErr) {
+                       radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsGetRecordEntry() status = %ld", status);  
+                       result = RLM_MODULE_FAIL;
+                       break;  
+               }
+               
+               for (attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++)
+               {
+                       status = dsGetAttributeEntry(nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry);
+                       if (status == eDSNoErr && pAttrEntry != NULL)
+                       {
+                               if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation) == 0) {
+                                       status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry);
+                                       if (status == eDSNoErr && pValueEntry != NULL)
+                                       {
+                                               pUserLocation = (char *) calloc(pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char));
+                                               memcpy(pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength);
+                                       }
+                               }
+                               else if (strcmp(pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName) == 0) {
+                                       status = dsGetAttributeValue(nodeRef, tDataBuff, 1, valueRef, &pValueEntry);
+                                       if (status == eDSNoErr && pValueEntry != NULL)
+                                       {
+                                               *outUserName = (char *) malloc(pValueEntry->fAttributeValueData.fBufferLength + 1);
+                                               bzero(*outUserName,pValueEntry->fAttributeValueData.fBufferLength + 1);
+                                               memcpy(*outUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength);
+                                       }
+                               }
+                               
+                               if (pValueEntry != NULL) {
+                                       dsDeallocAttributeValueEntry(dsRef, pValueEntry);
+                                       pValueEntry = NULL;
+                               }
+                               
+                               dsDeallocAttributeEntry(dsRef, pAttrEntry);
+                               pAttrEntry = NULL;
+                               dsCloseAttributeValueList(valueRef);
+                               valueRef = 0;
+                       }
+               }
+               
+               pUserNode = dsBuildFromPath(dsRef, pUserLocation, "/");
+               status = dsOpenDirNode(dsRef, pUserNode, userNodeRef);
+               if (status != eDSNoErr) {
+                       radlog(L_ERR,"rlm_mschap: getUserNodeRef(): dsOpenDirNode() status = %ld", status);  
+                       result = RLM_MODULE_FAIL;
+                       break;
+               }
+               
+               result = RLM_MODULE_OK;
+       }
+       while (0);
+       
+       if (pRecEntry != NULL)
+               dsDeallocRecordEntry(dsRef, pRecEntry);
+
+       if (tDataBuff != NULL)
+               dsDataBufferDeAllocate(dsRef, tDataBuff);
+               
+       if (pUserLocation != NULL)
+               free(pUserLocation);
+               
+       if (pRecName != NULL) {
+               dsDataListDeallocate(dsRef, pRecName);
+               free(pRecName);
+       }
+       if (pRecType != NULL) {
+               dsDataListDeallocate(dsRef, pRecType);
+               free(pRecType);
+       }
+       if (pAttrType != NULL) {
+               dsDataListDeallocate(dsRef, pAttrType);
+               free(pAttrType);
+       }
+       if (nodeRef != 0)
+               dsCloseDirNode(nodeRef);
+       
+       return  result;
+}
+
+
+int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair)
+{
+       tDirStatus                      status                          = eDSNoErr;
+       tDirReference           dsRef                           = 0;
+       tDirNodeReference   userNodeRef                 = 0;
+       tDataBuffer                     *tDataBuff                      = NULL;
+       tDataBuffer                     *pStepBuff                      = NULL;
+       tDataNode                       *pAuthType                      = NULL;
+       unsigned long           uiCurr                          = 0;
+       unsigned long           uiLen                           = 0;
+       char                            *username_string        = NULL;
+       char                            *shortUserName          = NULL;
+       VALUE_PAIR                      *response                       = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
+       
+       username_string = (char *) malloc(usernamepair->length + 1);
+       if (username_string == NULL)
+               return RLM_MODULE_FAIL;
+       
+       strlcpy(username_string, (char *)usernamepair->strvalue, usernamepair->length + 1);
+       
+       status = dsOpenDirService(&dsRef);
+       if (status != eDSNoErr) {
+               free(username_string);
+               radlog(L_ERR,"rlm_mschap: od_mschap_auth(): dsOpenDirService = %d", status);
+               return RLM_MODULE_FAIL;
+       }
+    
+       status = getUserNodeRef(username_string, &shortUserName, &userNodeRef, dsRef);
+       if(status != RLM_MODULE_OK) {
+               DEBUG2("rlm_osx_od: ds_mschap_auth: getUserNodeRef failed");
+               if (username_string != NULL)
+                       free(username_string);
+               return status;
+       }
+       
+       /* We got a node; fill the stepBuffer 
+               kDSStdAuthMSCHAP2
+                       MS-CHAPv2 authentication method. The Open Directory plug-in generates the reply data for the client. 
+                       The input buffer format consists of 
+                               a four byte length specifying the length of the user name that follows, the user name, 
+                               a four byte value specifying the length of the server challenge that follows, the server challenge, 
+                               a four byte value specifying the length of the peer challenge that follows, the peer challenge, 
+                               a four byte value specifying the length of the client's digest that follows, and the client's digest. 
+                               The output buffer consists of a four byte value specifying the length of the return digest for the client's challenge.
+               r = FillAuthBuff(pAuthBuff, 5,
+                                                       strlen(inName), inName,                                         // Directory Services long or short name
+                                                       strlen(schal), schal,                                           // server challenge
+                                                       strlen(peerchal), peerchal,                                     // client challenge
+                                                       strlen(p24), p24,                                                       // P24 NT-Response
+                                                       4, "User");                                                                     // must match the username that was used for the hash
+               
+               inName          =       username_string
+               schal           =   challenge->strvalue
+               peerchal        =   response->strvalue + 2 (16 octets)
+               p24                     =   response->strvalue + 26 (24 octets)
+       */
+
+       pStepBuff = dsDataBufferAllocate(dsRef, 4096);
+       tDataBuff = dsDataBufferAllocate(dsRef, 4096);
+       pAuthType = dsDataNodeAllocateString(dsRef, kDSStdAuthMSCHAP2);
+       uiCurr = 0;
+       
+       DEBUG2("        rlm_mschap:username_string = %s, shortUserName=%s (length = %lu)\n", username_string, shortUserName, strlen(shortUserName));
+       
+       // User name length + username
+       uiLen = strlen(shortUserName);
+       memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
+       uiCurr += sizeof(size_t);
+       memcpy(&(tDataBuff->fBufferData[uiCurr]), shortUserName, uiLen);
+       uiCurr += uiLen;
+#ifndef NDEBUG
+       int t;
+       DEBUG2("        rlm_mschap: stepbuf server challenge:\t");
+       for (t = 0; t < challenge->length; t++) {
+               fprintf(stderr, "%02x", challenge->strvalue[t]);
+       }
+       fprintf(stderr, "\n");
+       fflush(stderr);
+#endif
+       
+       // server challenge (ie. my (freeRADIUS) challenge)
+       uiLen = 16;
+       memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
+       uiCurr += sizeof(size_t);
+       memcpy(&(tDataBuff->fBufferData[uiCurr]), &(challenge->strvalue[0]), uiLen);
+       uiCurr += uiLen;
+       
+#ifndef NDEBUG
+       DEBUG2("        rlm_mschap: stepbuf peer challenge:\t\t");
+       for (t = 2; t < 18; t++) {
+               fprintf(stderr, "%02x", response->strvalue[t]);
+       }
+       fprintf(stderr, "\n");
+       fflush(stderr);
+#endif
+       
+       // peer challenge (ie. the client-generated response)
+       uiLen = 16;
+       memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
+       uiCurr += sizeof(size_t);
+       memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->strvalue[2]), uiLen);
+       uiCurr += uiLen;        
+       
+#ifndef NDEBUG
+       DEBUG2("        rlm_mschap stepbuf p24:\t\t");
+       for (t = 26; t < 50; t++) {
+               fprintf(stderr, "%02x", response->strvalue[t]);
+       }
+       fprintf(stderr, "\n");
+       fflush(stderr);
+#endif
+       
+       // p24 (ie. second part of client-generated response)
+       uiLen =  24; //strlen(&(response->strvalue[26])); may contain NULL byte in the middle.
+       memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
+       uiCurr += sizeof(size_t);
+       memcpy(&(tDataBuff->fBufferData[uiCurr]), &(response->strvalue[26]) , uiLen);
+       uiCurr += uiLen;
+       
+       // Client generated use name (short name?)
+       uiLen =  strlen(username_string);
+       memcpy(&(tDataBuff->fBufferData[uiCurr]), &uiLen, sizeof(size_t));
+       uiCurr += sizeof(size_t);
+       memcpy(&(tDataBuff->fBufferData[uiCurr]), username_string, uiLen);
+       uiCurr += uiLen;
+
+       fflush(stdout); 
+               
+       tDataBuff->fBufferLength = uiCurr;
+       
+       status = dsDoDirNodeAuth(userNodeRef, pAuthType, 1, tDataBuff, pStepBuff, NULL);
+       if (status == eDSNoErr)
+       {
+               if (pStepBuff->fBufferLength > 4)
+               {
+                       unsigned long len;
+                       
+                       memcpy(&len, pStepBuff->fBufferData, 4);
+                       if (len == 40) {
+                               char mschap_reply[41] = { '\0' };
+                               pStepBuff->fBufferData[len+4] = '\0';
+                               mschap_reply[0] = 'S';
+                               mschap_reply[1] = '=';
+                               memcpy(&(mschap_reply[2]), &(pStepBuff->fBufferData[4]), len);
+                               add_reply(&request->reply->vps, *response->strvalue,
+                                       "MS-CHAP2-Success", mschap_reply, len+2);
+                               DEBUG2("rlm_mschap: dsDoDirNodeAuth returns stepbuff: %s (len=%ld)\n", mschap_reply, len);
+                       }
+               }
+       }
+
+       /* clean up */
+       if (username_string != NULL)
+               free(username_string);
+       if (shortUserName != NULL)
+               free(shortUserName);
+
+       if (tDataBuff != NULL)
+               dsDataBufferDeAllocate(dsRef, tDataBuff);
+       if (pStepBuff != NULL)
+               dsDataBufferDeAllocate(dsRef, pStepBuff);
+       if (pAuthType != NULL)
+               dsDataNodeDeAllocate(dsRef, pAuthType);
+       if (userNodeRef != 0)
+               dsCloseDirNode(userNodeRef);
+       if (dsRef != 0)
+               dsCloseDirService(dsRef);
+       
+       if (status != eDSNoErr) {
+               errno = EACCES;
+               radlog(L_ERR, "rlm_mschap: authentication failed %d", status); // <-- returns -14091 (eDSAuthMethodNotSupported) -14090
+               return RLM_MODULE_REJECT;
+    }
+       
+       return RLM_MODULE_OK;
+}
+
+#endif /* __APPLE__ */
index bdb09c3..4f74b41 100644 (file)
@@ -54,6 +54,10 @@ RCSID("$Id$")
 
 #include       "smbdes.h"
 
+#ifdef __APPLE__
+extern int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair)
+#endif
+
 /* Allowable account control bits */
 #define ACB_DISABLED   0x0001  /* 1 = User account disabled */
 #define ACB_HOMDIRREQ  0x0002  /* 1 = Home directory required */
@@ -259,6 +263,9 @@ typedef struct rlm_mschap_t {
        char *xlat_name;
        char *ntlm_auth;
        char *auth_type;
+#if __APPLE__
+       int  open_directory;
+#endif  
 } rlm_mschap_t;
 
 
@@ -605,6 +612,10 @@ static const CONF_PARSER module_config[] = {
          offsetof(rlm_mschap_t, passwd_file), NULL,  NULL },
        { "ntlm_auth",   PW_TYPE_STRING_PTR,
          offsetof(rlm_mschap_t, ntlm_auth), NULL,  NULL },
+#ifdef __APPLE__
+       { "use_open_directory",    PW_TYPE_BOOLEAN,
+         offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
+#endif
 
        { NULL, -1, 0, NULL, NULL }             /* end the list */
 };
@@ -1227,6 +1238,16 @@ static int mschap_authenticate(void * instance, REQUEST *request)
                        username_string = username->vp_strvalue;
                }
 
+#if __APPLE__
+               /*
+                *      No "known good" NT-Password attribute.  Try to do
+                *      OpenDirectory authentication.
+                */
+               if (!nt_password && inst->open_directory) {
+                       DEBUG2("  rlm_mschap: No NT-Password configured. Trying DirectoryService Authentication.");
+                       return od_mschap_auth(request, challenge, username);
+               }
+#endif
                /*
                 *      The old "mschapv2" function has been moved to
                 *      here.