/* * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* ----------------------------------------------------------------------------- * * Theory of operation : * * plugin to add support for authentication through Directory Services. * ----------------------------------------------------------------------------- */ /* ----------------------------------------------------------------------------- Includes ----------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include "../../Helpers/vpnd/RASSchemaDefinitions.h" #include "../../Helpers/pppd/pppd.h" #include "../../Helpers/pppd/chap-new.h" #include "../../Helpers/pppd/chap_ms.h" #include "../../Family/ppp_comp.h" #include "DSUser.h" #define BUF_LEN 1024 char serviceName[] = "com.apple.ras"; static CFBundleRef bundle = 0; extern u_char mppe_send_key[MPPE_MAX_KEY_LEN]; extern u_char mppe_recv_key[MPPE_MAX_KEY_LEN]; extern int mppe_keys_set; /* Have the MPPE keys been set? */ static int dsauth_check(void); static int dsauth_ip_allowed_address(u_int32_t addr); static int dsauth_pap(char *user, char *passwd, char **msgp, struct wordlist **paddrs, struct wordlist **popts); static int dsauth_chap(char *name, char *ourname, int id, struct chap_digest_type *digest, unsigned char *challenge, unsigned char *response, unsigned char *message, int message_space); static void dsauth_set_mppe_keys(tDirReference dirRef, tDirNodeReference userNode, char* user, u_char *remmd, int remmd_len, tAttributeValueEntryPtr authAuthorityAttr); static void dsauth_get_admin_acct(u_int32_t *acctNameSize, char** acctName, u_int32_t *passwordSize, char **password); static int dsauth_get_admin_password(u_int32_t acctlen, char* acctname, u_int32_t *password_len, char **password); static int dsauth_find_user_node(tDirReference dirRef, char *user_name, tDirNodeReference *user_node, tAttributeValueEntryPtr *recordNameAttr, tAttributeValueEntryPtr *authAuthorityAttr); /* ----------------------------------------------------------------------------- plugin entry point, called by pppd ----------------------------------------------------------------------------- */ int start(CFBundleRef ref) { bundle = ref; CFRetain(bundle); pap_check_hook = dsauth_check; pap_auth_hook = dsauth_pap; chap_check_hook = dsauth_check; chap_verify_hook = dsauth_chap; allowed_address_hook = dsauth_ip_allowed_address; //add_options(Options); info("Directory Services Authentication plugin initialized"); return 0; } //---------------------------------------------------------------------- // dsauth_check //---------------------------------------------------------------------- static int dsauth_check(void) { return 1; } //---------------------------------------------------------------------- // dsauth_ip_allowed_address //---------------------------------------------------------------------- static int dsauth_ip_allowed_address(u_int32_t addr) { // any address is OK return 1; } //---------------------------------------------------------------------- // dsauth_pap //---------------------------------------------------------------------- static int dsauth_pap(char *user, char *passwd, char **msgp, struct wordlist **paddrs, struct wordlist **popts) { tDirReference dirRef; tDirNodeReference userNode = 0; tDataNodePtr authTypeDataNodePtr = 0; tDataBufferPtr authDataBufPtr = 0; tDataBufferPtr responseDataBufPtr = 0; tAttributeValueEntryPtr recordNameAttr = 0; tAttributeValueEntryPtr authAuthorityAttr = 0; tDirStatus dsResult = eDSNoErr; char *ptr; int authResult = 0; u_int32_t userShortNameSize; u_int32_t passwordSize = strlen(passwd); u_int32_t authDataSize; if ((dsResult = dsOpenDirService(&dirRef)) == eDSNoErr) { if ((responseDataBufPtr = dsDataBufferAllocate(dirRef, BUF_LEN)) == 0) { error("DSAuth plugin: Could not allocate data buffer\n"); goto cleanup; } if ((authTypeDataNodePtr = dsDataNodeAllocateString(dirRef, kDSStdAuthNodeNativeNoClearText)) == 0) { error("DSAuth plugin: Could not allocate data buffer\n"); goto cleanup; } if (dsauth_find_user_node(dirRef, user, &userNode, &recordNameAttr, &authAuthorityAttr) == 0) { userShortNameSize = recordNameAttr->fAttributeValueData.fBufferLength; authDataSize = userShortNameSize + passwordSize + (2 * sizeof(u_int32_t)); if ((authDataBufPtr = dsDataBufferAllocate(dirRef, authDataSize)) != 0) { authDataBufPtr->fBufferLength = authDataSize; /* store user name and password into the auth buffer in the correct format */ ptr = (char*)(authDataBufPtr->fBufferData); // 4 byte length & user name *((u_int32_t*)ptr) = userShortNameSize; ptr += sizeof(u_int32_t); memcpy(ptr, recordNameAttr->fAttributeValueData.fBufferData, userShortNameSize); ptr += userShortNameSize; // 4 byte length & password *((u_int32_t*)ptr) = passwordSize; ptr += sizeof(u_int32_t); memcpy(ptr, passwd, passwordSize); if ((dsResult = dsDoDirNodeAuth(userNode, authTypeDataNodePtr, TRUE, authDataBufPtr, responseDataBufPtr, 0)) == eDSNoErr) { authResult = 1; info("DSAuth plugin: user authentication successful\n"); } bzero(authDataBufPtr->fBufferData, authDataSize); // don't leave password in buffer } dsCloseDirNode(userNode); // returned from dsauth_find_user() dsDeallocAttributeValueEntry(dirRef, recordNameAttr); dsDeallocAttributeValueEntry(dirRef, authAuthorityAttr); } cleanup: if (responseDataBufPtr) dsDataBufferDeAllocate(dirRef, responseDataBufPtr); if (authTypeDataNodePtr) dsDataNodeDeAllocate(dirRef, authTypeDataNodePtr); if (authDataBufPtr) dsDataBufferDeAllocate(dirRef, authDataBufPtr); dsCloseDirService(dirRef); } return authResult; } //---------------------------------------------------------------------- // dsauth_chap //---------------------------------------------------------------------- #define CHALLENGE_SIZE 16 #define NT_RESPONSE_SIZE 24 static int dsauth_chap(char *name, char *ourname, int id, struct chap_digest_type *digest, unsigned char *challenge, unsigned char *response, unsigned char *message, int message_space) { tDirReference dirRef; tDirNodeReference userNode = 0; tDataNodePtr authTypeDataNodePtr = 0; tDataBufferPtr authDataBufPtr = 0; tDataBufferPtr responseDataBufPtr = 0; tAttributeValueEntryPtr recordNameAttr = 0; tAttributeValueEntryPtr authAuthorityAttr = 0; tDirStatus dsResult = eDSNoErr; int authResult = 0; char *ptr; MS_Chap2Response *resp; u_int32_t userShortNameSize; u_int32_t userNameSize = strlen(name); u_int32_t authDataSize; int challenge_len, response_len; challenge_len = *challenge++; /* skip length, is 16 */ response_len = *response++; // currently only support MS-CHAPv2 if (digest->code != CHAP_MICROSOFT_V2 || response_len != MS_CHAP2_RESPONSE_LEN || challenge_len != CHALLENGE_SIZE) return 0; resp = (MS_Chap2Response*)response; if ((dsResult = dsOpenDirService(&dirRef)) == eDSNoErr) { if ((responseDataBufPtr = dsDataBufferAllocate(dirRef, BUF_LEN)) == 0) { error("DSAuth plugin: Could not allocate data buffer\n"); goto cleanup; } if ((authTypeDataNodePtr = dsDataNodeAllocateString(dirRef, kDSStdAuthMSCHAP2)) == 0) { error("DSAuth plugin: Could not allocate data buffer\n"); goto cleanup; } if (dsauth_find_user_node(dirRef, name, &userNode, &recordNameAttr, &authAuthorityAttr) == 0) { userShortNameSize = recordNameAttr->fAttributeValueData.fBufferLength; authDataSize = userNameSize + userShortNameSize + NT_RESPONSE_SIZE + (2 * CHALLENGE_SIZE) + (5 * sizeof(u_int32_t)); if ((authDataBufPtr = dsDataBufferAllocate(dirRef, authDataSize)) != 0) { authDataBufPtr->fBufferLength = authDataSize; // setup the response buffer ptr = (char*)(authDataBufPtr->fBufferData); // 4 byte length & user name *((u_int32_t*)ptr) = userShortNameSize; ptr += sizeof(u_int32_t); memcpy(ptr, recordNameAttr->fAttributeValueData.fBufferData, userShortNameSize); ptr += userShortNameSize; // 4 byte length & server challenge *((u_int32_t*)ptr) = CHALLENGE_SIZE; ptr += sizeof(u_int32_t); memcpy(ptr, challenge, CHALLENGE_SIZE); ptr += CHALLENGE_SIZE; // 4 byte length & peer challenge *((u_int32_t*)ptr) = CHALLENGE_SIZE; ptr += sizeof(u_int32_t); memcpy(ptr, resp->PeerChallenge, CHALLENGE_SIZE); ptr += CHALLENGE_SIZE; // 4 byte length & client digest *((u_int32_t*)ptr) = NT_RESPONSE_SIZE; ptr += sizeof(u_int32_t); memcpy(ptr, resp->NTResp, NT_RESPONSE_SIZE); ptr += NT_RESPONSE_SIZE; // 4 byte length & user name (repeated) *((u_int32_t*)ptr) = userNameSize; ptr += sizeof(u_int32_t); memcpy(ptr, name, userNameSize); if ((dsResult = dsDoDirNodeAuth(userNode, authTypeDataNodePtr, TRUE, authDataBufPtr, responseDataBufPtr, 0)) == eDSNoErr) { // setup return data if ((responseDataBufPtr->fBufferLength == MS_AUTH_RESPONSE_LENGTH + 4) && *((u_int32_t*)(responseDataBufPtr->fBufferData)) == MS_AUTH_RESPONSE_LENGTH) { responseDataBufPtr->fBufferData[4 + MS_AUTH_RESPONSE_LENGTH] = 0; if (resp->Flags[0]) slprintf(message, message_space, "S=%s", responseDataBufPtr->fBufferData + 4); else slprintf(message, message_space, "S=%s M=%s", responseDataBufPtr->fBufferData + 4, "Access granted"); authResult = 1; dsauth_set_mppe_keys(dirRef, userNode, recordNameAttr->fAttributeValueData.fBufferData, response, response_len, authAuthorityAttr); } } } dsCloseDirNode(userNode); dsDeallocAttributeValueEntry(dirRef, recordNameAttr); dsDeallocAttributeValueEntry(dirRef, authAuthorityAttr); } cleanup: if (responseDataBufPtr) dsDataBufferDeAllocate(dirRef, responseDataBufPtr); if (authTypeDataNodePtr) dsDataNodeDeAllocate(dirRef, authTypeDataNodePtr); if (authDataBufPtr) dsDataBufferDeAllocate(dirRef, authDataBufPtr); dsCloseDirService(dirRef); } return authResult; } //---------------------------------------------------------------------- // dsauth_set_mppe_keys // get the mppe keys from the password server // if this fails - do nothing. there is no way to // know if the keys are actually going to be needed // at this point. //---------------------------------------------------------------------- static void dsauth_set_mppe_keys(tDirReference dirRef, tDirNodeReference userNode, char* user, u_char *remmd, int remmd_len, tAttributeValueEntryPtr authAuthorityAttr) { tDataNodePtr authTypeDataNodePtr = 0; tDataNodePtr authKeysDataNodePtr = 0; tDataBufferPtr authDataBufPtr = 0; tDataBufferPtr responseDataBufPtr = 0; tDirStatus dsResult = eDSNoErr; char *ptr, *tagStart; MS_Chap2Response *resp; char *keyaccessPassword = 0; char *keyaccessName = 0; u_int32_t keyaccessNameSize = 0; u_int32_t keyaccessPasswordSize; int len, useKeyAgent, i; u_int32_t userNameSize = strlen(user); mppe_keys_set = 0; useKeyAgent = 0; // parse authAuthorityAttribute to determine if key agent needs to be used. // auth authority tag will be between 2 semicolons. tagStart = 0; ptr = authAuthorityAttr->fAttributeValueData.fBufferData; for (i = 0; i < authAuthorityAttr->fAttributeValueData.fBufferLength; i++) { if (*ptr == ';') { if (tagStart == 0) tagStart = ptr + 1; else break; } ptr++; } if (*ptr != ';') return; if (strncmp(tagStart, kDSTagAuthAuthorityPasswordServer, ptr - tagStart) == 0) { useKeyAgent = 1; dsauth_get_admin_acct(&keyaccessNameSize, &keyaccessName, &keyaccessPasswordSize, &keyaccessPassword); if (keyaccessName == 0) { error("DSAuth plugin: Could not retrieve key agent account information.\n"); return; } } else if (strncmp(tagStart, kDSTagAuthAuthorityShadowHash, ptr - tagStart) == 0) { useKeyAgent = 0; } else return; // unsupported authentication authority - don't set the keys resp = (MS_Chap2Response*)remmd; if ((responseDataBufPtr = dsDataBufferAllocate(dirRef, BUF_LEN)) == 0) { error("DSAuth plugin: Could not allocate data buffer\n"); goto cleanup; } if ((authTypeDataNodePtr = dsDataNodeAllocateString(dirRef, kDSStdAuthNodeNativeNoClearText)) == 0) { error("DSAuth plugin: Could not allocate data buffer\n"); goto cleanup; } if ((authKeysDataNodePtr = dsDataNodeAllocateString(dirRef, "dsAuthMethodStandard:dsAuthVPN_MPPEMasterKeys" /* kDSStdAuthVPN_MPPEMasterKeys */)) == 0) { error("DSAuth plugin: Could not allocate data buffer\n"); goto cleanup; } if ((authDataBufPtr = dsDataBufferAllocate(dirRef, BUF_LEN)) == 0) { error("DSAuth plugin: Could not allocate data buffer\n"); goto cleanup; } if (useKeyAgent) { // need to use key agent to get MPPE keys for user in LDAP domain ptr = (char*)(authDataBufPtr->fBufferData); // 4 byte length & admin name *((u_int32_t*)ptr) = keyaccessNameSize; ptr += sizeof(u_int32_t); memcpy(ptr, keyaccessName, keyaccessNameSize); ptr += keyaccessNameSize; // 4 byte length & password *((u_int32_t*)ptr) = keyaccessPasswordSize; ptr += sizeof(u_int32_t); memcpy(ptr, keyaccessPassword, keyaccessPasswordSize); authDataBufPtr->fBufferLength = keyaccessNameSize + keyaccessPasswordSize + (2 * sizeof(u_int32_t)); // authenticate to the user node if ((dsResult = dsDoDirNodeAuth(userNode, authTypeDataNodePtr, false, authDataBufPtr, responseDataBufPtr, 0)) != eDSNoErr) { error("DSAuth plugin: Could not authenticate key agent for encryption key retrieval.\n"); goto cleanup; } } ptr = (char*)(authDataBufPtr->fBufferData); // get the mppe keys // 4 byte length & user name *((u_int32_t*)ptr) = userNameSize; ptr += sizeof(u_int32_t); memcpy(ptr, user, userNameSize); ptr += userNameSize; // 4 byte length & client digest *((u_int32_t*)ptr) = NT_RESPONSE_SIZE; ptr += sizeof(u_int32_t); memcpy(ptr, resp->NTResp, NT_RESPONSE_SIZE); ptr += NT_RESPONSE_SIZE; // 4 byte length and master key len - always 128 *((u_int32_t*)ptr) = 1; ptr += sizeof(u_int32_t); *ptr = MPPE_MAX_KEY_LEN; authDataBufPtr->fBufferLength = userNameSize + NT_RESPONSE_SIZE + 1 + (3 * sizeof(u_int32_t)); // get the keys if ((dsResult = dsDoDirNodeAuth(userNode, authKeysDataNodePtr, false, authDataBufPtr, responseDataBufPtr, 0)) == eDSNoErr) { if (responseDataBufPtr->fBufferLength == (2 * sizeof(u_int32_t)) + (2 * MPPE_MAX_KEY_LEN)) { ptr = (char*)(responseDataBufPtr->fBufferData); len = *((u_int32_t*)ptr); ptr += sizeof(u_int32_t); if (len == sizeof(mppe_send_key)) memcpy(mppe_send_key, ptr, sizeof(mppe_send_key)); ptr += len; len = *((u_int32_t*)ptr); ptr += sizeof(u_int32_t); if (len == sizeof(mppe_recv_key)) memcpy(mppe_recv_key, ptr, sizeof(mppe_recv_key)); mppe_keys_set = 1; } } else error("DSAuth plugin: Failed to retrieve MPPE encryption keys from the password server.\n"); cleanup: if (keyaccessPassword) { bzero(keyaccessPassword, keyaccessPasswordSize); // clear the admin password from memory SecKeychainItemFreeContent(NULL, keyaccessPassword); } if (keyaccessName) { free(keyaccessName); } if (responseDataBufPtr) dsDataBufferDeAllocate(dirRef, responseDataBufPtr); if (authTypeDataNodePtr) dsDataNodeDeAllocate(dirRef, authTypeDataNodePtr); if (authKeysDataNodePtr) dsDataNodeDeAllocate(dirRef, authTypeDataNodePtr); if (authDataBufPtr) dsDataBufferDeAllocate(dirRef, authDataBufPtr); } //---------------------------------------------------------------------- // dsauth_get_admin_acct //---------------------------------------------------------------------- static void dsauth_get_admin_acct(u_int32_t *acctNameSize, char** acctName, u_int32_t *passwordSize, char **password) { SCPreferencesRef prefs; CFPropertyListRef globals; CFStringRef acctNameRef; char namestr[256]; u_int32_t namelen; *passwordSize = 0; *password = 0; *acctNameSize = 0; *acctName = 0; // // get the acct name from the plist // if ((prefs = SCPreferencesCreate(0, CFSTR("pppd"), kRASServerPrefsFileName)) != 0) { // get globals dict from the plist if ((globals = SCPreferencesGetValue(prefs, kRASGlobals)) != 0) { // retrieve the password server account id if ((acctNameRef = CFDictionaryGetValue(globals, KRASGlobPSKeyAccount)) != 0) { namestr[0] = 0; CFStringGetCString(acctNameRef, namestr, 256, kCFStringEncodingMacRoman); if (namestr[0]) { namelen = strlen(namestr); if (dsauth_get_admin_password(namelen, namestr, passwordSize, password) == 0) { *acctNameSize = namelen; *acctName = malloc(namelen + 1); if (acctName != 0) memcpy(*acctName, namestr, namelen + 1); } } else error("DSAuth plugin: Key access user name not valid.\n"); } } CFRelease(prefs); } } //---------------------------------------------------------------------- // dsauth_get_admin_password //---------------------------------------------------------------------- static int dsauth_get_admin_password(u_int32_t acctlen, char* acctname, u_int32_t *password_len, char **password) { SecKeychainRef keychain = 0; OSStatus status; if ((status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem)) == noErr) { if ((status = SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem, &keychain)) == noErr) { status = SecKeychainFindGenericPassword(keychain, strlen(serviceName), serviceName, acctlen, acctname, (UInt32*)password_len, (void**)password, NULL); } } if (keychain) CFRelease(keychain); if (status == noErr) return 0; else { error("DSAuth plugin: Error %d while retrieving key agent password from the system keychain.\n", status); return -1; } } //---------------------------------------------------------------------- // dsauth_find_user_node //---------------------------------------------------------------------- static int dsauth_find_user_node(tDirReference dirRef, char *user_name, tDirNodeReference *user_node, tAttributeValueEntryPtr *recordNameAttr, tAttributeValueEntryPtr *authAuthorityAttr) { tDirStatus dsResult = eDSNoErr; tDataListPtr userPathDataListPtr = 0; unsigned long searchNodeCount; tAttributeValueEntryPtr userNodePath = 0; tDirNodeReference searchNodeRef = 0; *user_node = 0; // init return values *recordNameAttr = 0; *authAuthorityAttr = 0; // get search node ref if ((dsResult = dsauth_get_search_node_ref(dirRef, 1, &searchNodeRef, &searchNodeCount)) == eDSNoErr) { // get the meta node location attribute from the user's record dsResult = dsauth_get_user_attr(dirRef, searchNodeRef, user_name, kDSNAttrMetaNodeLocation, &userNodePath); if (dsResult == eDSNoErr && userNodePath != 0) { // open the user node and return the node ref if (userPathDataListPtr = dsBuildFromPath(dirRef, userNodePath->fAttributeValueData.fBufferData, "/")) { dsResult == dsOpenDirNode(dirRef, userPathDataListPtr, user_node); dsDataListDeallocate(dirRef, userPathDataListPtr); } if (dsResult == eDSNoErr) dsResult = dsauth_get_user_attr(dirRef, searchNodeRef, user_name, kDSNAttrRecordName, recordNameAttr); if (dsResult == eDSNoErr) dsResult = dsauth_get_user_attr(dirRef, searchNodeRef, user_name, kDSNAttrAuthenticationAuthority, authAuthorityAttr); } if (userNodePath) dsDeallocAttributeValueEntry(dirRef, userNodePath); dsCloseDirNode(searchNodeRef); // close the search node ref } if (dsResult != eDSNoErr || *user_node == 0 || *recordNameAttr == 0 || *authAuthorityAttr == 0) { if (*user_node) dsCloseDirNode(*user_node); if (*recordNameAttr) dsDeallocAttributeValueEntry(dirRef, *recordNameAttr); if (*authAuthorityAttr) dsDeallocAttributeValueEntry(dirRef, *authAuthorityAttr); return -1; } return 0; }