/* * Copyright (c) 2002 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@ */ /*! * @header CPSPlugIn */ #include <sys/stat.h> #include <sys/types.h> #include <syslog.h> #include <net/ethernet.h> #include <ldap.h> #include "CPSPlugIn.h" #include "PSUtilities.h" #include "CAuthFileBase.h" #include "ReplicaFileDefs.h" #include "CPSPlugInUtils.h" using namespace std; #include <DirectoryServiceCore/ServerModuleLib.h> #include <DirectoryServiceCore/CRCCalc.h> #include <DirectoryServiceCore/CPlugInRef.h> #include <DirectoryServiceCore/DSCThread.h> #include <DirectoryServiceCore/CContinue.h> #include <DirectoryServiceCore/DSEventSemaphore.h> #include <DirectoryServiceCore/DSMutexSemaphore.h> #include <DirectoryServiceCore/CSharedData.h> #include <DirectoryServiceCore/DSUtils.h> #include <DirectoryServiceCore/PrivateTypes.h> #include "SASLCode.h" #define DEBUG 0 #define kPWSPlugInPrefsDirPath "/Library/Preferences/DirectoryService" #define kPWSPlugInPrefsFilePath kPWSPlugInPrefsDirPath "/PasswordServerPluginPrefs.plist" #define kPWSPlugInPrefsSASLPriorityKey "SASL Mech Priority" #define kPWSPlugInDigestMethodStr ",method=\"" #define kDSTempSyncFileControlStr "/var/db/authserver/apsSyncFi%ld.%ld.gz" #define kDSRequestNonceHashStr "hash" #define dsDataListDeallocFree(DSREF, NODE) { dsDataListDeallocate( (DSREF), (NODE) ); free( (NODE) ); } #undef kAuthNTLMv2WithSessionKey #define kAuthNTLMv2WithSessionKey (1305) // -------------------------------------------------------------------------------- // Globals CPlugInRef *gPSContextTable = NULL; static DSEventSemaphore gKickSearchRequests; static DSMutexSemaphore *gSASLMutex = NULL; static DSMutexSemaphore *gPWSConnMutex = NULL; CContinue *gContinue = NULL; extern long gOpenCount; static long gCloseCount = 0; static MethodMapEntry gMethodMap[] = { { "APOP", kDSStdAuthAPOP }, { "CRAM-MD5", kDSStdAuthCRAM_MD5 }, { "CRYPT", kDSStdAuthCrypt }, { "MS-CHAPV2", kDSStdAuthMSCHAP2 }, { "PPS", "dsAuthMethodStandard:dsAuthNodePPS" }, { "SMB-LAN-MANAGER", kDSStdAuthSMB_LM_Key }, { "SMB-NT", kDSStdAuthSMB_NT_Key }, { "SMB-NTLMv2", kDSStdAuthNTLMv2 }, { "TWOWAYRANDOM", kDSStdAuth2WayRandom }, { "WEBDAV-DIGEST", kDSStdAuthDIGEST_MD5 }, { NULL, NULL } }; // Consts ---------------------------------------------------------------------------- static const UInt32 kBuffPad = 16; extern "C" { CFUUIDRef ModuleFactoryUUID = CFUUIDGetConstantUUIDWithBytes ( NULL, \ 0xF8, 0xAC, 0xD8, 0x6B, 0x3C, 0x66, 0x11, 0xD6, \ 0x93, 0x9C, 0x00, 0x03, 0x93, 0x50, 0xEB, 0x4E ); } static CDSServerModule* _Creator ( void ) { return( new CPSPlugIn ); } CDSServerModule::tCreator CDSServerModule::sCreator = _Creator; // -------------------------------------------------------------------------------- // * CPSPlugIn () // -------------------------------------------------------------------------------- CPSPlugIn::CPSPlugIn ( void ) { fState = kUnknownState; fOpenNodeCount = 0; fCalledSASLInit = false; try { if ( gPSContextTable == NULL ) { gPSContextTable = new CPlugInRef( CPSPlugIn::ContextDeallocProc ); Throw_NULL( gPSContextTable, eMemoryAllocError ); } if ( gSASLMutex == NULL ) { gSASLMutex = new DSMutexSemaphore(); Throw_NULL( gSASLMutex, eMemoryAllocError ); } if ( gPWSConnMutex == NULL ) { gPWSConnMutex = new DSMutexSemaphore(); Throw_NULL( gPWSConnMutex, eMemoryAllocError ); } if ( gContinue == NULL ) { gContinue = new CContinue( CPSPlugIn::ContinueDeallocProc ); Throw_NULL( gContinue, eMemoryAllocError ); } strlcpy( fSASLMechPriority, kAuthNative_Priority, sizeof(fSASLMechPriority) ); } catch (SInt32 err) { DEBUGLOG( "CPSPlugIn::CPSPlugIn failed: eMemoryAllocError"); throw( err ); } } // CPSPlugIn // -------------------------------------------------------------------------------- // * ~CPSPlugIn () // -------------------------------------------------------------------------------- CPSPlugIn::~CPSPlugIn ( void ) { } // ~CPSPlugIn // -------------------------------------------------------------------------------- // * Validate () // -------------------------------------------------------------------------------- SInt32 CPSPlugIn::Validate ( const char *inVersionStr, const UInt32 inSignature ) { fSignature = inSignature; return( eDSNoErr ); } // Validate // -------------------------------------------------------------------------------- // * Initialize () // -------------------------------------------------------------------------------- SInt32 CPSPlugIn::Initialize ( void ) { SInt32 siResult = eDSNoErr; CFMutableDictionaryRef prefsDict = NULL; CFStringRef priorityString = NULL; struct stat sb; // set the active and initted flags fState = kUnknownState; fState += kInitialized; fState += kActive; // read prefs file siResult = lstat( kPWSPlugInPrefsFilePath, &sb ); if ( siResult == 0 && pwsf_loadxml(kPWSPlugInPrefsFilePath, &prefsDict) == 0 ) { if ( CFDictionaryGetValueIfPresent(prefsDict, CFSTR(kPWSPlugInPrefsSASLPriorityKey), (const void **)&priorityString) ) { CFStringGetCString( priorityString, fSASLMechPriority, sizeof(fSASLMechPriority), kCFStringEncodingUTF8); } } else { // create a template prefsDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); if ( prefsDict != NULL ) { CFDictionaryAddValue( prefsDict, CFSTR(kPWSPlugInPrefsSASLPriorityKey), CFSTR(kAuthNative_Priority) ); pwsf_savexml( kPWSPlugInPrefsFilePath, prefsDict ); } } DSCFRelease( prefsDict ); return( siResult ); } // Initialize // -------------------------------------------------------------------------------- // * SetPluginState () // -------------------------------------------------------------------------------- SInt32 CPSPlugIn::SetPluginState ( const UInt32 inState ) { // don't allow any changes other than active / in-active if (kActive & inState) //want to set to active { if (fState & kActive) //if already active { //TODO ??? } else { //call to Init so that we re-init everything that requires it Initialize(); } WakeUpRequests(); } if (kInactive & inState) //want to set to in-active { if (!(fState & kInactive)) { fState += kInactive; } if (fState & kActive) { fState -= kActive; } } return( eDSNoErr ); } // SetPluginState //-------------------------------------------------------------------------------------------------- // * WakeUpRequests() (static) // //-------------------------------------------------------------------------------------------------- void CPSPlugIn::WakeUpRequests ( void ) { gKickSearchRequests.PostEvent(); } // WakeUpRequests // --------------------------------------------------------------------------- // * WaitForInit // // --------------------------------------------------------------------------- void CPSPlugIn::WaitForInit ( void ) { // we wait for 2 minutes before giving up gKickSearchRequests.WaitForEvent( (UInt32)(2 * 60 * kMilliSecsPerSec) ); } // WaitForInit // --------------------------------------------------------------------------- // * ProcessRequest // // --------------------------------------------------------------------------- SInt32 CPSPlugIn::ProcessRequest ( void *inData ) { SInt32 siResult = 0; if ( inData == NULL ) { return( ePlugInDataError ); } if( ((sHeader *)inData)->fType == kKerberosMutex || ((sHeader *)inData)->fType == kServerRunLoop ) { // we don't care about Kerberos mutexes here return eDSNoErr; } WaitForInit(); if ( (fState & kFailedToInit) ) { return( ePlugInFailedToInitialize ); } if ( ((fState & kInactive) || !(fState & kActive)) && (((sHeader *)inData)->fType != kDoPlugInCustomCall) && (((sHeader *)inData)->fType != kOpenDirNode) ) { return( ePlugInNotActive ); } if ( ((sHeader *)inData)->fType == kHandleNetworkTransition ) { siResult = Initialize(); } else { siResult = HandleRequest( inData ); } return( siResult ); } // ProcessRequest // --------------------------------------------------------------------------- // * HandleRequest // // --------------------------------------------------------------------------- SInt32 CPSPlugIn::HandleRequest ( void *inData ) { SInt32 siResult = 0; sHeader *pMsgHdr = NULL; if ( inData == NULL ) { return( -8088 ); } pMsgHdr = (sHeader *)inData; switch ( pMsgHdr->fType ) { case kOpenDirNode: siResult = OpenDirNode( (sOpenDirNode *)inData ); break; case kCloseDirNode: siResult = CloseDirNode( (sCloseDirNode *)inData ); break; case kGetDirNodeInfo: siResult = GetDirNodeInfo( (sGetDirNodeInfo *)inData ); break; case kGetAttributeEntry: siResult = GetAttributeEntry( (sGetAttributeEntry *)inData ); break; case kGetAttributeValue: siResult = GetAttributeValue( (sGetAttributeValue *)inData ); break; case kCloseAttributeList: siResult = CloseAttributeList( (sCloseAttributeList *)inData ); break; case kCloseAttributeValueList: siResult = CloseAttributeValueList( (sCloseAttributeValueList *)inData ); break; case kDoDirNodeAuth: siResult = DoAuthentication( (sDoDirNodeAuth *)inData ); break; case kDoPlugInCustomCall: siResult = DoPlugInCustomCall( (sDoPlugInCustomCall *)inData ); break; default: siResult = eNotHandledByThisNode; break; } pMsgHdr->fResult = siResult; return( siResult ); } // HandleRequest //------------------------------------------------------------------------------------ // * ReleaseContinueData //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::ReleaseContinueData ( sReleaseContinueData *inData ) { SInt32 siResult = eDSNoErr; // RemoveItem calls our ContinueDeallocProc to clean up if ( gContinue->RemoveItem( (void *)inData->fInContinueData ) != eDSNoErr ) { siResult = eDSInvalidContext; } return( siResult ); } // ReleaseContinueData //------------------------------------------------------------------------------------ // * OpenDirNode //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::OpenDirNode ( sOpenDirNode *inData ) { SInt32 siResult = eDSNoErr; tDataListPtr pNodeList = NULL; char *pathStr = NULL; char *subStr = NULL; sPSContextData *pContext = NULL; bool nodeNameIsID = false; unsigned int prefixLen = sizeof(kPasswordServerPrefixStr) - 1; sPSServerEntry anEntry; try { if ( inData != NULL ) { pNodeList = inData->fInDirNodeName; pathStr = dsGetPathFromListPriv( pNodeList, (char *)"/" ); Throw_NULL( pathStr, eDSNullNodeName ); DEBUGLOG( "CPSPlugIn::OpenDirNode path = %s", pathStr); //special case for the configure PS node? if (strcmp(pathStr,"/PasswordServer") == 0) { // set up the context data now with the relevant parameters for the configure PasswordServer node // DS API reference number is used to access the reference table /* pContext = MakeContextData(); pContext->psName = new char[1+strlen("PasswordServer Configure")]; ::strcpy(pContext->psName,"PasswordServer Configure"); // add the item to the reference table gPSContextTable->AddItem( inData->fOutNodeRef, pContext ); */ // currently, we do not configure anything so this is a bad node. siResult = eDSOpenNodeFailed; } // check that there is something after the delimiter or prefix // strip off the PasswordServer prefix here else if ( (strlen(pathStr) > prefixLen) && (::strncmp(pathStr,kPasswordServerPrefixStr,prefixLen) == 0) ) { char *debugEnvVar = getenv("PWSDEBUG"); if ( debugEnvVar != NULL ) { if ( strcmp( debugEnvVar, "0" ) == 0 ) psfwSetUSR1Debug( false ); else psfwSetUSR1Debug( true ); } pContext = MakeContextData(); // add the item to the reference table gPSContextTable->AddItem( inData->fOutNodeRef, pContext ); // sasl_client_init() is called when DirectoryService // starts. There is no longer a need to do it here. subStr = pathStr + prefixLen; if ( strncmp( subStr, "only/", 5 ) == 0 ) { subStr += 5; pContext->providedNodeOnlyOrFail = true; } if ( strncmp( subStr, "ipv4/", 5 ) == 0 ) subStr += 5; else if ( strncmp( subStr, "ipv6/", 5 ) == 0 ) subStr += 5; else if ( strncmp( subStr, "dns/", 4 ) == 0 ) subStr += 4; else if ( strncmp( subStr, "id/", 3 ) == 0 ) { subStr += 3; nodeNameIsID = true; } if ( nodeNameIsID && strlen(subStr) >= sizeof(anEntry.id) ) throw( (SInt32)eParameterError ); else if ( strlen(subStr) >= sizeof(anEntry.ip) ) throw( (SInt32)eParameterError ); bzero( &anEntry, sizeof(anEntry) ); if ( nodeNameIsID ) { strcpy( anEntry.id, subStr ); } else { int rc; int error_num; struct in_addr inetAddr; struct hostent *hostEnt; // is it an IP address? rc = inet_aton( subStr, &inetAddr ); if ( rc == 1 ) { strcpy( anEntry.ip, subStr ); } else { strlcpy( anEntry.dns, subStr, sizeof(anEntry.dns) ); // resolve if possible hostEnt = getipnodebyname( anEntry.dns, AF_INET, AI_DEFAULT, &error_num ); if ( hostEnt != NULL ) { if ( hostEnt->h_addr_list[0] != NULL ) { if ( inet_ntop(AF_INET, hostEnt->h_addr_list[0], anEntry.ip, sizeof(anEntry.ip)) == NULL ) anEntry.ip[0] = 0; } freehostent( hostEnt ); } DEBUGLOG( "anEntry.ip = %s", anEntry.ip ); } anEntry.ipFromNode = true; } if ( strcmp(anEntry.ip, "127.0.0.1") == 0 ) { struct stat sb; if ( lstat(kPWFilePath, &sb) != 0 ) { // no password server on this machine, fail now throw( (SInt32)eDSOpenNodeFailed ); } } pContext->serverProvidedFromNode = anEntry; strcpy( (char *)pContext->psIV, "D5F:A24A" ); fOpenNodeCount++; siResult = eDSNoErr; } // there was some name passed in here ie. length > 1 else { siResult = eDSOpenNodeFailed; } } // inData != NULL } // try catch( SInt32 err ) { siResult = err; if (pContext != NULL) { gPSContextTable->RemoveItem( inData->fOutNodeRef ); } } catch (...) { siResult = eDSOpenNodeFailed; } if (pathStr != NULL) { delete( pathStr ); pathStr = NULL; } return( siResult ); } // OpenDirNode //------------------------------------------------------------------------------------ // * CloseDirNode //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::CloseDirNode ( sCloseDirNode *inData ) { SInt32 siResult = eDSNoErr; sPSContextData *pContext = NULL; try { pContext = (sPSContextData *)gPSContextTable->GetItemData( inData->fInNodeRef ); Throw_NULL( pContext, eDSBadContextData ); // do whatever to close out the context EndServerSession( pContext, kSendQuit ); if ( fOpenNodeCount > 0 ) fOpenNodeCount--; this->CleanContextData( pContext ); gPSContextTable->RemoveItem( inData->fInNodeRef ); gPWSConnMutex->Wait(); gContinue->RemoveItems( inData->fInNodeRef ); gPWSConnMutex->Signal(); } catch( SInt32 err ) { siResult = err; } catch (...) { siResult = eDSCloseFailed; } return( siResult ); } // CloseDirNode // --------------------------------------------------------------------------- // * HandleFirstContact // --------------------------------------------------------------------------- SInt32 CPSPlugIn::HandleFirstContact( sPSContextData *inContext, const char *inIP, const char *inUserKeyHash, const char *inUserKeyStr, bool inSecondTime ) { SInt32 siResult = kCPSUtilFail; char *psName = NULL; bool usingLocalCache = false; int sock = -1; sPSServerEntry anEntry = {0}; sPSServerEntry *entrylist = NULL; CFIndex servIndex = 0; CFIndex servCount = 0; gPWSConnMutex->Wait(); try { if ( ! inContext->providedNodeOnlyOrFail ) { // check the config record for the current LDAP server if ( inContext->replicaFile != nil ) { DEBUGLOG( "CPSPlugIn::HandleFirstContact: trying LDAP Hint" ); siResult = kCPSUtilFail; // special-case localhost because it's not in the replica table CFStringRef ldapServerString = [(ReplicaFile *)inContext->replicaFile currentServerForLDAP]; if ( ldapServerString != NULL ) { if ( CFStringCompare(ldapServerString, CFSTR("127.0.0.1"), 0) == kCFCompareEqualTo ) { sPSServerEntry localEntry = { 0, 0, 0, "127.0.0.1", kPasswordServerPortStr, "", "", 1 }; siResult = IdentifyReachableReplicaByIP( &localEntry, 1, inUserKeyHash, &anEntry, &sock ); DEBUGLOG( "CPSPlugIn::HandleFirstContact: hint is 127.0.0.1, result = %l", siResult ); // retrieve the replica list for caching // get entry for the OD master if ( siResult == kCPSUtilOK ) { pwsf_GetServerListFromConfig( &inContext->serverList, (ReplicaFile *)inContext->replicaFile ); GetServerFromDict( [(ReplicaFile *)inContext->replicaFile getParent], 0, &inContext->master ); } } else { // Try current ldap IP. Translate the config record plist and extract the entry for ldap. siResult = pwsf_GetServerListFromConfig( &inContext->serverList, (ReplicaFile *)inContext->replicaFile ); if ( siResult == kCPSUtilOK && inContext->serverList != NULL && CFArrayGetCount( inContext->serverList ) > 0 ) { siResult = kCPSUtilFail; if ( ConvertCFArrayToServerArray(inContext->serverList, &entrylist, &servCount) == kCPSUtilOK ) { bool currentLDAPServerUnfound = true; for ( servIndex = 0; servIndex < servCount; servIndex++ ) { if ( entrylist[servIndex].currentServerForLDAP ) { currentLDAPServerUnfound = false; siResult = IdentifyReachableReplicaByIP( &entrylist[servIndex], 1, inUserKeyHash, &anEntry, &sock ); if ( siResult == kCPSUtilOK ) { GetServerFromDict( [(ReplicaFile *)inContext->replicaFile getParent], 0, &inContext->master ); } else { // if the LDAP entry isn't present, log more info DEBUGLOG( "CPSPlugIn::HandleFirstContact: Unable to contact the current LDAP server " "(ip = %s).", entrylist[servIndex].ip ); } break; } } DSFree( entrylist ); if ( currentLDAPServerUnfound ) { char ldapServerStr[256] = {0}; CFStringGetCString( ldapServerString, ldapServerStr, sizeof(ldapServerStr), kCFStringEncodingUTF8 ); DEBUGLOG( "CPSPlugIn::HandleFirstContact: could not find entry for LDAP hint " "(ip = %s)", ldapServerStr ); } } else { DEBUGLOG( "CPSPlugIn::HandleFirstContact: the LDAP config record is invalid." ); } } else { DEBUGLOG( "CPSPlugIn::HandleFirstContact: the LDAP config record has no entries." ); } } CFRelease( ldapServerString ); } else { DEBUGLOG( "CPSPlugIn::HandleFirstContact: the LDAP config record does not contain a hint." ); } } else { DEBUGLOG( "CPSPlugIn::HandleFirstContact: LDAP did not provide a config record." ); siResult = kCPSUtilFail; } // if that didn't work, try the local replica list cache if ( siResult != kCPSUtilOK ) { DEBUGLOG( "CPSPlugIn::HandleFirstContact: trying .local file" ); DSCFRelease( inContext->serverList ); siResult = GetPasswordServerList( &inContext->serverList, kPWSearchLocalFile ); if ( siResult == kCPSUtilOK && inContext->serverList != NULL && CFArrayGetCount( inContext->serverList ) > 0 ) { siResult = IdentifyReachableReplica( inContext->serverList, inUserKeyHash, &anEntry, &sock ); usingLocalCache = (siResult == kCPSUtilOK); } else siResult = kCPSUtilFail; } // if that didn't work, revisit the provided replica list if ( siResult != kCPSUtilOK ) { DEBUGLOG( "CPSPlugIn::HandleFirstContact: trying config record" ); DSCFRelease( inContext->serverList ); siResult = pwsf_GetServerListFromConfig( &inContext->serverList, (ReplicaFile *)inContext->replicaFile ); if ( siResult == kCPSUtilOK && inContext->serverList != NULL && CFArrayGetCount( inContext->serverList ) > 0 ) { siResult = IdentifyReachableReplica( inContext->serverList, inUserKeyHash, &anEntry, &sock ); if ( siResult == kCPSUtilOK ) GetServerFromDict( [(ReplicaFile *)inContext->replicaFile getParent], 0, &inContext->master ); } else siResult = kCPSUtilFail; } // next, try the password server's replication database (if this is an OD master or replica) if ( siResult != kCPSUtilOK ) { DEBUGLOG( "CPSPlugIn::HandleFirstContact: trying passwordserver's file" ); DSCFRelease( inContext->serverList ); siResult = GetPasswordServerListForKeyHash( &inContext->serverList, kPWSearchReplicaFile, inUserKeyHash ); if ( siResult == kCPSUtilOK && inContext->serverList != NULL && CFArrayGetCount( inContext->serverList ) > 0 ) { siResult = IdentifyReachableReplica( inContext->serverList, inUserKeyHash, &anEntry, &sock ); if ( siResult == kCPSUtilOK ) GetServerFromDict( [(ReplicaFile *)inContext->replicaFile getParent], 0, &inContext->master ); } else siResult = kCPSUtilFail; } // we're desperate now, try Bonjour if ( siResult != kCPSUtilOK ) { DEBUGLOG( "CPSPlugIn::HandleFirstContact: trying bonjour" ); DSCFRelease( inContext->serverList ); siResult = GetPasswordServerList( &inContext->serverList, kPWSearchRegisteredServices ); if ( siResult == kCPSUtilOK && inContext->serverList != NULL && CFArrayGetCount( inContext->serverList ) > 0 ) siResult = IdentifyReachableReplica( inContext->serverList, inUserKeyHash, &anEntry, &sock ); else siResult = kCPSUtilFail; } } // try node IP if ( siResult != kCPSUtilOK ) { DEBUGLOG( "CPSPlugIn::HandleFirstContact: trying node IP (%s)", inContext->serverProvidedFromNode.ip ); DSCFRelease( inContext->serverList ); siResult = IdentifyReachableReplicaByIP( &inContext->serverProvidedFromNode, 1, inUserKeyHash, &anEntry, &sock ); if ( siResult == kCPSUtilOK && (!inContext->providedNodeOnlyOrFail) && inUserKeyHash != NULL ) inContext->askForReplicaList = true; } // try localhost if ( !inSecondTime && !inContext->providedNodeOnlyOrFail && siResult != kCPSUtilOK ) { DEBUGLOG( "CPSPlugIn::HandleFirstContact: trying localhost" ); DSCFRelease( inContext->serverList ); sPSServerEntry localEntry = { 0, 0, 0, "127.0.0.1", kPasswordServerPortStr, "", "", 0 }; siResult = IdentifyReachableReplicaByIP( &localEntry, 1, inUserKeyHash, &anEntry, &sock ); } if ( siResult != kCPSUtilOK || DSIsStringEmpty(anEntry.ip) ) throw( (SInt32)eDSAuthNoAuthServerFound ); psName = (char *) calloc( 1, strlen(anEntry.ip) + 1 ); Throw_NULL( psName, eDSNullNodeName ); strcpy( psName, anEntry.ip ); if ( inContext->psName != NULL ) free( inContext->psName ); inContext->psName = psName; strlcpy(inContext->psPort, anEntry.port, 10); // close disconnected connections before opening new ones // release the conn mutex so we don't get deadlocked in // the ref table iterator gPWSConnMutex->Signal(); gPSContextTable->DoOnAllItems( CPSPlugIn::ReleaseCloseWaitConnections ); gPWSConnMutex->Wait(); siResult = BeginServerSession( inContext, sock, inUserKeyHash ); if ( siResult == eDSNoErr ) { // check for wrong server error if ( (inUserKeyStr != NULL) && (!RSAPublicKeysEqual( inUserKeyStr, inContext->rsaPublicKeyStr )) ) { EndServerSession( inContext, kSendQuit ); siResult = eDSAuthNoAuthServerFound; // if retrieved from local cache, try flushing the cache and do a fresh search if ( usingLocalCache ) { struct stat sb; if ( lstat( kPWReplicaLocalFile, &sb ) == 0 ) { unlink( kPWReplicaLocalFile ); siResult = HandleFirstContact( inContext, inIP, inUserKeyHash, inUserKeyStr, true ); } } } // save the cache iff we did a fresh lookup and were not limited to a single IP if ( (!usingLocalCache) && (!inContext->providedNodeOnlyOrFail) ) { if ( DSIsStringEmpty(anEntry.id) ) snprintf( anEntry.id, sizeof(anEntry.id), "%s", inContext->rsaPublicKeyHash ); (void)SaveLocalReplicaCache( inContext->serverList, &anEntry ); } } else if ( sock > 0 ) { EndServerSession( inContext, false ); } } catch( SInt32 error ) { siResult = error; } catch (...) { siResult = eDSAuthFailed; } gPWSConnMutex->Signal(); return siResult; } // --------------------------------------------------------------------------- // * BeginServerSession // // // --------------------------------------------------------------------------- SInt32 CPSPlugIn::BeginServerSession( sPSContextData *inContext, int inSock, const char *inUserKeyHash ) { SInt32 siResult = eDSNoErr; char *tptr = NULL; char buf[4096]; PWServerError serverResult; try { DEBUGLOG( "BeginServerSession, ip = %s, inSock = %d", inContext->psName ? inContext->psName : "(null)", inSock ); if ( inSock != -1 ) { EndServerSession( inContext, false ); inContext->fd = inSock; inContext->serverOut = fdopen(inSock, "w"); // get password server version from the greeting serverResult = readFromServer(inSock, buf, sizeof(buf)); if ( serverResult.err == 0 && (tptr = strstr(buf, "ApplePasswordServer")) != NULL ) { char *cur; int index = 0; tptr += sizeof( "ApplePasswordServer" ); while ( (cur = strsep(&tptr, ".")) != NULL ) { sscanf( cur, "%d", &inContext->serverVers[index++] ); if ( index >= 4 ) break; } } } else { // connect to remote server siResult = ConnectToServer( inContext ); if ( siResult != eDSNoErr ) throw( siResult ); } inContext->localaddr[0] = '\0'; inContext->remoteaddr[0] = '\0'; if ( !inContext->isUNIXDomainSocket ) { // set ip addresses socklen_t salen; char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV]; struct sockaddr_storage local_ip; salen = sizeof(local_ip); if (getsockname(inContext->fd, (struct sockaddr *)&local_ip, &salen) < 0) { salen = 0; DEBUGLOG("getsockname"); } if ( salen > 0 ) { getnameinfo((struct sockaddr *)&local_ip, salen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf), NI_NUMERICHOST | NI_WITHSCOPEID | NI_NUMERICSERV); snprintf(inContext->localaddr, sizeof(inContext->localaddr), "%s;%s", hbuf, pbuf); snprintf(inContext->remoteaddr, sizeof(inContext->remoteaddr), "%s;%s", inContext->psName, inContext->psPort); } } // retrieve the password server's list of available auth methods serverResult = SendFlushReadWithMutex( inContext, "LIST RSAPUBLIC", NULL, NULL, buf, sizeof(buf) ); if ( serverResult.err != 0 ) throw( PWSErrToDirServiceError(serverResult) ); sasl_chop(buf); siResult = GetSASLMechListFromString( buf, &inContext->mech, &inContext->mechCount ); if ( siResult != eDSNoErr ) { DEBUGLOG( "GetSASLMechListFromString = %l", siResult); throw( siResult ); } // did the rsa public key come too? if ( recvfrom( inContext->fd, buf, 1, (MSG_DONTWAIT | MSG_PEEK), NULL, NULL ) > 0 ) { serverResult = readFromServer( inContext->fd, buf, sizeof(buf) ); if ( serverResult.err != 0 ) throw( PWSErrToDirServiceError(serverResult) ); siResult = this->GetRSAPublicKey( inContext, buf ); } else { // old server, don't ask for the replica list inContext->askForReplicaList = false; // retrieve the password server's public RSA key siResult = this->GetRSAPublicKey( inContext ); } if ( siResult != eDSNoErr ) { DEBUGLOG( "rsapublic = %l", siResult); throw( siResult ); } if ( inContext->askForReplicaList && inUserKeyHash != NULL ) { char *replicaListData; unsigned long replicaListDataLen; struct stat sb; char filePath[sizeof(kPWReplicaRemoteFilePrefix) + strlen(inUserKeyHash)]; // construct the cache file name strcpy( filePath, kPWReplicaRemoteFilePrefix ); strcat( filePath, inUserKeyHash ); // only write a replica file if one doesn't already exist if ( lstat( filePath, &sb ) != 0 ) { siResult = GetReplicaListFromServer( inContext, &replicaListData, &replicaListDataLen ); if ( siResult == eDSNoErr ) { ReplicaFile *replicaFile = [[ReplicaFile alloc] initWithXMLStr:replicaListData]; [replicaFile saveXMLDataToFile:filePath]; [replicaFile free]; if ( replicaListData != NULL ) free( replicaListData ); } } inContext->askForReplicaList = false; } } catch( SInt32 error ) { siResult = error; } catch (...) { siResult = eDSAuthFailed; } return siResult; } // --------------------------------------------------------------------------- // * EndServerSession // --------------------------------------------------------------------------- SInt32 CPSPlugIn::EndServerSession( sPSContextData *inContext, bool inSendQuit ) { gPWSConnMutex->Wait(); if ( inSendQuit ) { if ( Connected( inContext ) ) { int result; PWServerError serverResult; struct timeval recvTimeoutVal = { 0, 150000 }; char buf[kOneKBuffer]; result = setsockopt( inContext->fd, SOL_SOCKET, SO_RCVTIMEO, &recvTimeoutVal, sizeof(recvTimeoutVal) ); serverResult = SendFlushReadWithMutex( inContext, "QUIT", NULL, NULL, buf, sizeof(buf) ); } } if ( inContext->serverOut != NULL ) { fpurge( inContext->serverOut ); fclose( inContext->serverOut ); inContext->serverOut = NULL; // inContext->serverOut was created using fdopen(inContext->fd) // That means the fclose() above also closed inContext->fd, so // just set it to -1. inContext->fd = -1; gCloseCount++; } if ( inContext->fd > 0 ) { close( inContext->fd ); gCloseCount++; } // always set to -1 inContext->fd = -1; inContext->castKeySet = false; bzero( &inContext->rc5Key, sizeof(RC5_32_KEY) ); gPWSConnMutex->Signal(); if ( gOpenCount != gCloseCount ) DEBUGLOG( "CPSPlugIn::EndServerSession opens: %l, closes %l", gOpenCount, gCloseCount ); return eDSNoErr; } // --------------------------------------------------------------------------- // * GetSASLMechListFromString // --------------------------------------------------------------------------- SInt32 CPSPlugIn::GetSASLMechListFromString( const char *inSASLList, AuthMethName **mechList, int *mechCount ) { const char *tptr = NULL; const char *end = NULL; int count = 0; if ( inSASLList == NULL || mechList == NULL || mechCount == NULL ) return eParameterError; *mechList = NULL; *mechCount = 0; tptr = inSASLList; for ( count = 0; tptr != NULL; count++ ) { tptr = strchr( tptr, ' ' ); if ( tptr != NULL ) tptr++; } if ( count == 0 ) return eDSNoErr; *mechList = (AuthMethName *)calloc(count, sizeof(AuthMethName)); if ( *mechList == NULL ) return eMemoryAllocError; *mechCount = count; tptr = strstr( inSASLList, kSASLListPrefix ); if ( tptr != NULL ) { tptr += sizeof( kSASLListPrefix ) - 1; for ( ; tptr && count > 0; count-- ) { if ( *tptr == '\"' ) tptr++; else break; end = strchr( tptr, '\"' ); if ( end != NULL ) strlcpy( (*mechList)[count-1].method, tptr, end - tptr + 1 ); else strcpy( (*mechList)[count-1].method, tptr ); tptr = end; if ( tptr != NULL ) tptr += 2; } } return eDSNoErr; } // --------------------------------------------------------------------------- // * GetRSAPublicKey // --------------------------------------------------------------------------- SInt32 CPSPlugIn::GetRSAPublicKey( sPSContextData *inContext, char *inData ) { SInt32 siResult = eDSNoErr; PWServerError serverResult; char buf[kOneKBuffer]; char *keyStr; char *bufPtr = NULL; int bits = 0; try { Throw_NULL( inContext, eDSBadContextData ); if ( inData == NULL ) { // get string serverResult = SendFlushReadWithMutex( inContext, "RSAPUBLIC", NULL, NULL, buf, sizeof(buf) ); if ( serverResult.err != 0 ) { DEBUGLOG( "no public key"); throw( (SInt32)eDSAuthServerError ); } bufPtr = buf; } else { bufPtr = inData; } sasl_chop( bufPtr ); inContext->rsaPublicKeyStr = (char *) calloc( 1, strlen(bufPtr)+1 ); Throw_NULL( inContext->rsaPublicKeyStr, eMemoryAllocError ); strcpy( inContext->rsaPublicKeyStr, bufPtr + 4 ); // get as struct inContext->rsaPublicKey = key_new( KEY_RSA ); Throw_NULL( inContext->rsaPublicKey, eDSAllocationFailed ); keyStr = bufPtr + 4; bits = pwsf_key_read(inContext->rsaPublicKey, &keyStr); if (bits == 0) { DEBUGLOG( "no key bits"); throw( (SInt32)eDSAuthServerError ); } // calculate the hash of the ID for comparison pwsf_CalcServerUniqueID( inContext->rsaPublicKeyStr, inContext->rsaPublicKeyHash ); } catch( SInt32 err ) { DEBUGLOG( "catch in GetRSAPublicKey = %l", err); siResult = err; } catch (...) { siResult = eDSAuthServerError; } return siResult; } // --------------------------------------------------------------------------- // * DoRSAValidation // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoRSAValidation ( sPSContextData *inContext, const char *inUserKey ) { SInt32 siResult = eDSNoErr; char *encodedStr = NULL; PWServerError serverResult; char buf[2 * kOneKBuffer]; char *bnStr = NULL; int len; MD5_CTX ctx; int extra = 384; unsigned char md5Result[MD5_DIGEST_LENGTH]; gPWSConnMutex->Wait(); try { Throw_NULL( inContext, eDSBadContextData ); // servers must have a public key if ( inContext->rsaPublicKey == NULL ) throw( (SInt32)eDSAuthServerError ); // make sure we are talking to the right server if ( ! RSAPublicKeysEqual( inContext->rsaPublicKeyStr, inUserKey ) ) throw( (SInt32)eDSAuthServerError ); siResult = GetBigNumber( inContext, &bnStr ); switch ( siResult ) { case kCPSUtilOK: break; case kCPSUtilMemoryError: throw( (SInt32)eMemoryError ); break; case kCPSUtilParameterError: throw( (SInt32)eParameterError ); break; default: throw( (SInt32)eDSAuthFailed ); } int nonceLen = strlen(bnStr); // just to be safe if ( nonceLen > 256 ) { bnStr[256] = '\0'; nonceLen = 256; } // add some scratch space for the RSA padding if ( inContext->rsaPublicKey->rsa != NULL && inContext->rsaPublicKey->rsa->n != NULL ) extra = RSA_size( inContext->rsaPublicKey->rsa ); encodedStr = (char *) malloc( nonceLen + sizeof(kDSRequestNonceHashStr) + extra ); Throw_NULL( encodedStr, eMemoryError ); // put the "HASH" text on the end to make a plain text attack a little more work // The "HASH" text must be encrypted to prevent a man-in-the-middle from stripping it off strcpy( buf, bnStr ); strcat( buf, kDSRequestNonceHashStr ); len = RSA_public_encrypt(nonceLen + sizeof(kDSRequestNonceHashStr), (unsigned char *)buf, (unsigned char *)encodedStr, inContext->rsaPublicKey->rsa, RSA_PKCS1_PADDING); if ( len <= 0 ) { DEBUGLOG( "rsa_public_encrypt() failed"); throw( (SInt32)eDSAuthServerError ); } if ( ConvertBinaryTo64( encodedStr, (unsigned)len, buf ) == SASL_OK ) { unsigned long encodedStrLen; serverResult = SendFlush( inContext, "RSAVALIDATE", buf, NULL ); if ( serverResult.err != 0 ) throw( PWSErrToDirServiceError(serverResult) ); // The response to RSAVALIDATE always comes back unencrypted. inContext->castKeySet = false; serverResult = readFromServer( inContext->fd, buf, sizeof(buf) ); if ( serverResult.err != 0 ) throw( PWSErrToDirServiceError(serverResult) ); // Assume failure and clear the error on success siResult = eDSAuthServerError; if ( Convert64ToBinary( buf + 4, encodedStr, kOneKBuffer, &encodedStrLen ) == SASL_OK ) { encodedStr[nonceLen] = '\0'; MD5_Init( &ctx ); MD5_Update( &ctx, bnStr, nonceLen ); MD5_Final( md5Result, &ctx ); if ( encodedStrLen >= MD5_DIGEST_LENGTH ) { // check for both hash and plain in case we are talking to a Jaguar server if ( memcmp(md5Result, encodedStr, MD5_DIGEST_LENGTH) == 0 ) { CAST_set_key( &inContext->castKey, nonceLen, (unsigned char *)bnStr ); bzero( inContext->castIV, sizeof(inContext->castIV) ); bzero( inContext->castReceiveIV, sizeof(inContext->castReceiveIV) ); inContext->castKeySet = true; siResult = eDSNoErr; } else { if ( memcmp(bnStr, encodedStr, nonceLen) == 0 ) siResult = eDSNoErr; } } } } else { siResult = eDSAuthFailed; } } catch( SInt32 err ) { siResult = err; } catch (...) { siResult = eDSAuthServerError; } gPWSConnMutex->Signal(); if ( bnStr != NULL ) free( bnStr ); if ( encodedStr != NULL ) free( encodedStr ); return siResult; } // --------------------------------------------------------------------------- // * SetupSecureSyncSession // --------------------------------------------------------------------------- SInt32 CPSPlugIn::SetupSecureSyncSession( sPSContextData *inContext ) { SInt32 siResult = eDSNoErr; char *encryptedStr = NULL; char *decryptedStr = NULL; char *bnStr = NULL; CAuthFileBase *authFile = NULL; char *encNonceStr = NULL; PWServerError serverResult; char buf[kOneKBuffer]; char base64Buf[kOneKBuffer]; int len; MD5_CTX ctx; unsigned char md5Result[MD5_DIGEST_LENGTH]; unsigned long encryptedStrLen; UInt32 randomLength; UInt32 nonceLength; time_t now; char timeBuf[30]; if ( inContext == NULL ) return eDSBadContextData; // already set up? if ( SecureSyncSessionIsSetup(inContext) ) return eDSNoErr; gPWSConnMutex->Wait(); try { // load RSA private key from Password Server database authFile = new CAuthFileBase(); if ( authFile == NULL ) throw( (SInt32)eMemoryError ); siResult = authFile->validateFiles(); if ( siResult != 0 ) throw( (SInt32)eDSAuthNoAuthServerFound ); if ( authFile->loadRSAKeys() != 1 ) throw( (SInt32)eDSAuthServerError ); siResult = GetBigNumber( inContext, &bnStr ); switch ( siResult ) { case kCPSUtilOK: if ( bnStr == NULL ) throw( (SInt32)eDSAuthFailed ); break; case kCPSUtilMemoryError: throw( (SInt32)eMemoryError ); break; case kCPSUtilParameterError: throw( (SInt32)eParameterError ); break; default: throw( (SInt32)eDSAuthFailed ); } int nonceLen = strlen(bnStr); encryptedStr = (char *) calloc(1, kOneKBuffer); Throw_NULL( encryptedStr, eMemoryError ); len = RSA_public_encrypt(nonceLen + 1, (unsigned char *)bnStr, (unsigned char *)encryptedStr, inContext->rsaPublicKey->rsa, RSA_PKCS1_PADDING); if ( len <= 0 ) { DEBUGLOG( "rsa_public_encrypt() failed"); throw( (SInt32)eDSAuthServerError ); } if ( ConvertBinaryTo64( encryptedStr, (unsigned)len, buf ) != SASL_OK ) { DEBUGLOG( "ConvertBinaryTo64() failed"); throw( (SInt32)eParameterError ); } time( &now ); snprintf( timeBuf, sizeof(timeBuf), "%lu", (unsigned long)now ); serverResult = SendFlushReadWithMutex( inContext, "SYNC SESSIONKEY", buf, timeBuf, buf, sizeof(buf) ); if ( serverResult.err != 0 ) throw( PWSErrToDirServiceError(serverResult) ); if ( Convert64ToBinary( buf + 4, encryptedStr, kOneKBuffer, &encryptedStrLen ) != SASL_OK ) { DEBUGLOG( "Convert64ToBinary() failed"); DEBUGLOG( "value = %s", buf + 4 ); throw( (SInt32)eParameterError ); } if ( encryptedStrLen < 8 ) { DEBUGLOG( "not enough data returned from SYNC SESSIONKEY"); throw( (SInt32)eDSAuthServerError ); } // get the RSA-encrypted random randomLength = ntohl( *((unsigned long *)encryptedStr) ); DEBUGLOG( "length of random from SYNC SESSIONKEY is %l", randomLength ); if ( randomLength > 128 ) { DEBUGLOG( "length of random from SYNC SESSIONKEY is too long"); throw( (SInt32)eDSAuthServerError ); } decryptedStr = (char *) malloc( randomLength + RSA_PKCS1_PADDING_SIZE + 1 ); if ( decryptedStr == NULL ) throw( (SInt32)eMemoryError ); siResult = authFile->decryptRSA( (unsigned char *)encryptedStr + 4, randomLength, (unsigned char *)decryptedStr ); decryptedStr[randomLength] = '\0'; if ( siResult != 0 ) throw( (SInt32)eDSNotAuthorized ); // get the rc5-encrypted nonce nonceLength = ntohl( *((unsigned long *)(encryptedStr + 4 + randomLength)) ); if ( nonceLength > 1024 ) throw( (SInt32)eDSAuthServerError ); encNonceStr = (char *) malloc( nonceLength + RC5_32_BLOCK + 1 ); if ( encNonceStr == NULL ) throw( (SInt32)eMemoryError ); memcpy( encNonceStr, encryptedStr + 4 + randomLength + 4, RC5_32_BLOCK ); // make the composite session key MD5_Init( &ctx ); MD5_Update( &ctx, decryptedStr, strlen(decryptedStr) ); MD5_Update( &ctx, bnStr, nonceLen ); MD5_Final( md5Result, &ctx ); RC5_32_set_key( &inContext->rc5Key, MD5_DIGEST_LENGTH, md5Result, RC5_16_ROUNDS ); // decrypt the server's nonce RC5_32_ecb_encrypt( (unsigned char *)encNonceStr, (unsigned char *)buf, &inContext->rc5Key, RC5_DECRYPT ); buf[nonceLength] = '\0'; // make rc5-encrypted nonce+1 nonceLen = strlen( buf ); for (int idx = 0; idx < nonceLen; idx++ ) buf[idx]++; // include the 0-terminator nonceLen++; // encrypt for (int idx = 0; idx < nonceLen; idx += RC5_32_BLOCK ) RC5_32_ecb_encrypt( (unsigned char *)buf + idx, (unsigned char *)encNonceStr + idx, &inContext->rc5Key, RC5_ENCRYPT ); // send padded nonceLen = (nonceLen/8)*8 + 8; if ( ConvertBinaryTo64( encNonceStr, nonceLen, base64Buf ) == SASL_OK ) { // send it serverResult = SendFlushReadWithMutex( inContext, "SYNC SESSIONKEYV", base64Buf, NULL, buf, sizeof(buf) ); if ( serverResult.err == kAuthUserNotAuthenticated && serverResult.type == kPolicyError ) siResult = eDSNotAuthorized; else siResult = PWSErrToDirServiceError( serverResult ); } } catch( SInt32 err ) { siResult = err; } catch (...) { siResult = eDSAuthServerError; } gPWSConnMutex->Signal(); if ( bnStr != NULL ) free( bnStr ); if ( encryptedStr != NULL ) free( encryptedStr ); if ( decryptedStr != NULL ) free( decryptedStr ); if ( encNonceStr != NULL ) free( encNonceStr ); if ( authFile != NULL ) { delete authFile; } if ( siResult != eDSNoErr ) DEBUGLOG( "CPSPlugin::SetupSecureSyncSession returning %l", siResult ); return siResult; } // --------------------------------------------------------------------------- // * SecureSyncSessionIsSetup // --------------------------------------------------------------------------- bool CPSPlugIn::SecureSyncSessionIsSetup ( sPSContextData *inContext ) { RC5_32_KEY zeroKey; bzero( &zeroKey, sizeof(RC5_32_KEY) ); return ( memcmp( &zeroKey, &inContext->rc5Key, sizeof(RC5_32_KEY) ) != 0 ); } // --------------------------------------------------------------------------- // * MakeContextData // --------------------------------------------------------------------------- sPSContextData* CPSPlugIn::MakeContextData ( void ) { sPSContextData *pOut = NULL; SInt32 siResult = eDSNoErr; pOut = (sPSContextData *) calloc(1, sizeof(sPSContextData)); if ( pOut != NULL ) { //do nothing with return here since we know this is new //and we did a calloc above siResult = CleanContextData(pOut); } return( pOut ); } // MakeContextData // --------------------------------------------------------------------------- // * CleanContextData // --------------------------------------------------------------------------- SInt32 CPSPlugIn::CleanContextData ( sPSContextData *inContext ) { SInt32 siResult = eDSNoErr; if ( inContext == NULL ) { DEBUGLOG( "CPSPlugIn::CleanContextData eDSBadContextData"); siResult = eDSBadContextData; } else { sasl_conn_t *lconn = NULL; // although CleanContextData() does not use the passwordserver connection directly, // it needs to block the connection mutex during clean-up. gPWSConnMutex->Wait(); lconn = inContext->conn; inContext->conn = NULL; if (inContext->psName != NULL) { free( inContext->psName ); inContext->psName = NULL; } inContext->offset = 0; EndServerSession( inContext ); if (inContext->rsaPublicKeyStr != NULL) { free(inContext->rsaPublicKeyStr); inContext->rsaPublicKeyStr = NULL; } if (inContext->rsaPublicKey != NULL) { key_free(inContext->rsaPublicKey); inContext->rsaPublicKey = NULL; } if (inContext->mech != NULL) { free(inContext->mech); inContext->mech = NULL; } inContext->mechCount = 0; bzero(inContext->last.username, sizeof(inContext->last.username)); if (inContext->last.password != NULL) { bzero(inContext->last.password, inContext->last.passwordLen); free(inContext->last.password); inContext->last.password = NULL; } inContext->last.passwordLen = 0; inContext->last.successfulAuth = false; bzero(inContext->nao.username, sizeof(inContext->nao.username)); if (inContext->nao.password != NULL) { bzero(inContext->nao.password, inContext->nao.passwordLen); free(inContext->nao.password); inContext->nao.password = NULL; } inContext->nao.passwordLen = 0; inContext->nao.successfulAuth = false; if ( inContext->replicaFile != nil ) { [(ReplicaFile *)inContext->replicaFile free]; inContext->replicaFile = nil; } DSCFRelease( inContext->serverList ); if ( inContext->syncFilePath != NULL ) { unlink( inContext->syncFilePath ); free( inContext->syncFilePath ); inContext->syncFilePath = NULL; } bzero( &inContext->rc5Key, sizeof(RC5_32_KEY) ); inContext->madeFirstContact = false; DSFreeString( inContext->serviceInfoStr ); gPWSConnMutex->Signal(); if (lconn != NULL) { // not taking any chances on dead-lock so release the conn mutex before // grabbing the sasl mutex gSASLMutex->Wait(); sasl_dispose(&lconn); gSASLMutex->Signal(); lconn = NULL; } } return( siResult ); } // CleanContextData //------------------------------------------------------------------------------------ // * GetAttributeEntry //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::GetAttributeEntry ( sGetAttributeEntry *inData ) { SInt32 siResult = eDSNoErr; UInt16 usAttrTypeLen = 0; UInt16 usAttrCnt = 0; UInt16 usAttrLen = 0; UInt16 usValueCnt = 0; UInt16 usValueLen = 0; UInt32 i = 0; UInt32 uiIndex = 0; UInt32 uiAttrEntrySize = 0; UInt32 uiOffset = 0; UInt32 uiTotalValueSize = 0; UInt32 offset = 4; UInt32 buffSize = 0; UInt32 buffLen = 0; char *p = NULL; char *pAttrType = NULL; tDataBufferPtr pDataBuff = NULL; tAttributeEntryPtr pAttribInfo = NULL; sPSContextData *pAttrContext = NULL; sPSContextData *pValueContext = NULL; try { Throw_NULL( inData, eMemoryError ); pAttrContext = (sPSContextData *)gPSContextTable->GetItemData( inData->fInAttrListRef ); Throw_NULL( pAttrContext, eDSBadContextData ); uiIndex = inData->fInAttrInfoIndex; if (uiIndex == 0) throw( (SInt32)eDSInvalidIndex ); pDataBuff = inData->fInOutDataBuff; Throw_NULL( pDataBuff, eDSNullDataBuff ); buffSize = pDataBuff->fBufferSize; //buffLen = pDataBuff->fBufferLength; //here we can't use fBufferLength for the buffLen SINCE the buffer is packed at the END of the data block //and the fBufferLength is the overall length of the data for all blocks at the end of the data block //the value ALSO includes the bookkeeping data at the start of the data block //so we need to read it here p = pDataBuff->fBufferData + pAttrContext->offset; offset = pAttrContext->offset; // Do record check, verify that offset is not past end of buffer, etc. if ( 2 > (SInt32)(buffSize - offset) ) throw( (SInt32)eDSInvalidBuffFormat ); // Get the attribute count memcpy( &usAttrCnt, p, 2 ); if (uiIndex > usAttrCnt) throw( (SInt32)eDSInvalidIndex ); // Move 2 bytes p += 2; offset += 2; // Skip to the attribute that we want for ( i = 1; i < uiIndex; i++ ) { // Do record check, verify that offset is not past end of buffer, etc. if (2 > (SInt32)(buffSize - offset) ) throw( (SInt32)eDSInvalidBuffFormat ); // Get the length for the attribute memcpy( &usAttrLen, p, 2 ); // Move the offset past the length word and the length of the data p += 2 + usAttrLen; offset += 2 + usAttrLen; } // Get the attribute offset uiOffset = offset; // Do record check, verify that offset is not past end of buffer, etc. if (2 > (SInt32)(buffSize - offset)) throw( (SInt32)eDSInvalidBuffFormat ); // Get the length for the attribute block memcpy( &usAttrLen, p, 2 ); // Skip past the attribute length p += 2; offset += 2; //set the bufLen to stricter range buffLen = offset + usAttrLen; // Do record check, verify that offset is not past end of buffer, etc. if (2 > (SInt32)(buffLen - offset)) throw( (SInt32)eDSInvalidBuffFormat ); // Get the length for the attribute type memcpy( &usAttrTypeLen, p, 2 ); pAttrType = p + 2; p += 2 + usAttrTypeLen; offset += 2 + usAttrTypeLen; // Do record check, verify that offset is not past end of buffer, etc. if (2 > (SInt32)(buffLen - offset)) throw( (SInt32)eDSInvalidBuffFormat ); // Get number of values for this attribute memcpy( &usValueCnt, p, 2 ); p += 2; offset += 2; for ( i = 0; i < usValueCnt; i++ ) { // Do record check, verify that offset is not past end of buffer, etc. if (2 > (SInt32)(buffLen - offset)) throw( (SInt32)eDSInvalidBuffFormat ); // Get the length for the value memcpy( &usValueLen, p, 2 ); p += 2 + usValueLen; offset += 2 + usValueLen; uiTotalValueSize += usValueLen; } uiAttrEntrySize = sizeof( tAttributeEntry ) + usAttrTypeLen + kBuffPad; pAttribInfo = (tAttributeEntry *)::calloc( 1, uiAttrEntrySize ); pAttribInfo->fAttributeValueCount = usValueCnt; pAttribInfo->fAttributeDataSize = uiTotalValueSize; pAttribInfo->fAttributeValueMaxSize = 512; // <- need to check this xxxxx pAttribInfo->fAttributeSignature.fBufferSize = usAttrTypeLen + kBuffPad; pAttribInfo->fAttributeSignature.fBufferLength = usAttrTypeLen; memcpy( pAttribInfo->fAttributeSignature.fBufferData, pAttrType, usAttrTypeLen ); pValueContext = MakeContextData(); Throw_NULL( pValueContext , eMemoryAllocError ); pValueContext->offset = uiOffset; gPSContextTable->AddItem( inData->fOutAttrValueListRef, pValueContext ); inData->fOutAttrInfoPtr = pAttribInfo; } catch( SInt32 err ) { siResult = err; } return( siResult ); } // GetAttributeEntry //------------------------------------------------------------------------------------ // * GetAttributeValue //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::GetAttributeValue ( sGetAttributeValue *inData ) { SInt32 siResult = eDSNoErr; UInt16 usValueCnt = 0; UInt16 usValueLen = 0; UInt16 usAttrNameLen = 0; UInt32 i = 0; UInt32 uiIndex = 0; UInt32 offset = 0; char *p = NULL; tDataBuffer *pDataBuff = NULL; tAttributeValueEntry *pAttrValue = NULL; sPSContextData *pValueContext = NULL; UInt32 buffSize = 0; UInt32 buffLen = 0; UInt16 attrLen = 0; try { pValueContext = (sPSContextData *)gPSContextTable->GetItemData( inData->fInAttrValueListRef ); Throw_NULL( pValueContext , eDSBadContextData ); uiIndex = inData->fInAttrValueIndex; if (uiIndex == 0) throw( (SInt32)eDSInvalidIndex ); pDataBuff = inData->fInOutDataBuff; Throw_NULL( pDataBuff , eDSNullDataBuff ); buffSize = pDataBuff->fBufferSize; //buffLen = pDataBuff->fBufferLength; //here we can't use fBufferLength for the buffLen SINCE the buffer is packed at the END of the data block //and the fBufferLength is the overall length of the data for all blocks at the end of the data block //the value ALSO includes the bookkeeping data at the start of the data block //so we need to read it here p = pDataBuff->fBufferData + pValueContext->offset; offset = pValueContext->offset; // Do record check, verify that offset is not past end of buffer, etc. if (2 > (SInt32)(buffSize - offset)) throw( (SInt32)eDSInvalidBuffFormat ); // Get the buffer length memcpy( &attrLen, p, 2 ); //now add the offset to the attr length for the value of buffLen to be used to check for buffer overruns //AND add the length of the buffer length var as stored ie. 2 bytes buffLen = attrLen + pValueContext->offset + 2; if (buffLen > buffSize) throw( (SInt32)eDSInvalidBuffFormat ); // Skip past the attribute length p += 2; offset += 2; // Do record check, verify that offset is not past end of buffer, etc. if (2 > (SInt32)(buffLen - offset)) throw( (SInt32)eDSInvalidBuffFormat ); // Get the attribute name length memcpy( &usAttrNameLen, p, 2 ); p += 2 + usAttrNameLen; offset += 2 + usAttrNameLen; // Do record check, verify that offset is not past end of buffer, etc. if (2 > (SInt32)(buffLen - offset)) throw( (SInt32)eDSInvalidBuffFormat ); // Get the value count memcpy( &usValueCnt, p, 2 ); p += 2; offset += 2; if (uiIndex > usValueCnt) throw( (SInt32)eDSInvalidIndex ); // Skip to the value that we want for ( i = 1; i < uiIndex; i++ ) { // Do record check, verify that offset is not past end of buffer, etc. if (2 > (SInt32)(buffLen - offset)) throw( (SInt32)eDSInvalidBuffFormat ); // Get the length for the value memcpy( &usValueLen, p, 2 ); p += 2 + usValueLen; offset += 2 + usValueLen; } // Do record check, verify that offset is not past end of buffer, etc. if (2 > (SInt32)(buffLen - offset)) throw( (SInt32)eDSInvalidBuffFormat ); memcpy( &usValueLen, p, 2 ); p += 2; offset += 2; //if (usValueLen == 0) throw (eDSInvalidBuffFormat ); //if zero is it okay? pAttrValue = (tAttributeValueEntry *)::calloc( 1, sizeof( tAttributeValueEntry ) + usValueLen + kBuffPad ); Throw_NULL(pAttrValue, eMemoryAllocError); pAttrValue->fAttributeValueData.fBufferSize = usValueLen + kBuffPad; pAttrValue->fAttributeValueData.fBufferLength = usValueLen; // Do record check, verify that offset is not past end of buffer, etc. if ( usValueLen > (SInt32)(buffLen - offset) ) throw ( (SInt32)eDSInvalidBuffFormat ); memcpy( pAttrValue->fAttributeValueData.fBufferData, p, usValueLen ); // Set the attribute value ID pAttrValue->fAttributeValueID = CalcCRC( pAttrValue->fAttributeValueData.fBufferData ); inData->fOutAttrValue = pAttrValue; } catch ( SInt32 err ) { siResult = err; } return( siResult ); } // GetAttributeValue // --------------------------------------------------------------------------- // * CalcCRC // --------------------------------------------------------------------------- UInt32 CPSPlugIn::CalcCRC ( char *inStr ) { char *p = inStr; SInt32 siI = 0; SInt32 siStrLen = 0; UInt32 uiCRC = 0xFFFFFFFF; CRCCalc aCRCCalc; if ( inStr != NULL ) { siStrLen = strlen( inStr ); for ( siI = 0; siI < siStrLen; ++siI ) { uiCRC = aCRCCalc.UPDC32( *p, uiCRC ); p++; } } return( uiCRC ); } // CalcCRC //------------------------------------------------------------------------------------ // * GetDirNodeInfo //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::GetDirNodeInfo ( sGetDirNodeInfo *inData ) { SInt32 siResult = eDSNoErr; UInt32 uiOffset = 0; UInt32 uiCntr = 1; UInt32 uiAttrCnt = 0; CAttributeList *inAttrList = NULL; char *pAttrName = NULL; char *pData = NULL; sPSContextData *pContext = NULL; sPSContextData *pAttrContext = NULL; CBuff outBuff; CDataBuff *aRecData = NULL; CDataBuff *aAttrData = NULL; CDataBuff *aTmpData = NULL; bool nodeNameIsID = false; // Can extract here the following: // kDSAttributesAll // kDSNAttrNodePath // kDS1AttrReadOnlyNode // kDSNAttrAuthMethod //KW need to add mappings info next try { Throw_NULL( inData , eMemoryError ); pContext = (sPSContextData *)gPSContextTable->GetItemData( inData->fInNodeRef ); Throw_NULL( pContext , eDSBadContextData ); inAttrList = new CAttributeList( inData->fInDirNodeInfoTypeList ); Throw_NULL( inAttrList, eDSNullNodeInfoTypeList ); if (inAttrList->GetCount() == 0) throw( (SInt32)eDSEmptyNodeInfoTypeList ); siResult = outBuff.Initialize( inData->fOutDataBuff, true ); if ( siResult != eDSNoErr ) throw( siResult ); siResult = outBuff.SetBuffType( 'Gdni' ); //can't use 'StdB' since a tRecordEntry is not returned if ( siResult != eDSNoErr ) throw( siResult ); aRecData = new CDataBuff(); Throw_NULL( aRecData , eMemoryError ); aAttrData = new CDataBuff(); Throw_NULL( aAttrData , eMemoryError ); aTmpData = new CDataBuff(); Throw_NULL( aTmpData , eMemoryError ); // contact the password server to get the SASL list nodeNameIsID = (pContext->serverProvidedFromNode.id[0] != '\0'); siResult = HandleFirstContact( pContext, nodeNameIsID ? NULL : pContext->serverProvidedFromNode.ip, nodeNameIsID ? pContext->serverProvidedFromNode.id : NULL ); if ( siResult == eDSNoErr ) pContext->madeFirstContact = true; // Even if the server is unreachable, we can report what we know siResult = eDSNoErr; // Set the record name and type aRecData->AppendShort( strlen( "dsAttrTypeStandard:DirectoryNodeInfo" ) ); aRecData->AppendString( (char *)"dsAttrTypeStandard:DirectoryNodeInfo" ); aRecData->AppendShort( strlen( "DirectoryNodeInfo" ) ); aRecData->AppendString( (char *)"DirectoryNodeInfo" ); while ( inAttrList->GetAttribute( uiCntr++, &pAttrName ) == eDSNoErr ) { //package up all the dir node attributes dependant upon what was asked for if ((strcmp( pAttrName, kDSAttributesAll ) == 0) || (strcmp( pAttrName, kDSNAttrNodePath ) == 0) ) { aTmpData->Clear(); uiAttrCnt++; // Append the attribute name aTmpData->AppendShort( strlen( kDSNAttrNodePath ) ); aTmpData->AppendString( kDSNAttrNodePath ); if ( inData->fInAttrInfoOnly == false ) { // Attribute value count always two aTmpData->AppendShort( 2 ); // Append attribute value aTmpData->AppendShort( strlen( "PasswordServer" ) ); aTmpData->AppendString( (char *)"PasswordServer" ); char *tmpStr = NULL; if (pContext->psName != NULL) { tmpStr = new char[1+strlen(pContext->psName)]; ::strcpy( tmpStr, pContext->psName ); } else { tmpStr = new char[1+strlen("Unknown Node Location")]; ::strcpy( tmpStr, "Unknown Node Location" ); } // Append attribute value aTmpData->AppendShort( strlen( tmpStr ) ); aTmpData->AppendString( tmpStr ); delete( tmpStr ); } // fInAttrInfoOnly is false // Add the attribute length aAttrData->AppendShort( aTmpData->GetLength() ); aAttrData->AppendBlock( aTmpData->GetData(), aTmpData->GetLength() ); aTmpData->Clear(); } // kDSAttributesAll or kDSNAttrNodePath if ( (strcmp( pAttrName, kDSAttributesAll ) == 0) || (strcmp( pAttrName, kDS1AttrReadOnlyNode ) == 0) ) { aTmpData->Clear(); uiAttrCnt++; // Append the attribute name aTmpData->AppendShort( strlen( kDS1AttrReadOnlyNode ) ); aTmpData->AppendString( kDS1AttrReadOnlyNode ); if ( inData->fInAttrInfoOnly == false ) { // Attribute value count aTmpData->AppendShort( 1 ); //possible for a node to be ReadOnly, ReadWrite, WriteOnly //note that ReadWrite does not imply fully readable or writable // Add the root node as an attribute value aTmpData->AppendShort( strlen( "ReadOnly" ) ); aTmpData->AppendString( "ReadOnly" ); } // Add the attribute length and data aAttrData->AppendShort( aTmpData->GetLength() ); aAttrData->AppendBlock( aTmpData->GetData(), aTmpData->GetLength() ); // Clear the temp block aTmpData->Clear(); } if ((strcmp( pAttrName, kDSAttributesAll ) == 0) || (strcmp( pAttrName, kDSNAttrAuthMethod ) == 0) ) { aTmpData->Clear(); uiAttrCnt++; // Append the attribute name aTmpData->AppendShort( strlen( kDSNAttrAuthMethod ) ); aTmpData->AppendString( kDSNAttrAuthMethod ); if ( inData->fInAttrInfoOnly == false ) { int idx, mechCount; char dsTypeStr[256]; // get the count for the mechs that get returned mechCount = this->GetSASLMechCount( pContext ); // Attribute value count aTmpData->AppendShort( 7 + mechCount ); aTmpData->AppendShort( strlen( kDSStdAuthClearText ) ); aTmpData->AppendString( kDSStdAuthClearText ); aTmpData->AppendShort( strlen( kDSStdAuthSetPasswd ) ); aTmpData->AppendString( kDSStdAuthSetPasswd ); aTmpData->AppendShort( strlen( kDSStdAuthChangePasswd ) ); aTmpData->AppendString( kDSStdAuthChangePasswd ); aTmpData->AppendShort( strlen( kDSStdAuthSetPasswdAsRoot ) ); aTmpData->AppendString( kDSStdAuthSetPasswdAsRoot ); aTmpData->AppendShort( strlen( kDSStdAuthNodeNativeClearTextOK ) ); aTmpData->AppendString( kDSStdAuthNodeNativeClearTextOK ); aTmpData->AppendShort( strlen( kDSStdAuthNodeNativeNoClearText ) ); aTmpData->AppendString( kDSStdAuthNodeNativeNoClearText ); // password server supports kDSStdAuth2WayRandomChangePasswd // with or without the plug-in aTmpData->AppendShort( strlen( kDSStdAuth2WayRandomChangePasswd ) ); aTmpData->AppendString( kDSStdAuth2WayRandomChangePasswd ); for ( idx = 0; idx < pContext->mechCount; idx++ ) { GetAuthMethodFromSASLName( pContext->mech[idx].method, dsTypeStr ); if ( dsTypeStr[0] != '\0' ) { // Append first attribute value aTmpData->AppendShort( strlen( dsTypeStr ) ); aTmpData->AppendString( dsTypeStr ); } } } // fInAttrInfoOnly is false // Add the attribute length aAttrData->AppendShort( aTmpData->GetLength() ); aAttrData->AppendBlock( aTmpData->GetData(), aTmpData->GetLength() ); aTmpData->Clear(); } // kDSAttributesAll or kDSNAttrAuthMethod } // while aRecData->AppendShort( uiAttrCnt ); if (uiAttrCnt > 0) { aRecData->AppendBlock( aAttrData->GetData(), aAttrData->GetLength() ); } outBuff.AddData( aRecData->GetData(), aRecData->GetLength() ); inData->fOutAttrInfoCount = uiAttrCnt; pData = outBuff.GetDataBlock( 1, &uiOffset ); if ( pData != NULL ) { pAttrContext = MakeContextData(); Throw_NULL( pAttrContext , eMemoryAllocError ); //add to the offset for the attr list the length of the GetDirNodeInfo fixed record labels // record length = 4 // aRecData->AppendShort( strlen( "dsAttrTypeStandard:DirectoryNodeInfo" ) ); = 2 // aRecData->AppendString( "dsAttrTypeStandard:DirectoryNodeInfo" ); = 36 // aRecData->AppendShort( strlen( "DirectoryNodeInfo" ) ); = 2 // aRecData->AppendString( "DirectoryNodeInfo" ); = 17 // total adjustment = 4 + 2 + 36 + 2 + 17 = 61 pAttrContext->offset = uiOffset + 61; gPSContextTable->AddItem( inData->fOutAttrListRef, pAttrContext ); } } catch( SInt32 err ) { siResult = err; } if ( inAttrList != NULL ) { delete( inAttrList ); inAttrList = NULL; } if ( aRecData != NULL ) { delete( aRecData ); aRecData = NULL; } if ( aAttrData != NULL ) { delete( aAttrData ); aAttrData = NULL; } if ( aTmpData != NULL ) { delete( aTmpData ); aTmpData = NULL; } return( siResult ); } // GetDirNodeInfo //------------------------------------------------------------------------------------ // * CloseAttributeList //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::CloseAttributeList ( sCloseAttributeList *inData ) { SInt32 siResult = eDSNoErr; sPSContextData *pContext = NULL; pContext = (sPSContextData *)gPSContextTable->GetItemData( inData->fInAttributeListRef ); if ( pContext != NULL ) { //only "offset" should have been used in the Context gPSContextTable->RemoveItem( inData->fInAttributeListRef ); } else { siResult = eDSInvalidAttrListRef; } return( siResult ); } // CloseAttributeList //------------------------------------------------------------------------------------ // * CloseAttributeValueList //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::CloseAttributeValueList ( sCloseAttributeValueList *inData ) { SInt32 siResult = eDSNoErr; sPSContextData *pContext = NULL; pContext = (sPSContextData *)gPSContextTable->GetItemData( inData->fInAttributeValueListRef ); if ( pContext != NULL ) { //only "offset" should have been used in the Context gPSContextTable->RemoveItem( inData->fInAttributeValueListRef ); } else { siResult = eDSInvalidAttrValueRef; } return( siResult ); } // CloseAttributeValueList // --------------------------------------------------------------------------- // * GetStringFromAuthBuffer // retrieve a string from a standard auth buffer // buffer format should be 4 byte length followed by username, then optional // additional data after. Buffer length must be at least 5 (length + 1 character name) // --------------------------------------------------------------------------- SInt32 CPSPlugIn::GetStringFromAuthBuffer(tDataBufferPtr inAuthData, int stringNum, char **outString) { tDataListPtr dataList = dsAuthBufferGetDataListAllocPriv(inAuthData); if (dataList != NULL) { *outString = dsDataListGetNodeStringPriv(dataList, stringNum); // this allocates a copy of the string dsDataListDeallocatePriv(dataList); free(dataList); dataList = NULL; return eDSNoErr; } return eDSInvalidBuffFormat; } // --------------------------------------------------------------------------- // * Get2StringsFromAuthBuffer // retrieve the first 2 strings from a standard auth buffer // buffer format should be 4 byte length followed by username, then optional // additional data after. Buffer length must be at least 5 (length + 1 character name) // --------------------------------------------------------------------------- SInt32 CPSPlugIn::Get2StringsFromAuthBuffer(tDataBufferPtr inAuthData, char **outString1, char **outString2) { SInt32 result = GetStringFromAuthBuffer( inAuthData, 1, outString1 ); if ( result == eDSNoErr ) result = GetStringFromAuthBuffer( inAuthData, 2, outString2 ); return result; } // --------------------------------------------------------------------------- // * GetDataFromAuthBuffer // retrieve data from a standard auth buffer // buffer format should be 4 byte length followed by username, then optional // additional data after. Buffer length must be at least 5 (length + 1 character name) // --------------------------------------------------------------------------- SInt32 CPSPlugIn::GetDataFromAuthBuffer(tDataBufferPtr inAuthData, int nodeNum, unsigned char **outData, UInt32 *outLen) { tDataNodePtr pDataNode; tDirStatus status; *outData = NULL; *outLen = 0; tDataListPtr dataList = dsAuthBufferGetDataListAllocPriv(inAuthData); if (dataList != NULL) { status = dsDataListGetNodePriv(dataList, nodeNum, &pDataNode); if ( status != eDSNoErr ) return status; if ( pDataNode->fBufferLength > 0 ) { *outData = (unsigned char *) malloc(pDataNode->fBufferLength + RC5_32_BLOCK); if ( ! (*outData) ) return eMemoryAllocError; memcpy(*outData, ((tDataBufferPriv*)pDataNode)->fBufferData, pDataNode->fBufferLength); *outLen = pDataNode->fBufferLength; } dsDataListDeallocatePriv(dataList); free(dataList); dataList = NULL; return eDSNoErr; } return eDSInvalidBuffFormat; } //------------------------------------------------------------------------------------ // * UpdateCachedPasswordOnChange //------------------------------------------------------------------------------------ void CPSPlugIn::UpdateCachedPasswordOnChange( sPSContextData *inContext, const char *inChangedUser, const char *inPassword, long inPasswordLen ) { if ( inContext == NULL || inChangedUser == NULL || inPassword == NULL ) return; if ( strncmp( inContext->last.username, inChangedUser, 34 ) == 0 ) { if ( inContext->last.password != NULL ) { free( inContext->last.password ); inContext->last.password = NULL; inContext->last.passwordLen = 0; } inContext->last.password = (char *) malloc( inPasswordLen + 1 ); if ( inContext->last.password != NULL ) { memcpy( inContext->last.password, inPassword, inPasswordLen ); inContext->last.password[inPasswordLen] = '\0'; inContext->last.passwordLen = inPasswordLen; } } if ( strncmp( inContext->nao.username, inChangedUser, 34 ) == 0 ) { if ( inContext->nao.password != NULL ) { free( inContext->nao.password ); inContext->nao.password = NULL; inContext->nao.passwordLen = 0; } inContext->nao.password = (char *) malloc( inPasswordLen + 1 ); if ( inContext->nao.password != NULL ) { memcpy( inContext->nao.password, inPassword, inPasswordLen ); inContext->nao.password[inPasswordLen] = '\0'; inContext->nao.passwordLen = inPasswordLen; } } } //------------------------------------------------------------------------------------ // * GetSASLMechCount //------------------------------------------------------------------------------------ int CPSPlugIn::GetSASLMechCount( sPSContextData *inContext ) { int idx; int mechCount = 0; char dsTypeStr[256]; for ( idx = 0; idx < inContext->mechCount; idx++ ) { GetAuthMethodFromSASLName( inContext->mech[idx].method, dsTypeStr ); if ( dsTypeStr[0] != '\0' ) mechCount++; } return mechCount; } //------------------------------------------------------------------------------------ // * SASLInit //------------------------------------------------------------------------------------ void CPSPlugIn::SASLInit ( void ) { if ( !fCalledSASLInit ) { gPWSConnMutex->Wait(); if ( !fCalledSASLInit ) { LDAP *ldp = NULL; if ( ldap_initialize( &ldp, NULL ) == LDAP_SUCCESS ) ldap_unbind_ext( ldp, NULL, NULL ); fCalledSASLInit = true; } gPWSConnMutex->Signal(); } } //------------------------------------------------------------------------------------ // * DoAuthentication //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::DoAuthentication ( sDoDirNodeAuth *inData ) { inData->fResult = this->DoAuthenticationSetup( inData ); DEBUGLOG( "CPSPlugIn::DoAuthentication returning %l", inData->fResult ); return inData->fResult; } //------------------------------------------------------------------------------------ // * DoAuthenticationSetup //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::DoAuthenticationSetup ( sDoDirNodeAuth *inData ) { SInt32 siResult = eDSAuthFailed; SInt32 siResult2 = eDSNoErr; char *rsaKeyPtr = NULL; Boolean bNeedsRSAValidation = true; Boolean bHasValidAuth = false; bool bMethodCanSetPassword = false; char hexHash[34] = {0,}; CAuthParams pb; SASLInit(); pb.pContext = (sPSContextData *)gPSContextTable->GetItemData( inData->fInNodeRef ); if ( pb.pContext == NULL ) return eDSBadContextData; siResult = GetAuthMethodConstant( pb.pContext, inData->fInAuthMethod, &pb.uiAuthMethod, pb.saslMechNameStr ); DEBUGLOG( "GetAuthMethodConstant siResult=%l, uiAuthMethod=%l, mech=%s", siResult, pb.uiAuthMethod,pb.saslMechNameStr); if ( siResult != eDSNoErr ) return( siResult ); // auto-downgrade kAuthNTSessionKey to kAuthSMB_NT_Key if there is no authenticator if ( pb.uiAuthMethod == kAuthNTSessionKey || pb.uiAuthMethod == kAuthNTLMv2WithSessionKey ) { if ( GetStringFromAuthBuffer(inData->fInAuthStepData, 4, &pb.paramStr) != eDSNoErr || DSIsStringEmpty(pb.paramStr) ) pb.uiAuthMethod = (pb.uiAuthMethod == kAuthNTSessionKey) ? kAuthSMB_NT_Key : kAuthNTLMv2; if ( pb.paramStr != NULL ) DSFreeString( pb.paramStr ); } if ( inData->fIOContinueData == 0 ) { siResult = UnpackUsernameAndPassword( pb.pContext, pb.uiAuthMethod, inData->fInAuthStepData, &pb.userName, &pb.password, &pb.passwordLen, &pb.challenge ); if ( siResult != eDSNoErr ) return( siResult ); } else { if ( gContinue->VerifyItem( (void *)inData->fIOContinueData ) == false ) return( (SInt32)eDSInvalidContinueData ); } // get a pointer to the rsa public key if ( pb.userName != NULL ) { rsaKeyPtr = strchr( pb.userName, ',' ); if ( rsaKeyPtr != NULL ) rsaKeyPtr++; else syslog(LOG_INFO, "WARN: got user ID with no RSA key!" ); } // make sure there is a server to contact if ( pb.pContext->madeFirstContact ) { // make sure there is a connection if ( Connected(pb.pContext) ) { bNeedsRSAValidation = false; } else { // close out anything stale EndServerSession( pb.pContext ); gPWSConnMutex->Wait(); siResult = ConnectToServer( pb.pContext ); gPWSConnMutex->Signal(); if ( siResult != 0 ) return( siResult ); } } else { if ( pb.userName != NULL && rsaKeyPtr != NULL ) { pwsf_CalcServerUniqueID( rsaKeyPtr, hexHash ); DEBUGLOG( "hexHash=%s", hexHash ); siResult = HandleFirstContact( pb.pContext, NULL, hexHash, rsaKeyPtr ); } else { siResult = HandleFirstContact( pb.pContext, NULL, NULL ); } if ( siResult != eDSNoErr ) return( siResult ); if ( ! Connected(pb.pContext) ) { // close out anything stale EndServerSession( pb.pContext ); return( (SInt32)eDSAuthServerError ); } pb.pContext->madeFirstContact = true; } // if bNeedsRSAValidation == true, then this is a new connection and the authentication // needs to be re-established. if ( ! bNeedsRSAValidation ) { siResult = UseCurrentAuthenticationIfPossible( pb.pContext, pb.userName, pb.uiAuthMethod, &bHasValidAuth ); if ( siResult != eDSNoErr ) return( siResult ); } // do not authenticate for auth methods that do not need SASL authentication if ( !bHasValidAuth && siResult == eDSNoErr && RequiresSASLAuthentication( pb.uiAuthMethod ) ) { siResult = GetAuthMethodSASLName( pb.pContext, pb.uiAuthMethod, inData->fInDirNodeAuthOnlyFlag, pb.saslMechNameStr, &bMethodCanSetPassword ); DEBUGLOG( "GetAuthMethodSASLName siResult=%l, mech=%s", siResult, pb.saslMechNameStr ); if ( siResult != eDSNoErr ) return( siResult ); siResult = this->SetServiceInfo( inData, pb.pContext ); if ( siResult != eDSNoErr ) { DEBUGLOG( "SetServiceInfo siResult=%l", siResult ); return( siResult ); } if ( rsaKeyPtr != NULL && bNeedsRSAValidation && !pb.pContext->isUNIXDomainSocket ) siResult = DoRSAValidation( pb.pContext, rsaKeyPtr ); if ( siResult == eDSNoErr ) { pb.pContext->last.successfulAuth = false; if ( inData->fIOContinueData == 0 ) { pb.pContinue = (sPSContinueData *)calloc( 1, sizeof( sPSContinueData ) ); if ( pb.pContinue == NULL ) return eMemoryError; gContinue->AddItem( pb.pContinue, inData->fInNodeRef ); inData->fIOContinueData = pb.pContinue; pb.pContinue->fAuthPass = 0; pb.pContinue->fData = NULL; pb.pContinue->fDataLen = 0; pb.pContinue->fSASLSecret = NULL; } gPWSConnMutex->Wait(); switch ( pb.uiAuthMethod ) { case kAuthPPS: siResult = DoSASLPPSAuth( pb.pContext, pb.userName, pb.password, pb.passwordLen, inData ); break; case kAuth2WayRandom: siResult = DoSASLTwoWayRandAuth( pb.pContext, pb.userName, pb.saslMechNameStr, inData ); break; default: siResult = DoSASLAuth( pb.pContext, pb.userName, pb.password, pb.passwordLen, pb.challenge, pb.saslMechNameStr, inData, &pb.stepData ); } gPWSConnMutex->Signal(); if ( siResult == eDSNoErr && pb.uiAuthMethod != kAuth2WayRandom && pb.uiAuthMethod != kAuthPPS ) { pb.pContext->last.successfulAuth = true; pb.pContext->last.methodCanSetPassword = bMethodCanSetPassword; // If authOnly == false, copy the username and password for later use // with SetPasswordAsRoot. if ( inData->fInDirNodeAuthOnlyFlag == false || pb.uiAuthMethod == kAuthNativeRetainCredential ) { memcpy( pb.pContext->nao.username, pb.pContext->last.username, kMaxUserNameLength + 1 ); if ( pb.pContext->nao.password != NULL ) { bzero( pb.pContext->nao.password, pb.pContext->nao.passwordLen ); free( pb.pContext->nao.password ); pb.pContext->nao.password = NULL; pb.pContext->nao.passwordLen = 0; } pb.pContext->nao.password = (char *) malloc( pb.pContext->last.passwordLen + 1 ); if ( pb.pContext->nao.password == NULL ) return eMemoryError; memcpy( pb.pContext->nao.password, pb.pContext->last.password, pb.pContext->last.passwordLen ); pb.pContext->nao.password[pb.pContext->last.passwordLen] = '\0'; pb.pContext->nao.passwordLen = pb.pContext->last.passwordLen; pb.pContext->nao.successfulAuth = true; pb.pContext->nao.methodCanSetPassword = pb.pContext->last.methodCanSetPassword; } } } } // For kAuthNTSessionKey and kAuthNTLMv2WithSessionKey, the SASL auth is for // the trust account. We only want to verify with the master if there's a // policy violation for the user account. The user's error is returned // by DoAuthenticationResponse(). if ( siResult == eDSNoErr ) { siResult2 = this->DoAuthenticationResponse( inData, pb ); if ( siResult2 != eDSNoErr ) DEBUGLOG( "CPSPlugIn::DoAuthenticationResponse returned %l", siResult2 ); } else if ( pb.uiAuthMethod == kAuthNTSessionKey || pb.uiAuthMethod == kAuthNTLMv2WithSessionKey ) { siResult = this->DoAuthenticationResponse( inData, pb ); if ( siResult != eDSNoErr ) DEBUGLOG( "CPSPlugIn::DoAuthenticationResponse returned %l", siResult ); } if ( siResult == eDSNoErr && (pb.uiAuthMethod != kAuthSetUserName && pb.uiAuthMethod != kAuthSetPasswd) ) { if ( siResult2 != eDSNoErr ) siResult = siResult2; } else { // for policy violations, confirm with the master if we're talking // to a replica if ( siResult == eDSNoErr && (pb.uiAuthMethod == kAuthSetUserName || pb.uiAuthMethod == kAuthSetPasswd) ) siResult = siResult2; switch ( siResult ) { case eDSAuthFailed: if ( pb.uiAuthMethod != kAuthSetUserName && pb.uiAuthMethod != kAuthSetPasswd ) break; case eDSAuthPasswordExpired: case eDSAuthAccountDisabled: case eDSAuthAccountExpired: case eDSAuthAccountInactive: case eDSAuthInvalidLogonHours: if ( !pb.pContext->isUNIXDomainSocket && (!DSIsStringEmpty(pb.pContext->master.ip) || !DSIsStringEmpty(pb.pContext->serverProvidedFromNode.ip)) && strcmp(pb.pContext->master.ip, pb.pContext->psName) != 0 ) { // close the current session EndServerSession( pb.pContext, kSendQuit ); // create a context for the master sPSContextData contextCopy; bzero( &contextCopy, sizeof(sPSContextData) ); contextCopy.fd = -1; contextCopy.replicaFile = pb.pContext->replicaFile; if ( DSIsStringEmpty(pb.pContext->master.ip) ) memcpy( &contextCopy.serverProvidedFromNode, &pb.pContext->serverProvidedFromNode, sizeof(sPSServerEntry) ); else memcpy( &contextCopy.serverProvidedFromNode, &pb.pContext->master, sizeof(sPSServerEntry) ); contextCopy.providedNodeOnlyOrFail = true; // open a connection to the master if ( pb.userName != NULL && rsaKeyPtr != NULL ) { siResult2 = HandleFirstContact( &contextCopy, NULL, hexHash, rsaKeyPtr ); } else { siResult2 = HandleFirstContact( &contextCopy, NULL, NULL ); } // doesn't belong to us contextCopy.replicaFile = NULL; // if we can't reach the master, return the original error if ( siResult2 == eDSNoErr ) { if ( ! Connected(&contextCopy) ) { // close out anything stale CleanContextData( &contextCopy ); return( siResult ); } // ask the OD master for a second opinion if ( rsaKeyPtr != NULL && !contextCopy.isUNIXDomainSocket ) { siResult2 = DoRSAValidation( &contextCopy, rsaKeyPtr ); if ( siResult2 == eDSNoErr ) { pb.pContext->last.successfulAuth = false; if ( inData->fIOContinueData != 0 ) { gContinue->RemoveItem( inData->fIOContinueData ); inData->fIOContinueData = 0; } pb.pContinue = (sPSContinueData *)calloc( 1, sizeof( sPSContinueData ) ); if ( pb.pContinue == NULL ) { CleanContextData( &contextCopy ); return eMemoryError; } gContinue->AddItem( pb.pContinue, inData->fInNodeRef ); inData->fIOContinueData = pb.pContinue; pb.pContinue->fAuthPass = 0; pb.pContinue->fData = NULL; pb.pContinue->fDataLen = 0; pb.pContinue->fSASLSecret = NULL; gPWSConnMutex->Wait(); switch ( pb.uiAuthMethod ) { case kAuthPPS: siResult2 = DoSASLPPSAuth( &contextCopy, pb.userName, pb.password, pb.passwordLen, inData ); break; case kAuth2WayRandom: siResult2 = DoSASLTwoWayRandAuth( &contextCopy, pb.userName, pb.saslMechNameStr, inData ); break; default: siResult2 = DoSASLAuth( &contextCopy, pb.userName, pb.password, pb.passwordLen, pb.challenge, pb.saslMechNameStr, inData, &pb.stepData ); } gPWSConnMutex->Signal(); if ( siResult2 == eDSNoErr ) { sPSContextData *pSavedContext = pb.pContext; pb.pContext = &contextCopy; siResult = this->DoAuthenticationResponse( inData, pb ); pb.pContext = pSavedContext; if ( siResult != eDSNoErr ) DEBUGLOG( "CPSPlugIn::DoAuthenticationResponse from redirect to master returned %l", siResult ); } } } } else { DEBUGLOG( "CPSPlugIn::DoAuthenticationResponse redirect to master returned %l", siResult2 ); } CleanContextData( &contextCopy ); } break; default: break; } if ( siResult == eDSAuthNewPasswordRequired ) { switch( pb.uiAuthMethod ) { case kAuthSetPasswd: case kAuthSetPasswdAsRoot: case kAuthChangePasswd: case kAuthMSLMCHAP2ChangePasswd: siResult = this->DoAuthenticationResponse( inData, pb ); if ( siResult != eDSNoErr ) DEBUGLOG( "CPSPlugIn::DoAuthenticationResponse returned %l", siResult ); break; } } } if ( fOpenNodeCount >= kMaxOpenNodesBeforeQuickClose ) { if ( inData->fInDirNodeAuthOnlyFlag == true ) { if ( pb.uiAuthMethod == kAuthClearText || pb.uiAuthMethod == kAuthNativeClearTextOK || pb.uiAuthMethod == kAuthNativeNoClearText || pb.uiAuthMethod == kAuthAPOP || pb.uiAuthMethod == kAuthSMB_NT_Key || pb.uiAuthMethod == kAuthSMB_LM_Key || pb.uiAuthMethod == kAuthDIGEST_MD5 || pb.uiAuthMethod == kAuthCRAM_MD5 || pb.uiAuthMethod == kAuthMSCHAP2 || pb.uiAuthMethod == kAuthNTLMv2 ) { EndServerSession( pb.pContext, kSendQuit ); } } gPSContextTable->DoOnAllItems( CPSPlugIn::ReleaseCloseWaitConnections ); } return( siResult ); } // DoAuthentication //------------------------------------------------------------------------------------ // * DoAuthenticationResponse //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::DoAuthenticationResponse( sDoDirNodeAuth *inData, CAuthParams &pb ) { SInt32 siResult = eDSNoErr; tDataBufferPtr outBuf = inData->fOutAuthStepDataResponse; int saslResult = SASL_OK; const char *encodedStr = NULL; unsigned int encodedStrLen = 0; char encoded64Str[kOneKBuffer]; char buf[2 * kOneKBuffer]; PWServerError result; switch( pb.uiAuthMethod ) { case kAuthDIGEST_MD5: case kAuthDIGEST_MD5_Reauth: case kAuthMSCHAP2: #pragma mark kAuthDigestMD5 #pragma mark kAuthMSCHAP2 if ( pb.stepData != NULL ) siResult = PackStepBuffer( pb.stepData, false, NULL, NULL, NULL, outBuf ); break; case kAuthNTLMv2SessionKey: case kAuthNTLMv2WithSessionKey: #pragma mark kAuthNTLMv2SessionKey siResult = DoAuthMethodNTLMv2SessionKey( siResult, pb.uiAuthMethod, inData, pb.pContext, outBuf ); break; case kAuthSetPasswd: // buffer format is: // len1 username // len2 user's new password // len3 authenticatorID // len4 authenticatorPW case kAuthSetPasswdAsRoot: // buffer format is: // len1 username // len2 user's new password #pragma mark kAuthSetPasswd siResult = Get2StringsFromAuthBuffer( inData->fInAuthStepData, &pb.userIDToSet, &pb.paramStr ); if ( siResult == eDSNoErr ) StripRSAKey( pb.userIDToSet ); if ( siResult == eDSNoErr ) { if ( pb.paramStr == NULL ) return( (SInt32)eDSInvalidBuffFormat ); if (strlen(pb.paramStr) > kChangePassPaddedBufferSize ) return( (SInt32)eDSAuthParameterError ); // special-case for an empty password. DIGEST-MD5 does not // support empty passwords, but it's a DS requirement if ( DSIsStringEmpty(pb.paramStr) ) { free( pb.paramStr ); pb.paramStr = (char *) strdup( kEmptyPasswordAltStr ); } strlcpy(buf, pb.paramStr, sizeof(buf)); gSASLMutex->Wait(); saslResult = sasl_encode(pb.pContext->conn, buf, kChangePassPaddedBufferSize, &encodedStr, &encodedStrLen); gSASLMutex->Signal(); } if ( siResult == eDSNoErr && saslResult == SASL_OK && pb.userIDToSet != NULL ) { if ( ConvertBinaryTo64( encodedStr, encodedStrLen, encoded64Str ) == SASL_OK ) { result = SendFlushReadWithMutex( pb.pContext, "CHANGEPASS", pb.userIDToSet, encoded64Str, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); if ( siResult == eDSNoErr ) UpdateCachedPasswordOnChange( pb.pContext, pb.userIDToSet, pb.paramStr, strlen(pb.paramStr) ); } } else { printf("encode64 failed"); } break; case kAuthSetComputerAcctPasswdAsRoot: #pragma mark kAuthSetComputerAcctPasswdAsRoot siResult = DoAuthMethodSetComputerAccountPassword( inData, pb.pContext, outBuf ); break; case kAuthChangePasswd: #pragma mark kAuthChangePasswd /*! * @defined kDSStdAuthChangePasswd * @discussion Change the password for a user. Does not require prior authentication. * The buffer is packed as follows: * * 4 byte length of username, * username in UTF8 encoding, * 4 byte length of old password, * old password in UTF8 encoding, * 4 byte length of new password, * new password in UTF8 encoding */ siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.paramStr ); if ( siResult == eDSNoErr ) { if ( pb.paramStr == NULL ) return( (SInt32)eDSInvalidBuffFormat ); if ( strlen(pb.paramStr) > kChangePassPaddedBufferSize ) return( (SInt32)eDSAuthParameterError ); // special-case for an empty password. DIGEST-MD5 does not // support empty passwords, but it's a DS requirement if ( DSIsStringEmpty(pb.paramStr) ) { free( pb.paramStr ); pb.paramStr = (char *) strdup( kEmptyPasswordAltStr ); } strlcpy(buf, pb.paramStr, sizeof(buf)); gSASLMutex->Wait(); saslResult = sasl_encode(pb.pContext->conn, buf, kChangePassPaddedBufferSize, &encodedStr, &encodedStrLen); gSASLMutex->Signal(); } if ( siResult == eDSNoErr && saslResult == SASL_OK && pb.paramStr != NULL ) { if ( ConvertBinaryTo64( encodedStr, encodedStrLen, encoded64Str ) == SASL_OK ) { StripRSAKey( pb.paramStr ); result = SendFlushReadWithMutex( pb.pContext, "CHANGEPASS", pb.userName, encoded64Str, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); if ( siResult == eDSNoErr ) UpdateCachedPasswordOnChange( pb.pContext, pb.userName, pb.paramStr, strlen(pb.paramStr) ); } } else { printf("encode64 failed"); } break; case kAuthNewUser: #pragma mark kAuthNewUser // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 user's name // len4 user's initial password siResult = DoAuthMethodNewUser( inData, pb.pContext, false, outBuf ); break; case kAuthNewUserWithPolicy: #pragma mark kAuthNewUserWithPolicy // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 user's name // len4 user's initial password // len5 policy string siResult = DoAuthMethodNewUser( inData, pb.pContext, true, outBuf ); break; case kAuthNewComputer: #pragma mark kAuthNewComputer siResult = DoAuthMethodNewComputer( inData, pb.pContext, outBuf ); break; case kAuthGetPolicy: #pragma mark kAuthGetPolicy // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 AccountID siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.userIDToSet ); if ( siResult == eDSNoErr ) { if ( pb.userIDToSet == NULL ) return( (SInt32)eDSInvalidBuffFormat ); StripRSAKey( pb.userIDToSet ); result = SendFlushReadWithMutex( pb.pContext, "GETPOLICY", pb.userIDToSet, NULL, buf, sizeof(buf) ); if ( result.err != 0 ) return( PWSErrToDirServiceError(result) ); sasl_chop(buf); siResult = PackStepBuffer( buf, true, NULL, NULL, NULL, outBuf ); } break; case kAuthGetEffectivePolicy: #pragma mark kAuthGetEffectivePolicy // buffer format is: // len1 AccountID StripRSAKey( pb.userName ); result = SendFlushReadWithMutex( pb.pContext, "GETPOLICY", pb.userName, "ACTUAL", buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); if ( siResult == eDSNoErr ) { sasl_chop( buf ); siResult = PackStepBuffer( buf, true, NULL, NULL, NULL, outBuf ); } break; case kAuthSetPolicy: #pragma mark kAuthSetPolicy // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 AccountID // len4 PolicyString siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.userIDToSet ); if ( siResult == eDSNoErr ) { StripRSAKey( pb.userIDToSet ); siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 4, &pb.paramStr ); } if ( siResult == eDSNoErr ) { result = SendFlushReadWithMutex( pb.pContext, "SETPOLICY", pb.userIDToSet, pb.paramStr, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); } break; case kAuthSetPolicyAsRoot: #pragma mark kAuthSetPolicyAsRoot // buffer format is: // len1 AccountID // len2 PolicyString siResult = Get2StringsFromAuthBuffer( inData->fInAuthStepData, &pb.userIDToSet, &pb.paramStr ); if ( siResult == eDSNoErr ) { StripRSAKey( pb.userIDToSet ); result = SendFlushReadWithMutex( pb.pContext, "SETPOLICY", pb.userIDToSet, pb.paramStr, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); } break; case kAuthGetGlobalPolicy: #pragma mark kAuthGetGlobalPolicy // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW result = SendFlushReadWithMutex( pb.pContext, "GETGLOBALPOLICY", NULL, NULL, buf, sizeof(buf) ); if ( result.err != 0 ) return( PWSErrToDirServiceError(result) ); sasl_chop( buf ); siResult = PackStepBuffer( buf, true, NULL, NULL, NULL, outBuf ); break; case kAuthSetGlobalPolicy: #pragma mark kAuthSetGlobalPolicy // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 PolicyString siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.paramStr ); if ( siResult == eDSNoErr ) { result = SendFlushReadWithMutex( pb.pContext, "SETGLOBALPOLICY", pb.paramStr, NULL, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); } break; case kAuthGetUserName: #pragma mark kAuthGetUserName // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 AccountID siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.userIDToSet ); if ( siResult == eDSNoErr ) { StripRSAKey( pb.userIDToSet ); result = SendFlushReadWithMutex( pb.pContext, "GETUSERNAME", pb.userIDToSet, NULL, buf, sizeof(buf) ); if ( result.err != 0 ) return( PWSErrToDirServiceError(result) ); sasl_chop( buf ); siResult = PackStepBuffer( buf, true, NULL, NULL, NULL, outBuf ); } break; case kAuthSetUserName: #pragma mark kAuthSetUserName // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 AccountID // len4 NewUserName siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.userIDToSet ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 4, &pb.paramStr ); if ( siResult == eDSNoErr ) { StripRSAKey( pb.userIDToSet ); result = SendFlushReadWithMutex( pb.pContext, "SETUSERNAME", pb.userIDToSet, pb.paramStr, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); } break; case kAuthGetUserData: #pragma mark kAuthGetUserData // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 AccountID siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.userIDToSet ); if ( siResult == eDSNoErr ) { char *outData = NULL; unsigned long outDataLen; unsigned long decodedStrLen; StripRSAKey( pb.userIDToSet ); result = SendFlushReadWithMutex( pb.pContext, "GETUSERDATA", pb.userIDToSet, NULL, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); if ( siResult == eDSNoErr ) { // base64 decode user data outDataLen = strlen( buf ); outData = (char *)malloc( outDataLen ); if ( outData == NULL ) return eMemoryError; if ( Convert64ToBinary( buf, outData, outDataLen, &decodedStrLen ) == 0 ) { if ( decodedStrLen <= outBuf->fBufferSize ) { memcpy( outBuf->fBufferData, &decodedStrLen, 4 ); memcpy( outBuf->fBufferData + 4, outData, decodedStrLen ); outBuf->fBufferLength = decodedStrLen; } else { siResult = eDSBufferTooSmall; } } free( outData ); } } break; case kAuthSetUserData: #pragma mark kAuthSetUserData // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 AccountID // len4 NewUserData { char *tptr; long dataSegmentLen; siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.userIDToSet ); if ( siResult == eDSNoErr ) { StripRSAKey(pb.userIDToSet); tptr = inData->fInAuthStepData->fBufferData; for (int repeatCount = 3; repeatCount > 0; repeatCount--) { memcpy(&dataSegmentLen, tptr, 4); tptr += 4 + dataSegmentLen; } memcpy(&dataSegmentLen, tptr, 4); pb.paramStr = (char *)malloc( ((dataSegmentLen + 3) * 4 / 3) + 20 ); if ( pb.paramStr == NULL ) return eMemoryError; // base64 encode user data siResult = ConvertBinaryTo64( tptr, dataSegmentLen, pb.paramStr ); } if ( siResult == eDSNoErr ) { result = SendFlushReadWithMutex( pb.pContext, "SETUSERDATA", pb.userIDToSet, pb.paramStr, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); } } break; case kAuthDeleteUser: #pragma mark kAuthDeleteUser // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 AccountID siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.userIDToSet ); if ( siResult == eDSNoErr ) { StripRSAKey( pb.userIDToSet ); result = SendFlushReadWithMutex( pb.pContext, "DELETEUSER", pb.userIDToSet, NULL, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); } break; case kAuthGetIDByName: #pragma mark kAuthGetIDByName // buffer format is: // len1 AuthenticatorID // len2 AuthenticatorPW // len3 Name to look up // len4 ALL (optional) GetStringFromAuthBuffer( inData->fInAuthStepData, 4, &pb.userIDToSet ); siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.paramStr ); if ( siResult == eDSNoErr ) { result = SendFlushReadWithMutex( pb.pContext, "GETIDBYNAME", pb.paramStr, pb.userIDToSet, buf, sizeof(buf) ); if ( result.err != 0 ) return( PWSErrToDirServiceError(result) ); sasl_chop( buf ); // add the public rsa key if ( pb.pContext->rsaPublicKeyStr ) { strcat(buf, ","); strlcat(buf, pb.pContext->rsaPublicKeyStr, sizeof(buf)); } siResult = PackStepBuffer( buf, true, NULL, NULL, NULL, outBuf ); } break; case kAuthGetDisabledUsers: #pragma mark kAuthGetDisabledUsers siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.paramStr ); if ( siResult != eDSNoErr ) return( siResult ); if ( DSIsStringEmpty(pb.paramStr) ) return( (SInt32)eDSInvalidBuffFormat ); // Note: the password server's line limit is 1023 bytes so we may need to send // multiple requests. { long loopIndex; long byteCount = strlen( pb.paramStr ); long loopCount = (byteCount / 1023) + 1; char *sptr = pb.paramStr; char *tptr; outBuf->fBufferLength = 0; for ( loopIndex = 0; loopIndex < loopCount; loopIndex++ ) { strlcpy( buf, sptr, sizeof(buf) ); if ( byteCount > 1023 ) { tptr = buf + 1022; while ( (*tptr != ' ') && (tptr > buf) ) *tptr-- = '\0'; sptr += strlen( buf ); byteCount = strlen(pb.paramStr) - (sptr - pb.paramStr); } DEBUGLOG( "ulist: %s", buf ); result = SendFlushReadWithMutex( pb.pContext, "GETDISABLEDUSERS", buf, NULL, buf, sizeof(buf) ); if ( result.err != 0 ) return( PWSErrToDirServiceError(result) ); // use encodedStrLen; it's available encodedStrLen = strlen( buf ); if ( outBuf->fBufferLength + encodedStrLen > outBuf->fBufferSize - 4 ) return( (SInt32)eDSBufferTooSmall ); // put a 4-byte length in the buffer memcpy( outBuf->fBufferData + outBuf->fBufferLength, &encodedStrLen, 4 ); outBuf->fBufferLength += 4; // add the string strcpy( outBuf->fBufferData + outBuf->fBufferLength, buf ); outBuf->fBufferLength += encodedStrLen; } } break; case kAuth2WayRandomChangePass: #pragma mark kAuth2WayRandomChangePass StripRSAKey( pb.paramStr ); siResult = ConvertBinaryTo64( pb.password, 8, encoded64Str ); if ( siResult == eDSNoErr ) { char desDataBuf[50]; snprintf( desDataBuf, sizeof(desDataBuf), "%s ", encoded64Str ); siResult = ConvertBinaryTo64( pb.password + 8, 8, encoded64Str ); if ( siResult == eDSNoErr ) { strcat( desDataBuf, encoded64Str ); strcat( desDataBuf, "\r\n" ); result = SendFlushReadWithMutex( pb.pContext, "TWRNDCHANGEPASS", pb.paramStr, desDataBuf, buf, sizeof(buf) ); } if ( siResult == eDSNoErr && result.err != 0 ) siResult = PWSErrToDirServiceError( result ); } break; case kAuthSyncSetupReplica: #pragma mark kAuthSyncSetupReplica siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &pb.paramStr ); if ( siResult == eDSNoErr ) { result = SendFlushReadWithMutex( pb.pContext, "SYNC SETUPREPLICA", pb.paramStr, NULL, buf, sizeof(buf) ); if ( result.err != 0 ) return( PWSErrToDirServiceError(result) ); sasl_chop( buf ); if ( strcasecmp( pb.paramStr, "GET" ) == 0 ) { const char *decodedStr; unsigned long decodedStrLen; unsigned decryptedStrLen; char *replicaNamePtr = NULL; replicaNamePtr = strchr( buf + 4, ' ' ); if ( replicaNamePtr != NULL ) *replicaNamePtr++ = '\0'; decodedStrLen = strlen( buf + 4 ); pb.stepData = (char *) malloc( decodedStrLen + 9 ); if ( pb.stepData == NULL ) return( (SInt32)eMemoryError ); // decode if ( Convert64ToBinary( buf + 4, pb.stepData, decodedStrLen, &decodedStrLen ) != SASL_OK ) return( (SInt32)eDSAuthServerError ); if (decodedStrLen % 8) decodedStrLen += 8 - (decodedStrLen % 8); // decrypt gSASLMutex->Wait(); saslResult = sasl_decode(pb.pContext->conn, pb.stepData, decodedStrLen, &decodedStr, &decryptedStrLen); gSASLMutex->Signal(); // use encodedStrLen; it's available encodedStrLen = 4 + decryptedStrLen + 4 + strlen(pb.pContext->rsaPublicKeyStr); if ( replicaNamePtr != NULL ) encodedStrLen += 4 + strlen( replicaNamePtr ); if ( encodedStrLen > outBuf->fBufferSize ) return( (SInt32)eDSBufferTooSmall ); // put a 4-byte length in the buffer decodedStrLen = decryptedStrLen; memcpy( outBuf->fBufferData, &decodedStrLen, 4 ); outBuf->fBufferLength = 4; // copy the private key memcpy( outBuf->fBufferData + outBuf->fBufferLength, decodedStr, decryptedStrLen ); outBuf->fBufferLength += decryptedStrLen; // put a 4-byte length in the buffer encodedStrLen = strlen( pb.pContext->rsaPublicKeyStr ); memcpy( outBuf->fBufferData + outBuf->fBufferLength, &encodedStrLen, 4 ); outBuf->fBufferLength += 4; // copy the public key strcpy( outBuf->fBufferData + outBuf->fBufferLength, pb.pContext->rsaPublicKeyStr ); outBuf->fBufferLength += encodedStrLen; if ( replicaNamePtr != NULL ) { // put a 4-byte length in the buffer encodedStrLen = strlen( replicaNamePtr ); memcpy( outBuf->fBufferData + outBuf->fBufferLength, &encodedStrLen, 4 ); outBuf->fBufferLength += 4; // copy the replica name strcpy( outBuf->fBufferData + outBuf->fBufferLength, replicaNamePtr ); outBuf->fBufferLength += encodedStrLen; } } } break; case kAuthListReplicas: #pragma mark kAuthListReplicas gPWSConnMutex->Wait(); siResult = DoAuthMethodListReplicas( inData, pb.pContext, outBuf ); gPWSConnMutex->Signal(); break; case kAuthPull: #pragma mark kAuthPull siResult = DoAuthMethodPull( inData, pb.pContext, outBuf ); break; case kAuthPush: #pragma mark kAuthPush siResult = DoAuthMethodPush( inData, pb.pContext, outBuf ); break; case kAuthProcessNoReply: #pragma mark kAuthProcessNoReply // get size from somewhere snprintf( buf, sizeof(buf), "%lu", pb.pContext->pushByteCount ); pb.pContext->pushByteCount = 0; result = SendFlushReadWithMutex( pb.pContext, "SYNC PROCESS-NO-REPLY", buf, NULL, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); break; case kAuthSMB_NTUserSessionKey: case kAuthNTSessionKey: #pragma mark kAuthSMB_NTUserSessionKey siResult = DoAuthMethodNTUserSessionKey( siResult, pb.uiAuthMethod, inData, pb.pContext, outBuf ); break; case kAuthSMBWorkstationCredentialSessionKey: #pragma mark kAuthSMBWorkstationCredentialSessionKey { UInt32 paramLen; const char *decryptedStr; unsigned long decodedStrLen; unsigned decryptedStrLen; char base64Param[30]; siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 1, &pb.userIDToSet ); if ( siResult == eDSNoErr ) siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 2, (unsigned char **)&pb.paramStr, ¶mLen ); if ( siResult == eDSNoErr ) { if ( pb.userIDToSet == NULL || paramLen > 16 ) return( (SInt32)eDSInvalidBuffFormat ); StripRSAKey( pb.userIDToSet ); if ( ConvertBinaryTo64( pb.paramStr, paramLen, base64Param ) != SASL_OK ) return( (SInt32)eParameterError ); result = SendFlushReadWithMutex( pb.pContext, "GETWCSK", pb.userIDToSet, base64Param, buf, sizeof(buf) ); if ( result.err != 0 ) return( PWSErrToDirServiceError(result) ); decodedStrLen = strlen( buf ) - 4; if ( decodedStrLen < 2 ) return( (SInt32)eDSAuthServerError ); pb.stepData = (char *) malloc( decodedStrLen ); if ( pb.stepData == NULL ) return( (SInt32)eMemoryError ); // decode if ( Convert64ToBinary( buf + 4, pb.stepData, decodedStrLen, &decodedStrLen ) != SASL_OK ) return( (SInt32)eDSAuthServerError ); // decrypt gSASLMutex->Wait(); saslResult = sasl_decode( pb.pContext->conn, pb.stepData, decodedStrLen, &decryptedStr, &decryptedStrLen); gSASLMutex->Signal(); if ( outBuf->fBufferSize < decryptedStrLen + 4 ) return( (SInt32)eDSBufferTooSmall ); outBuf->fBufferLength = decryptedStrLen + 4; decodedStrLen = decryptedStrLen; memcpy( outBuf->fBufferData, &decodedStrLen, 4 ); memcpy( outBuf->fBufferData + 4, decryptedStr, decryptedStrLen ); } } break; case kAuthNTSetWorkstationPasswd: #pragma mark kAuthNTSetWorkstationPasswd siResult = DoAuthMethodSetHash( inData, pb.pContext, "CHANGENTHASH" ); break; case kAuthSetLMHash: #pragma mark kAuthSetLMHash siResult = DoAuthMethodSetHash( inData, pb.pContext, "CHANGELMHASH" ); break; case kAuthGetKerberosPrincipal: #pragma mark kAuthGetKerberosPrincipal siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 1, &pb.userIDToSet ); if ( siResult != eDSNoErr ) return( siResult ); result = SendFlushReadWithMutex( pb.pContext, "GETKERBPRINC", pb.userIDToSet, NULL, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); if ( siResult != eDSNoErr ) return( siResult ); sasl_chop( buf ); siResult = PackStepBuffer( buf, true, NULL, NULL, NULL, outBuf ); break; case kAuthVPN_PPTPMasterKeys: #pragma mark kAuthVPN_PPTPMasterKeys siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 1, &pb.userIDToSet ); if ( siResult == eDSNoErr ) siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 2, (unsigned char **)&pb.paramStr, &pb.passwordLen ); if ( siResult == eDSNoErr ) { ConvertBinaryToHex( (unsigned char *)pb.paramStr, pb.passwordLen, buf ); free( pb.paramStr ); pb.paramStr = NULL; siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 3, (unsigned char **)&pb.paramStr, &pb.passwordLen ); if ( siResult == eDSNoErr && pb.passwordLen == 1 && (pb.paramStr[0] == 8 || pb.paramStr[0] == 16) ) { strcat( buf, " " ); strcat( buf, (pb.paramStr[0] == 8) ? "8" : "16" ); } else { if ( siResult == eDSNoErr ) siResult = eDSInvalidBuffFormat; } } if ( siResult == eDSNoErr ) { const char *decryptedStr; unsigned long buffItemLen; unsigned decryptedStrLen; if ( pb.userIDToSet == NULL ) return( (SInt32)eDSInvalidBuffFormat ); StripRSAKey( pb.userIDToSet ); result = SendFlushReadWithMutex( pb.pContext, "GETPPTPKEYS", pb.userIDToSet, buf, buf, sizeof(buf) ); if ( result.err != 0 ) return( PWSErrToDirServiceError(result) ); buffItemLen = strlen( buf ) - 4; if ( buffItemLen < 2 ) return( (SInt32)eDSAuthServerError ); pb.stepData = (char *) malloc( buffItemLen ); if ( pb.stepData == NULL ) return( (SInt32)eMemoryError ); // decode if ( Convert64ToBinary( buf + 4, pb.stepData, buffItemLen, &buffItemLen ) != SASL_OK ) return( (SInt32)eDSAuthServerError ); // decrypt gSASLMutex->Wait(); saslResult = sasl_decode( pb.pContext->conn, pb.stepData, buffItemLen, &decryptedStr, &decryptedStrLen); gSASLMutex->Signal(); if ( outBuf->fBufferSize < decryptedStrLen + 4 ) return( (SInt32)eDSBufferTooSmall ); buffItemLen = decryptedStr[0]; outBuf->fBufferLength = buffItemLen * 2 + 8; memcpy( outBuf->fBufferData, &buffItemLen, 4 ); memcpy( outBuf->fBufferData + 4, decryptedStr + 1, buffItemLen ); memcpy( outBuf->fBufferData + 4 + buffItemLen, &buffItemLen, 4 ); memcpy( outBuf->fBufferData + 4 + buffItemLen + 4, decryptedStr + 1 + buffItemLen, buffItemLen ); } break; case kAuthMSLMCHAP2ChangePasswd: #pragma mark kAuthMSLMCHAP2ChangePasswd siResult = DoAuthMethodMSChapChangePass( inData, pb.pContext ); break; case kAuthEncryptToUser: #pragma mark kAuthEncryptToUser siResult = DoAuthMethodEncryptToUser( inData, pb.pContext, outBuf ); break; case kAuthDecrypt: #pragma mark kAuthDecrypt siResult = DoAuthMethodDecrypt( inData, pb.pContext, outBuf ); break; case kAuthGetStats: #pragma mark kAuthGetStats siResult = DoAuthMethodGetStats( inData, pb.pContext, outBuf ); break; case kAuthGetChangeList: #pragma mark kAuthGetChangeList siResult = DoAuthMethodGetChangeList( inData, pb.pContext, outBuf ); break; } return( siResult ); } // DoAuthenticationResponse #pragma mark - // --------------------------------------------------------------------------- // * DoAuthMethodNewUser // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodNewUser( sDoDirNodeAuth *inData, sPSContextData *inContext, bool inWithPolicy, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; char *princToSet = NULL; char *paramStr = NULL; char *policyStr = NULL; int saslResult = SASL_OK; const char *encodedStr = NULL; unsigned int encodedStrLen = 0; long commandStrLen = 0; long policyStrLen = 0; unsigned long princToSetStrLen = 0; bool needPolicyLater = false; bool hasDotInName = false; NewUserParamListType paramListType = kNewUserParamsNone; PWServerError result = {0}; char buf[kOneKBuffer]; char encoded64Str[kOneKBuffer]; char userNameToSet[kOneKBuffer]; try { siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &princToSet ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 4, ¶mStr ); if ( siResult == eDSNoErr && inWithPolicy ) siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 5, &policyStr ); if ( siResult == eDSNoErr ) { if ( princToSet == NULL || paramStr == NULL ) throw( (SInt32)eDSInvalidBuffFormat ); princToSetStrLen = strlen( princToSet ); if ( princToSetStrLen == 0 ) DEBUGLOG( "CPSPlugIn::DoAuthMethodNewUser(): empty username in buffer" ); if ( princToSetStrLen == 0 || princToSetStrLen >= sizeof(userNameToSet) || strlen(paramStr) >= kChangePassPaddedBufferSize ) { throw( (SInt32)eDSAuthParameterError ); } // special-case for an empty password. DIGEST-MD5 does not // support empty passwords, but it's a DS requirement if ( DSIsStringEmpty(paramStr) ) { free( paramStr ); paramStr = (char *) strdup( kEmptyPasswordAltStr ); } strlcpy(buf, paramStr, sizeof(buf)); gSASLMutex->Wait(); saslResult = sasl_encode(inContext->conn, buf, kChangePassPaddedBufferSize, &encodedStr, &encodedStrLen); gSASLMutex->Signal(); } if ( siResult == eDSNoErr && saslResult == SASL_OK ) { if ( ConvertBinaryTo64( encodedStr, encodedStrLen, encoded64Str ) == SASL_OK ) { if ( inContext->rsaPublicKeyStr == NULL ) throw( (SInt32)eDSAuthServerError ); // figure out the param list type { long shortnameLen; char *tptr = strchr( princToSet, '.' ); hasDotInName = ( tptr != NULL ); if ( hasDotInName ) { shortnameLen = (long)(tptr - princToSet); if ( shortnameLen > 0 ) { strlcpy( userNameToSet, princToSet, shortnameLen + 1 ); } else { strcpy( userNameToSet, princToSet ); } } else { strcpy( userNameToSet, princToSet ); } } if ( inWithPolicy ) { paramListType = hasDotInName ? kNewUserParamsPrincipalNameAndPolicy : kNewUserParamsPolicy; } else { paramListType = hasDotInName ? kNewUserParamsPrincipalName : kNewUserParamsNone; } // construct the param list commandStrLen = snprintf( buf, sizeof(buf), "NEWUSER %s %s", userNameToSet, encoded64Str ); switch( paramListType ) { case kNewUserParamsNone: result = SendFlushReadWithMutex( inContext, buf, NULL, NULL, buf, sizeof(buf) ); break; case kNewUserParamsPolicy: if ( policyStr != NULL ) policyStrLen = strlen( policyStr ); needPolicyLater = (commandStrLen + sizeof(" WITHPOLICY ") + policyStrLen + 2 > 1535); if ( policyStrLen > 0 && !needPolicyLater ) { result = SendFlushReadWithMutex( inContext, buf, "WITHPOLICY", policyStr, buf, sizeof(buf) ); } else { result = SendFlushReadWithMutex( inContext, buf, NULL, NULL, buf, sizeof(buf) ); } break; case kNewUserParamsPrincipalName: result = SendFlushReadWithMutex( inContext, buf, "WITHPRINC", princToSet, buf, sizeof(buf) ); break; case kNewUserParamsPrincipalNameAndPolicy: if ( policyStr != NULL ) policyStrLen = strlen( policyStr ); needPolicyLater = (commandStrLen + sizeof(" WITHPRINC_AND_POLICY ") + princToSetStrLen + 1 + policyStrLen + 2 > 1535); if ( policyStrLen > 0 && !needPolicyLater ) { strcat( buf, " WITHPRINC_AND_POLICY " ); strlcat( buf, princToSet, sizeof(buf) ); result = SendFlushReadWithMutex( inContext, buf, policyStr, NULL, buf, sizeof(buf) ); } else { result = SendFlushReadWithMutex( inContext, buf, "WITHPRINC", princToSet, buf, sizeof(buf) ); } break; } if ( result.err != 0 ) throw( PWSErrToDirServiceError(result) ); sasl_chop( buf ); // use encodedStrLen; it's available encodedStrLen = strlen(buf) + 1 + strlen(inContext->rsaPublicKeyStr); if ( encodedStrLen > outBuf->fBufferSize ) throw( (SInt32)eDSBufferTooSmall ); // put a 4-byte length in the buffer encodedStrLen -= 4; memcpy( outBuf->fBufferData, &encodedStrLen, 4 ); outBuf->fBufferLength = 4; // copy the ID encodedStrLen = strlen( buf + 4 ); memcpy( outBuf->fBufferData + outBuf->fBufferLength, buf+4, encodedStrLen ); outBuf->fBufferLength += encodedStrLen; // add a separator outBuf->fBufferData[outBuf->fBufferLength] = ','; outBuf->fBufferLength++; // copy the public key strcpy( outBuf->fBufferData + outBuf->fBufferLength, inContext->rsaPublicKeyStr ); outBuf->fBufferLength += strlen(inContext->rsaPublicKeyStr); if ( needPolicyLater && policyStrLen > 0 ) { result = SendFlushReadWithMutex( inContext, "SETPOLICY", outBuf->fBufferData + 4, policyStr, buf, sizeof(buf) ); } } else { DEBUGLOG( "CPSPlugIn::DoAuthMethodNewUser(): encode64 failed, data length = %u", encodedStrLen ); } } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } DSFreeString( princToSet ); DSFreeString( paramStr ); DSFreeString( policyStr ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodNewComputer // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodNewComputer( sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; char *computerName = NULL; char *computerPass = NULL; char *ownerList = NULL; int saslResult = SASL_OK; const char *encodedStr = NULL; unsigned int encodedStrLen = 0; long commandStrLen = 0; PWServerError result = {0}; char buf[kOneKBuffer]; char encoded64Str[kOneKBuffer]; try { siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &computerName ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 4, &computerPass ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 5, &ownerList ); if ( siResult != eDSNoErr ) throw( (SInt32)eDSInvalidBuffFormat ); if ( strlen(computerPass) > kChangePassPaddedBufferSize ) throw( (SInt32)eDSAuthPasswordTooLong ); // special-case for an empty password. DIGEST-MD5 does not // support empty passwords, but it's a DS requirement if ( DSIsStringEmpty(computerPass) ) { free( computerPass ); computerPass = strdup( kEmptyPasswordAltStr ); } strlcpy(buf, computerPass, sizeof(buf)); gSASLMutex->Wait(); saslResult = sasl_encode(inContext->conn, buf, kChangePassPaddedBufferSize, &encodedStr, &encodedStrLen); gSASLMutex->Signal(); if ( saslResult != SASL_OK ) { DEBUGLOG( "CPSPlugIn::DoAuthMethodNewComputer(): sasl_encode = %d", saslResult ); throw( (SInt32)eDSAuthFailed ); } if ( ConvertBinaryTo64( encodedStr, encodedStrLen, encoded64Str ) == SASL_OK ) { if ( inContext->rsaPublicKeyStr == NULL ) throw( (SInt32)eDSAuthServerError ); // construct the param list commandStrLen = snprintf( buf, sizeof(buf), "NEWUSER %s %s", computerName, encoded64Str ); result = SendFlushReadWithMutex( inContext, buf, "COMPUTERACCT", ownerList, buf, sizeof(buf) ); if ( result.err != 0 ) throw( PWSErrToDirServiceError(result) ); sasl_chop( buf ); // use encodedStrLen; it's available encodedStrLen = strlen(buf) + 1 + strlen(inContext->rsaPublicKeyStr); if ( encodedStrLen > outBuf->fBufferSize ) throw( (SInt32)eDSBufferTooSmall ); // put a 4-byte length in the buffer encodedStrLen -= 4; memcpy( outBuf->fBufferData, &encodedStrLen, 4 ); outBuf->fBufferLength = 4; // copy the ID encodedStrLen = strlen( buf + 4 ); memcpy( outBuf->fBufferData + outBuf->fBufferLength, buf+4, encodedStrLen ); outBuf->fBufferLength += encodedStrLen; // add a separator outBuf->fBufferData[outBuf->fBufferLength] = ','; outBuf->fBufferLength++; // copy the public key strcpy( outBuf->fBufferData + outBuf->fBufferLength, inContext->rsaPublicKeyStr ); outBuf->fBufferLength += strlen(inContext->rsaPublicKeyStr); } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } DSFreeString( computerName ); DSFreePassword( computerPass ); DSFreeString( ownerList ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodSetComputerAccountPassword // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodSetComputerAccountPassword( sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; char *computerName = NULL; char *computerPass = NULL; char *serviceList = NULL; char *hostList = NULL; char *realm = NULL; int saslResult = SASL_OK; const char *encodedStr = NULL; unsigned int encodedStrLen = 0; long commandStrLen = 0; PWServerError result = {0}; char buf[4 * kOneKBuffer]; char encoded64Str[kOneKBuffer]; try { siResult = Get2StringsFromAuthBuffer( inData->fInAuthStepData, &computerName, &computerPass ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3, &serviceList ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 4, &hostList ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 5, &realm ); if ( siResult != eDSNoErr ) throw( (SInt32)eDSInvalidBuffFormat ); if ( strlen(computerPass) > kChangePassPaddedBufferSize ) throw( (SInt32)eDSAuthPasswordTooLong ); StripRSAKey( computerName ); // special-case for an empty password. DIGEST-MD5 does not // support empty passwords, but it's a DS requirement if ( DSIsStringEmpty(computerPass) ) { free( computerPass ); computerPass = strdup( kEmptyPasswordAltStr ); } strlcpy(buf, computerPass, sizeof(buf)); gSASLMutex->Wait(); saslResult = sasl_encode(inContext->conn, buf, kChangePassPaddedBufferSize, &encodedStr, &encodedStrLen); gSASLMutex->Signal(); if ( saslResult != SASL_OK ) { DEBUGLOG( "CPSPlugIn::DoAuthMethodNewComputer(): sasl_encode = %d", saslResult ); throw( (SInt32)eDSAuthFailed ); } if ( ConvertBinaryTo64( encodedStr, encodedStrLen, encoded64Str ) == SASL_OK ) { if ( inContext->rsaPublicKeyStr == NULL ) throw( (SInt32)eDSAuthServerError ); // construct the param list commandStrLen = snprintf( buf, sizeof(buf), "CHANGEPASS %s %s COMPUTERACCT %s %s %s", computerName, encoded64Str, serviceList, hostList, realm ); result = SendFlushReadWithMutex( inContext, buf, NULL, NULL, buf, sizeof(buf) ); if ( result.err != 0 ) throw( PWSErrToDirServiceError(result) ); // read back the list of principals sasl_chop( buf ); } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } DSFreeString( computerName ); DSFreePassword( computerPass ); DSFreeString( serviceList ); DSFreeString( hostList ); DSFreeString( realm ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodListReplicas // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodListReplicas( sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; sPSContinueData *pContinue = NULL; char *replicaDataBuff = NULL; unsigned long replicaDataReceived = 0; unsigned long replicaDataLen = 0; try { if ( outBuf->fBufferSize < 5 ) throw( (SInt32)eDSBufferTooSmall ); // repeat visit? if ( inData->fIOContinueData != 0 ) { // already verified in DoAuthentication() pContinue = (sPSContinueData *)inData->fIOContinueData; replicaDataLen = pContinue->fDataLen - pContinue->fDataPos; if ( 4 + replicaDataLen > outBuf->fBufferSize ) replicaDataLen = outBuf->fBufferSize - 4; memcpy( outBuf->fBufferData, &replicaDataLen, 4 ); memcpy( outBuf->fBufferData + 4, pContinue->fData + pContinue->fDataPos, replicaDataLen ); outBuf->fBufferLength = 4 + replicaDataLen; pContinue->fDataPos += replicaDataLen; if ( pContinue->fDataPos >= pContinue->fDataLen ) { // we are done gContinue->RemoveItem( pContinue ); inData->fIOContinueData = 0; } } else { siResult = GetReplicaListFromServer( inContext, &replicaDataBuff, &replicaDataReceived ); if ( siResult != eDSNoErr ) throw( siResult ); // Do we need a continue data? if ( 4 + replicaDataReceived > outBuf->fBufferSize ) { if ( inData->fIOContinueData == 0 ) { pContinue = (sPSContinueData *)::calloc( 1, sizeof( sPSContinueData ) ); Throw_NULL( pContinue, eMemoryError ); gContinue->AddItem( pContinue, inData->fInNodeRef ); inData->fIOContinueData = pContinue; } else { throw( (SInt32)eDSInvalidContinueData ); } pContinue->fData = (unsigned char *) replicaDataBuff; pContinue->fDataLen = replicaDataReceived; pContinue->fDataPos = outBuf->fBufferSize - 4; memcpy( outBuf->fBufferData, &(pContinue->fDataPos), 4 ); memcpy( outBuf->fBufferData + 4, replicaDataBuff, pContinue->fDataPos ); outBuf->fBufferLength = 4 + pContinue->fDataPos; } else { memcpy( outBuf->fBufferData, &replicaDataReceived, 4 ); memcpy( outBuf->fBufferData + 4, replicaDataBuff, replicaDataReceived ); outBuf->fBufferLength = 4 + replicaDataReceived; free( replicaDataBuff ); replicaDataBuff = NULL; } } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodPull // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodPull( sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; char *replicaNameStr = NULL; char *allStr = NULL; int dataType = 0; unsigned int dataSize = 0; unsigned int dataLen = 0; unsigned char *dataBuffer = NULL; unsigned char *decryptPtr = NULL; sPSContinueData *pContinue = NULL; unsigned char dbHeaderBlock[sizeof(SInt16) + sizeof(PWFileHeader)]; unsigned char passRecBlock[sizeof(SInt16) + sizeof(PWFileEntry)]; PWServerError result; const char * dataTypeDesc = ""; try { Throw_NULL( outBuf, eParameterError ); outBuf->fBufferLength = 0; // repeat visit? if ( inData->fIOContinueData != NULL ) { // already verified in DoAuthentication() pContinue = (sPSContinueData *)inData->fIOContinueData; if ( pContinue != NULL ) { if ( pContinue->fData != NULL ) { decryptPtr = pContinue->fData; dataLen = pContinue->fDataLen; pContinue->fData = NULL; pContinue->fDataLen = 0; } else { // we are done gContinue->RemoveItem( pContinue ); inData->fIOContinueData = NULL; } } } else { siResult = SetupSecureSyncSession( inContext ); if ( siResult != eDSNoErr ) throw( siResult ); siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 1, &replicaNameStr ); if ( siResult != eDSNoErr ) throw( siResult ); // ALL parameter is optional GetStringFromAuthBuffer( inData->fInAuthStepData, 2, &allStr ); // send gPWSConnMutex->Wait(); result = SendFlush( inContext, "SYNC PULL", replicaNameStr, allStr ); if ( result.err == 0 ) { // get results result = pwsf_ReadSyncDataFromServerWithCASTKey( inContext, &dataBuffer, &dataType, &dataSize, &dataLen ); } gPWSConnMutex->Signal(); if ( result.err != 0 ) { DEBUGLOG( "Error while initiating PULL command = %l", result.err ); throw( PWSErrToDirServiceError(result) ); } decryptPtr = (unsigned char *) malloc( sizeof(SInt16) + dataSize ); if ( decryptPtr == NULL ) throw( (SInt32)eMemoryError ); *((SInt16 *)decryptPtr) = (SInt16)dataType; RC5_32_cbc_encrypt( dataBuffer, decryptPtr + sizeof(SInt16), dataSize, &inContext->rc5Key, inContext->psIV, RC5_DECRYPT ); } while ( decryptPtr != NULL ) { dataTypeDesc = "invalid"; switch( dataType ) { case kDBTypeLastSyncTime: // no compression, nothing to do siResult = dsAppendAuthBuffer( outBuf, 1, sizeof(SInt16) + dataLen, decryptPtr ); dataTypeDesc = "kDBTypeLastSyncTime"; break; case kDBTypeHeader: *((SInt16 *)dbHeaderBlock) = (SInt16)dataType; siResult = pwsf_expand_header( decryptPtr + sizeof(SInt16), dataLen, (PWFileHeader *)&dbHeaderBlock[sizeof(SInt16)] ); if ( siResult == eDSNoErr ) siResult = dsAppendAuthBuffer( outBuf, 1, sizeof(SInt16) + sizeof(PWFileHeader), dbHeaderBlock ); dataTypeDesc = "kDBTypeHeader"; break; case kDBTypeSlot: *((SInt16 *)passRecBlock) = (SInt16)dataType; siResult = pwsf_expand_slot( decryptPtr + sizeof(SInt16), dataLen, (PWFileEntry *)&passRecBlock[sizeof(SInt16)] ); if ( siResult == eDSNoErr ) siResult = dsAppendAuthBuffer( outBuf, 1, sizeof(SInt16) + sizeof(PWFileEntry), passRecBlock ); dataTypeDesc = "kDBTypeSlot"; break; case kDBTypeKerberosPrincipal: // no compression, nothing to do siResult = dsAppendAuthBuffer( outBuf, 1, sizeof(SInt16) + dataLen, decryptPtr ); dataTypeDesc = "kDBTypeKerberosPrincipal"; break; } DEBUGLOG( "Received dataType=%s dataSize=%d", dataTypeDesc, dataSize ); if ( siResult == eDSBufferTooSmall ) { if ( inData->fIOContinueData == 0 ) { pContinue = (sPSContinueData *) calloc( 1, sizeof(sPSContinueData) ); Throw_NULL( pContinue, eMemoryError ); gContinue->AddItem( pContinue, inData->fInNodeRef ); inData->fIOContinueData = pContinue; } pContinue->fData = decryptPtr; pContinue->fDataLen = dataLen; decryptPtr = NULL; siResult = eDSNoErr; break; } else if ( siResult != eDSNoErr ) throw( (SInt32)siResult ); DSFree( dataBuffer ); DSFree( decryptPtr ); gPWSConnMutex->Wait(); result = pwsf_ReadSyncDataFromServerWithCASTKey( inContext, &dataBuffer, &dataType, &dataSize, &dataLen ); gPWSConnMutex->Signal(); if ( result.err != 0 ) { DEBUGLOG( "pwsf_ReadSyncDataFromServerWithCASTKey(2) = %l", result.err ); throw( PWSErrToDirServiceError(result) ); } if ( dataBuffer != NULL ) { decryptPtr = (unsigned char *) malloc( sizeof(SInt16) + dataSize ); if ( decryptPtr == NULL ) throw( (SInt32)eMemoryError ); *((SInt16 *)decryptPtr) = (SInt16)dataType; RC5_32_cbc_encrypt( dataBuffer, decryptPtr + sizeof(SInt16), dataSize, &inContext->rc5Key, inContext->psIV, RC5_DECRYPT ); } } } catch( SInt32 error ) { siResult = error; } catch( ... ) { siResult = eDSAuthFailed; } DSFreeString( replicaNameStr ); DSFreeString( allStr ); DSFree( dataBuffer ); DSFree( decryptPtr ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodPush // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodPush( sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; char *paramStr = NULL; PWServerError result = {0, kPolicyError}; unsigned char *syncData = NULL; unsigned char *encSyncData = NULL; UInt32 syncDataLen = 0; UInt32 *dataType = 0; UInt32 dataTypeLen = 0; char buf[kOneKBuffer] = {0}; int bufferItemIndex = 1; if ( inContext->pushByteCount > 0 && !SecureSyncSessionIsSetup(inContext) ) return eDSAuthFailed; try { siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, bufferItemIndex++, (unsigned char **)&dataType, &dataTypeLen ); if ( siResult != eDSNoErr ) throw( siResult ); if ( dataType == NULL || dataTypeLen != sizeof(UInt32) ) throw( (SInt32)eDSInvalidBuffFormat ); siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, bufferItemIndex++, &syncData, &syncDataLen ); if ( siResult != eDSNoErr ) throw( siResult ); siResult = SetupSecureSyncSession( inContext ); if ( siResult != eDSNoErr ) throw( siResult ); inContext->pushByteCount += syncDataLen; // BEGIN no throw zone // GetDataFromAuthBuffer pads the allocation for us so we can append zeros bzero( syncData + syncDataLen, RC5_32_BLOCK ); if ( (syncDataLen % RC5_32_BLOCK) != 0 ) syncDataLen = (syncDataLen / RC5_32_BLOCK) * RC5_32_BLOCK + RC5_32_BLOCK; encSyncData = (unsigned char *) calloc( 1, syncDataLen + RC5_32_BLOCK ); if ( encSyncData == NULL ) siResult = eMemoryError; if ( siResult == eDSNoErr ) { RC5_32_cbc_encrypt( (unsigned char *)syncData, encSyncData, syncDataLen, &inContext->rc5Key, inContext->psIV, RC5_ENCRYPT ); paramStr = (char *)malloc( syncDataLen * 4/3 + 20 ); if ( paramStr == NULL ) siResult = eMemoryError; } if ( siResult == eDSNoErr ) { siResult = ConvertBinaryTo64( (char *)encSyncData, syncDataLen, paramStr ); if ( siResult != eDSNoErr ) DEBUGLOG( "PasswordServer PlugIn: CPSPlugIn::DoAuthMethodPush, ConvertBinaryTo64 = %l", siResult ); } if ( encSyncData != NULL ) free( encSyncData ); // END no throw zone if ( siResult == eDSNoErr ) { snprintf( buf, sizeof(buf), "SYNC PUSH %lu", *dataType ); result = SendFlushReadWithMutex( inContext, buf, paramStr, NULL, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); if ( siResult != eDSNoErr ) DEBUGLOG( "PasswordServer PlugIn: CPSPlugIn::DoAuthMethodPush, SendFlushReadWithMutex = %l", siResult ); } } catch( SInt32 error ) { siResult = error; } catch( ... ) { DEBUGLOG( "PasswordServer PlugIn: CPSPlugIn::DoAuthMethodPush, uncasted throw" ); } if ( paramStr != NULL ) free( paramStr ); if ( syncData != NULL ) free( syncData ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodNTUserSessionKey // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodNTUserSessionKey( SInt32 inAuthenticatorAuthResult, UInt32 inAuthMethod, sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; char *userID = NULL; char *paramStr = NULL; char *stepData = NULL; int saslResult = SASL_OK; sPSContinueData *pContinue = NULL; PWServerError result = {0}; unsigned char *challenge = NULL; unsigned char *digest = NULL; UInt32 len = 0; char password[32] = {0}; char buf[kOneKBuffer] = {0,}; try { siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 1, &userID ); if ( siResult == eDSNoErr ) { const char *decryptedStr; unsigned long decodedStrLen; unsigned decryptedStrLen; if ( userID == NULL ) throw( (SInt32)eDSInvalidBuffFormat ); StripRSAKey( userID ); if ( inAuthenticatorAuthResult == eDSNoErr ) { result = SendFlushReadWithMutex( inContext, "GETNTHASHHASH", userID, NULL, buf, sizeof(buf) ); if ( result.err != 0 ) throw( PWSErrToDirServiceError(result) ); decodedStrLen = strlen( buf ) - 4; if ( decodedStrLen < 2 ) throw( (SInt32)eDSAuthServerError ); stepData = (char *) malloc( decodedStrLen ); if ( stepData == NULL ) throw( (SInt32)eMemoryError ); // decode if ( Convert64ToBinary( buf + 4, stepData, decodedStrLen, &decodedStrLen ) != SASL_OK ) throw( (SInt32)eDSAuthServerError ); // decrypt gSASLMutex->Wait(); saslResult = sasl_decode( inContext->conn, stepData, decodedStrLen, &decryptedStr, &decryptedStrLen); gSASLMutex->Signal(); if ( saslResult != SASL_OK ) throw( (SInt32)eDSAuthFailed ); if ( outBuf->fBufferSize < decryptedStrLen + 4 ) throw( (SInt32)eDSBufferTooSmall ); outBuf->fBufferLength = decryptedStrLen + 4; decodedStrLen = decryptedStrLen; memcpy( outBuf->fBufferData, &decodedStrLen, 4 ); memcpy( outBuf->fBufferData + 4, decryptedStr, decryptedStrLen ); } if ( inAuthMethod == kAuthNTSessionKey ) { // now do the NT auth siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 2, &challenge, &len ); if ( siResult != eDSNoErr || challenge == NULL || len != 8 ) throw( (SInt32)eDSInvalidBuffFormat ); siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 3, &digest, &len ); if ( siResult != eDSNoErr || digest == NULL || len != 24 ) throw( (SInt32)eDSInvalidBuffFormat ); memcpy( password, challenge, 8 ); memcpy( password + 8, digest, 24 ); if ( inData->fIOContinueData == 0 ) { pContinue = (sPSContinueData *) calloc( 1, sizeof( sPSContinueData ) ); Throw_NULL( pContinue, eMemoryError ); gContinue->AddItem( pContinue, inData->fInNodeRef ); inData->fIOContinueData = pContinue; } siResult = DoSASLAuth( inContext, userID, password, 32, NULL, "SMB-NT", inData, &stepData ); } else { siResult = inAuthenticatorAuthResult; } } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } if ( userID != NULL ) free( userID ); if ( paramStr != NULL ) free( paramStr ); if ( stepData != NULL ) free( stepData ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodMSChapChangePass // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodMSChapChangePass( sDoDirNodeAuth *inData, sPSContextData *inContext ) { SInt32 siResult = eDSNoErr; char *userIDToSet = NULL; char *paramStr = NULL; UInt32 paramLen = 0; long encoding = 0; PWServerError result; char buf[kOneKBuffer]; char encoded64Str[kOneKBuffer]; try { siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 1, &userIDToSet ); if ( siResult == eDSNoErr ) { StripRSAKey( userIDToSet ); siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 2, (unsigned char **)¶mStr, ¶mLen ); if ( siResult == eDSNoErr ) { encoding = paramStr[0]; free( paramStr ); paramStr = NULL; siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 3, (unsigned char **)¶mStr, ¶mLen ); } } if ( siResult == eDSNoErr ) { snprintf( encoded64Str, sizeof(encoded64Str), "%ld ", encoding ); siResult = ConvertBinaryTo64( paramStr, paramLen, encoded64Str + strlen(encoded64Str) ); if ( siResult != eDSNoErr ) throw( siResult ); result = SendFlushReadWithMutex( inContext, "MSCHAPCHANGEPASS", userIDToSet, encoded64Str, buf, sizeof(buf) ); if ( result.err != 0 ) throw( PWSErrToDirServiceError(result) ); } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } if ( userIDToSet != NULL ) free( userIDToSet ); if ( paramStr != NULL ) free( paramStr ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodEncryptToUser // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodEncryptToUser( sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; char *userIDToSet = NULL; unsigned char *dataToEncrypt = NULL; UInt32 dataToEncryptLen = 0; unsigned long binBufLen = 0; const char *encodedStr = NULL; unsigned int encodedStrLen = 0; int saslResult = SASL_OK; PWServerError result; char buf[kOneKBuffer]; char binBuf[kOneKBuffer]; try { siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 1, &userIDToSet ); if ( siResult == eDSNoErr ) siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 2, &dataToEncrypt, &dataToEncryptLen ); if ( siResult != eDSNoErr ) throw( siResult ); gSASLMutex->Wait(); saslResult = sasl_encode( inContext->conn, (char *)dataToEncrypt, dataToEncryptLen, &encodedStr, &encodedStrLen ); gSASLMutex->Signal(); if ( saslResult != SASL_OK ) throw( (SInt32)eDSAuthFailed ); siResult = ConvertBinaryTo64( (char *)encodedStr, encodedStrLen, buf ); if ( siResult != eDSNoErr ) throw( siResult ); StripRSAKey( userIDToSet ); result = SendFlushReadWithMutex( inContext, "CYPHER ENCRYPT", userIDToSet, buf, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); if ( siResult == eDSNoErr ) { siResult = Convert64ToBinary( buf + 4, binBuf, sizeof(binBuf), &binBufLen ); if ( siResult == eDSNoErr ) { if ( outBuf->fBufferSize < 4 + binBufLen ) throw( (SInt32)eDSBufferTooSmall ); outBuf->fBufferLength = 4 + binBufLen; memcpy( outBuf->fBufferData, &binBufLen, 4 ); memcpy( outBuf->fBufferData + 4, binBuf, binBufLen ); } } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } if ( userIDToSet != NULL ) free( userIDToSet ); if ( dataToEncrypt != NULL ) free( dataToEncrypt ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodDecrypt // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodDecrypt( sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; unsigned char *dataToDecrypt = NULL; UInt32 dataToDecryptLen = 0; unsigned long binBufLen = 0; const char *decodedStr = NULL; unsigned int decodedStrLen = 0; int saslResult = SASL_OK; PWServerError result; char buf[kOneKBuffer]; char binBuf[kOneKBuffer]; try { siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 2, &dataToDecrypt, &dataToDecryptLen ); if ( siResult != eDSNoErr ) throw( siResult ); siResult = ConvertBinaryTo64( (char *)dataToDecrypt, dataToDecryptLen, buf ); if ( siResult != eDSNoErr ) throw( siResult ); result = SendFlushReadWithMutex( inContext, "CYPHER DECRYPT", buf, NULL, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); if ( siResult != eDSNoErr ) throw( siResult ); // the buffer to sasl_decode needs to be padded to the key size (CAST128 == 16 bytes) // prefill with zeros bzero( binBuf, sizeof(binBuf) ); siResult = Convert64ToBinary( buf + 4, binBuf, sizeof(binBuf), &binBufLen ); if ( siResult != eDSNoErr ) throw( siResult ); // add the remainder if ( (binBufLen % 16) ) binBufLen += 16 - (binBufLen % 16); gSASLMutex->Wait(); saslResult = sasl_decode( inContext->conn, binBuf, binBufLen, &decodedStr, &decodedStrLen ); gSASLMutex->Signal(); if ( saslResult != SASL_OK ) throw( (SInt32)eDSAuthFailed ); if ( outBuf->fBufferSize < decodedStrLen + 4 ) throw( (SInt32)eDSBufferTooSmall ); binBufLen = (UInt32)decodedStrLen; outBuf->fBufferLength = binBufLen + 4; memcpy( outBuf->fBufferData, &binBufLen, 4 ); memcpy( outBuf->fBufferData + 4, decodedStr, binBufLen ); } catch( SInt32 error ) { siResult = error; } catch( ... ) { } if ( dataToDecrypt != NULL ) free( dataToDecrypt ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodSetHash // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodSetHash( sDoDirNodeAuth *inData, sPSContextData *inContext, const char *inCommandStr ) { SInt32 siResult = eDSNoErr; char *userIDToSet = NULL; char *paramStr = NULL; UInt32 paramLen = 0; int saslResult = SASL_OK; const char *encodedStr = NULL; unsigned int encodedStrLen = 0; PWServerError result; char buf[kOneKBuffer]; char encoded64Str[kOneKBuffer]; try { siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 1, &userIDToSet ); if ( siResult == eDSNoErr ) siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 2, (unsigned char **)¶mStr, ¶mLen ); if ( siResult != eDSNoErr ) throw( siResult ); memcpy( buf, paramStr, paramLen ); gSASLMutex->Wait(); saslResult = sasl_encode(inContext->conn, buf, kChangePassPaddedBufferSize, &encodedStr, &encodedStrLen); gSASLMutex->Signal(); if ( siResult == eDSNoErr && saslResult == SASL_OK && userIDToSet != NULL ) { if ( ConvertBinaryTo64( encodedStr, encodedStrLen, encoded64Str ) == SASL_OK ) { result = SendFlushReadWithMutex( inContext, inCommandStr, userIDToSet, encoded64Str, buf, sizeof(buf) ); siResult = PWSErrToDirServiceError( result ); } } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } if ( userIDToSet != NULL ) free( userIDToSet ); if ( paramStr != NULL ) free( paramStr ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodNTLMv2SessionKey // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodNTLMv2SessionKey( SInt32 inAuthenticatorAuthResult, UInt32 inAuthMethod, sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; char *userIDToGet = NULL; unsigned char *clientBlob = NULL; UInt32 clientBlobLen = 0; char *user = NULL; char *domain = NULL; char *paramStr = NULL; size_t paramStrLen = 0; char *stepData = NULL; int saslResult = SASL_OK; const char *decryptedStr; unsigned long encodedStrLen; unsigned long decodedStrLen; unsigned decryptedStrLen; PWServerError result; char buf[kOneKBuffer]; char encoded64Str[kOneKBuffer]; sPSContinueData *pContinue = NULL; int bufferOffset = (inAuthMethod == kAuthNTLMv2WithSessionKey) ? 1 : 0; try { // User ID siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 1, &userIDToGet ); if ( siResult != eDSNoErr ) throw( siResult ); if ( userIDToGet == NULL ) throw( (SInt32)eDSInvalidBuffFormat ); StripRSAKey( userIDToGet ); // Client Blob, username, domain siResult = GetDataFromAuthBuffer( inData->fInAuthStepData, 2 + bufferOffset, &clientBlob, &clientBlobLen ); if ( siResult == eDSNoErr ) { if ( clientBlobLen < 24 ) throw( (SInt32)eParameterError ); siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 3 + bufferOffset, &user ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 4 + bufferOffset, &domain ); } if ( siResult != eDSNoErr ) throw( siResult ); // pack request if ( ConvertBinaryTo64( (char *)clientBlob, 16, encoded64Str ) != SASL_OK ) throw( (SInt32)eParameterError ); paramStrLen = strlen(userIDToGet) + strlen(encoded64Str) + strlen(user) + strlen(domain) + 4; paramStr = (char *) malloc( paramStrLen ); if ( paramStr == NULL ) throw( (SInt32)eMemoryError ); snprintf( paramStr, paramStrLen, "%s %s %s %s", userIDToGet, encoded64Str, user, domain ); // round-trip to password server result = SendFlushReadWithMutex( inContext, "GETNTLM2SESSKEY", paramStr, NULL, buf, sizeof(buf) ); if ( result.err != 0 ) throw( PWSErrToDirServiceError(result) ); // pack response encodedStrLen = strlen( buf ) - 4; if ( encodedStrLen < 24 ) throw( (SInt32)eDSAuthServerError ); stepData = (char *) malloc( encodedStrLen ); if ( stepData == NULL ) throw( (SInt32)eMemoryError ); // decode if ( Convert64ToBinary( buf + 4, stepData, encodedStrLen, &decodedStrLen ) != SASL_OK ) throw( (SInt32)eDSAuthServerError ); // decrypt gSASLMutex->Wait(); saslResult = sasl_decode( inContext->conn, stepData, decodedStrLen, &decryptedStr, &decryptedStrLen ); gSASLMutex->Signal(); if ( saslResult != SASL_OK ) throw( (SInt32)eDSAuthFailed ); if ( outBuf->fBufferSize < decryptedStrLen + 4 ) throw( (SInt32)eDSBufferTooSmall ); outBuf->fBufferLength = decryptedStrLen + 4; decodedStrLen = decryptedStrLen; memcpy( outBuf->fBufferData, &decodedStrLen, 4 ); memcpy( outBuf->fBufferData + 4, decryptedStr, decryptedStrLen ); if ( inAuthMethod == kAuthNTLMv2WithSessionKey ) { char *userName; char *passwordStr; UInt32 passwordLen; char *challenge; // now do the NTLMv2 auth siResult = UnpackUsernameAndPassword( inContext, kAuthNTLMv2, inData->fInAuthStepData, &userName, &passwordStr, &passwordLen, &challenge ); if ( siResult != eDSNoErr ) throw( siResult ); if ( inData->fIOContinueData == 0 ) { pContinue = (sPSContinueData *) calloc( 1, sizeof(sPSContinueData) ); Throw_NULL( pContinue, eMemoryError ); gContinue->AddItem( pContinue, inData->fInNodeRef ); inData->fIOContinueData = pContinue; } siResult = DoSASLAuth( inContext, userName, passwordStr, passwordLen, challenge, "SMB-NTLMv2", inData, &stepData ); } else { siResult = inAuthenticatorAuthResult; } } catch( SInt32 error ) { siResult = error; } catch( ... ) { siResult = eDSAuthFailed; } if ( userIDToGet != NULL ) free( userIDToGet ); if ( user != NULL ) free( user ); if ( domain != NULL ) free( domain ); if ( paramStr != NULL ) free( paramStr ); if ( stepData != NULL ) free( stepData ); return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodGetStats // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodGetStats( sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; char *sampleCountStr = NULL; sPSContinueData *pContinue = NULL; char *statDataBuff = NULL; unsigned long statDataReceived = 0; unsigned long statDataLen = 0; char commandBuff[256] = {0,}; int sampleCount = 0; try { if ( outBuf->fBufferSize < 5 ) throw( (SInt32)eDSBufferTooSmall ); // repeat visit? if ( inData->fIOContinueData != 0 ) { // already verified in DoAuthentication() pContinue = (sPSContinueData *)inData->fIOContinueData; statDataLen = pContinue->fDataLen - pContinue->fDataPos; if ( 4 + statDataLen > outBuf->fBufferSize ) statDataLen = outBuf->fBufferSize - 4; memcpy( outBuf->fBufferData, &statDataLen, 4 ); memcpy( outBuf->fBufferData + 4, pContinue->fData + pContinue->fDataPos, statDataLen ); outBuf->fBufferLength = 4 + statDataLen; pContinue->fDataPos += statDataLen; if ( pContinue->fDataPos >= pContinue->fDataLen ) { // we are done gContinue->RemoveItem( pContinue ); inData->fIOContinueData = 0; } } else { siResult = GetStringFromAuthBuffer( inData->fInAuthStepData, 1, &sampleCountStr ); if ( siResult != eDSNoErr ) throw( siResult ); if ( DSIsStringEmpty(sampleCountStr) ) { strcpy( commandBuff, "GETSTATS" ); } else { sscanf( sampleCountStr, "%d", &sampleCount ); if ( sampleCount <= 0 ) throw( (SInt32)eDSInvalidBuffFormat ); snprintf( commandBuff, sizeof(commandBuff), "GETSTATS %d", sampleCount ); } siResult = GetLargeReplyFromServer( commandBuff, inContext, &statDataBuff, &statDataReceived ); if ( siResult != eDSNoErr ) throw( siResult ); // Do we need a continue data? if ( 4 + statDataReceived > outBuf->fBufferSize ) { if ( inData->fIOContinueData == 0 ) { pContinue = (sPSContinueData *)::calloc( 1, sizeof( sPSContinueData ) ); Throw_NULL( pContinue, eMemoryError ); gContinue->AddItem( pContinue, inData->fInNodeRef ); inData->fIOContinueData = pContinue; } else { throw( (SInt32)eDSInvalidContinueData ); } pContinue->fData = (unsigned char *) statDataBuff; pContinue->fDataLen = statDataReceived; pContinue->fDataPos = outBuf->fBufferSize - 4; memcpy( outBuf->fBufferData, &(pContinue->fDataPos), 4 ); memcpy( outBuf->fBufferData + 4, statDataBuff, pContinue->fDataPos ); outBuf->fBufferLength = 4 + pContinue->fDataPos; } else { memcpy( outBuf->fBufferData, &statDataReceived, 4 ); memcpy( outBuf->fBufferData + 4, statDataBuff, statDataReceived ); outBuf->fBufferLength = 4 + statDataReceived; free( statDataBuff ); statDataBuff = NULL; } } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } if ( sampleCountStr != NULL ) { free( sampleCountStr ); sampleCountStr = NULL; } return siResult; } // --------------------------------------------------------------------------- // * DoAuthMethodGetChangeList // --------------------------------------------------------------------------- SInt32 CPSPlugIn::DoAuthMethodGetChangeList( sDoDirNodeAuth *inData, sPSContextData *inContext, tDataBufferPtr outBuf ) { SInt32 siResult = eDSNoErr; sPSContinueData *pContinue = NULL; char *statDataBuff = NULL; unsigned long statDataReceived = 0; unsigned long statDataLen = 0; try { if ( outBuf->fBufferSize < 5 ) throw( (SInt32)eDSBufferTooSmall ); // repeat visit? if ( inData->fIOContinueData != 0 ) { // already verified in DoAuthentication() pContinue = (sPSContinueData *)inData->fIOContinueData; statDataLen = pContinue->fDataLen - pContinue->fDataPos; if ( 4 + statDataLen > outBuf->fBufferSize ) statDataLen = outBuf->fBufferSize - 4; memcpy( outBuf->fBufferData, &statDataLen, 4 ); memcpy( outBuf->fBufferData + 4, pContinue->fData + pContinue->fDataPos, statDataLen ); outBuf->fBufferLength = 4 + statDataLen; pContinue->fDataPos += statDataLen; if ( pContinue->fDataPos >= pContinue->fDataLen ) { // we are done gContinue->RemoveItem( pContinue ); inData->fIOContinueData = 0; } } else { siResult = GetLargeReplyFromServer( "CHANGELIST", inContext, &statDataBuff, &statDataReceived ); if ( siResult != eDSNoErr ) throw( siResult ); // Do we need a continue data? if ( 4 + statDataReceived > outBuf->fBufferSize ) { if ( inData->fIOContinueData == 0 ) { pContinue = (sPSContinueData *)::calloc( 1, sizeof( sPSContinueData ) ); Throw_NULL( pContinue, eMemoryError ); gContinue->AddItem( pContinue, inData->fInNodeRef ); inData->fIOContinueData = pContinue; } else { throw( (SInt32)eDSInvalidContinueData ); } pContinue->fData = (unsigned char *) statDataBuff; pContinue->fDataLen = statDataReceived; pContinue->fDataPos = outBuf->fBufferSize - 3; memcpy( outBuf->fBufferData, &(pContinue->fDataPos), 4 ); memcpy( outBuf->fBufferData + 4, statDataBuff + 1, pContinue->fDataPos - 1 ); outBuf->fBufferLength = 4 + pContinue->fDataPos; } else { memcpy( outBuf->fBufferData, &statDataReceived, 4 ); memcpy( outBuf->fBufferData + 4, statDataBuff + 1, statDataReceived ); outBuf->fBufferLength = 4 + statDataReceived; free( statDataBuff ); statDataBuff = NULL; } } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } return siResult; } #pragma mark - // --------------------------------------------------------------------------- // * SetServiceInfo // // Returns: DS error // // Checks for service information in the second dsDoDirNodeAuth() buffer. // Returns eDSNoErr if the information is not included; only returns an // error if the data is provided but invalid. // --------------------------------------------------------------------------- SInt32 CPSPlugIn::SetServiceInfo( sDoDirNodeAuth *inData, sPSContextData *inContext ) { SInt32 siResult = eDSNoErr; char *plistStr = NULL; char *infoStr = NULL; char *base64Str = NULL; UInt32 plistStrLen = 0; CFDataRef plistData = NULL; CFDictionaryRef plistDict = NULL; CFDictionaryRef infoDict = NULL; CFDataRef infoData = NULL; CFIndex infoDataLength = 0; CFStringRef errorString = NULL; do { DSFreeString( inContext->serviceInfoStr ); if ( GetStringFromAuthBuffer(inData->fOutAuthStepDataResponse, 1, &plistStr) == eDSNoErr && plistStr != NULL ) { // make sure we got a plist plistStrLen = strlen( plistStr ); plistData = CFDataCreate( kCFAllocatorDefault, (const unsigned char *)plistStr, plistStrLen ); if ( plistData == NULL ) { siResult = eMemoryError; break; } plistDict = (CFDictionaryRef) CFPropertyListCreateFromXMLData( kCFAllocatorDefault, plistData, kCFPropertyListImmutable, &errorString ); if ( plistDict == NULL || CFGetTypeID(plistDict) != CFDictionaryGetTypeID() ) { DEBUGLOG( "CPSPlugIn::SetServiceInfo(): data from service is not in dictionary form." ); siResult = eDSInvalidBuffFormat; break; } // Extract the ServiceInformation dictionary if ( CFDictionaryGetValueIfPresent(plistDict, CFSTR("ServiceInformation"), (const void **)&infoDict) && infoDict != NULL ) { if ( CFGetTypeID(infoDict) != CFDictionaryGetTypeID() ) { DEBUGLOG( "CPSPlugIn::SetServiceInfo(): ServiceInformation from service is not in dictionary form." ); siResult = eDSInvalidBuffFormat; break; } infoData = CFPropertyListCreateXMLData( kCFAllocatorDefault, infoDict ); if ( infoData != NULL ) { infoDataLength = CFDataGetLength( infoData ); if ( infoDataLength == 0 ) { DEBUGLOG( "CPSPlugIn::SetServiceInfo(): ServiceInformation from service was sent but the dictionary is empty." ); siResult = eDSInvalidBuffFormat; break; } infoStr = (char *) calloc( 1, infoDataLength + 1 ); if ( infoStr == NULL ) { siResult = eMemoryError; break; } CFDataGetBytes( infoData, CFRangeMake(0,infoDataLength), (UInt8 *)infoStr ); // The plist is valid, convert to base-64. base64Str = (char *) malloc( ((infoDataLength + 3) * 4 / 3) + 20 ); if ( base64Str == NULL ) { siResult = eMemoryError; break; } ConvertBinaryTo64( infoStr, infoDataLength, base64Str ); inContext->serviceInfoStr = base64Str; } } inData->fOutAuthStepDataResponse->fBufferLength = 0; } } while (0); DSCFRelease( plistDict ); DSCFRelease( plistData ); DSCFRelease( errorString ); DSCFRelease( infoData ); DSFreeString( infoStr ); DSFreeString( plistStr ); return siResult; } // --------------------------------------------------------------------------- // * GetReplicaListFromServer // // Returns: DS error // --------------------------------------------------------------------------- SInt32 CPSPlugIn::GetReplicaListFromServer( sPSContextData *inContext, char **outData, unsigned long *outDataLen ) { return GetLargeReplyFromServer( "LISTREPLICAS", inContext, outData, outDataLen ); } // --------------------------------------------------------------------------- // * GetLargeReplyFromServer // // Returns: DS error // --------------------------------------------------------------------------- SInt32 CPSPlugIn::GetLargeReplyFromServer( const char *inCommand, sPSContextData *inContext, char **outData, unsigned long *outDataLen ) { SInt32 siResult = eDSNoErr; char *replicaDataBuff = NULL; bool moreData = false; unsigned long replicaDataLen = 0; unsigned long replicaDataReceived = 0; unsigned long replicaDataOneTimeLen = 0; char * bufPtr; fd_set fdset; PWServerError result; struct timeval waitMax = { 10, 0 }; char buf[kOneKBuffer]; if ( outData == NULL || outDataLen == NULL ) return eParameterError; *outData = NULL; *outDataLen = 0; try { gPWSConnMutex->Wait(); result = SendFlush( inContext, inCommand, NULL, NULL ); if ( result.err == 0 ) { // LISTREPLICAS always comes back without encryption result = readFromServer( inContext->fd, buf, sizeof(buf) ); } gPWSConnMutex->Signal(); if ( result.err != 0 ) throw( PWSErrToDirServiceError(result) ); sscanf( buf + 4, "%lu", &replicaDataLen ); bufPtr = strchr( buf + 4, ' ' ); if ( bufPtr == NULL ) throw( (SInt32)eDSAuthServerError ); replicaDataReceived = strlen( ++bufPtr ); replicaDataBuff = (char *) malloc( replicaDataLen + 20 ); Throw_NULL( replicaDataBuff, eMemoryError ); memcpy( replicaDataBuff, bufPtr, replicaDataReceived ); FD_ZERO( &fdset ); FD_SET( inContext->fd, &fdset ); while ( replicaDataReceived < replicaDataLen ) { moreData = select( FD_SETSIZE, &fdset, NULL, NULL, &waitMax ); if ( moreData <= 0 ) break; result = readFromServer( inContext->fd, buf, sizeof(buf) ); if ( result.err != 0 ) throw( PWSErrToDirServiceError(result) ); replicaDataOneTimeLen = strlen( buf ); memcpy( replicaDataBuff + replicaDataReceived, buf, replicaDataOneTimeLen ); replicaDataReceived += replicaDataOneTimeLen; } if ( replicaDataReceived == replicaDataLen + 2 ) { // all went well, take off the /r/n at the end of the line replicaDataReceived -= 2; // allow data to be used as a c-string *(replicaDataBuff + replicaDataReceived) = '\0'; // attach the results *outData = replicaDataBuff; *outDataLen = replicaDataReceived; } } catch( SInt32 error ) { siResult = error; } catch( ... ) { } if ( siResult != eDSNoErr && replicaDataBuff != NULL ) { free( replicaDataBuff ); *outData = NULL; } return siResult; } // --------------------------------------------------------------------------- // * UseCurrentAuthenticationIfPossible // // Returns: eDSNoErr, eParameterError or eMemoryError // // If the user account has not changed, set <inOutHasValidAuth> by auth // method, depending on whether or not that method allows reuse. Generally, // administrative auth methods are reuseable, and methods requiring secure // sessions or plain auths are not. // If the user account has changed, switch back if the last // authenticated user was not authOnly. // --------------------------------------------------------------------------- SInt32 CPSPlugIn::UseCurrentAuthenticationIfPossible( sPSContextData *inContext, const char *inUserName, UInt32 inAuthMethod, Boolean *inOutHasValidAuth ) { SInt32 siResult = eDSNoErr; char *strippedUserName = NULL; long len; if ( inContext == NULL || inOutHasValidAuth == NULL ) return eParameterError; *inOutHasValidAuth = false; try { if ( (inContext->last.successfulAuth || inContext->nao.successfulAuth) && inUserName != NULL ) { len = strlen( inUserName ); strippedUserName = (char *) malloc( len + 1 ); Throw_NULL( strippedUserName, eMemoryError ); strcpy( strippedUserName, inUserName ); StripRSAKey( strippedUserName ); if ( strcmp( strippedUserName, inContext->last.username ) == 0 ) { // if the name is a match for the last authentication, then // the state is correct. switch ( inAuthMethod ) { case kAuthGetPolicy: case kAuthSetPolicy: case kAuthGetGlobalPolicy: case kAuthSetGlobalPolicy: case kAuthGetUserName: case kAuthSetUserName: case kAuthGetUserData: case kAuthSetUserData: case kAuthDeleteUser: case kAuthGetIDByName: case kAuthSyncSetupReplica: case kAuthListReplicas: case kAuthGetEffectivePolicy: case kAuthGetKerberosPrincipal: case kAuthMSLMCHAP2ChangePasswd: case kAuthVPN_PPTPMasterKeys: case kAuthGetStats: case kAuthGetChangeList: *inOutHasValidAuth = true; break; case kAuthNewUser: case kAuthNewUserWithPolicy: case kAuthNewComputer: case kAuthSetPasswdAsRoot: case kAuthSetComputerAcctPasswdAsRoot: case kAuthSetPolicyAsRoot: case kAuthSMB_NTUserSessionKey: case kAuthNTSessionKey: case kAuthNTLMv2WithSessionKey: case kAuthSMBWorkstationCredentialSessionKey: case kAuthNTSetWorkstationPasswd: case kAuthEncryptToUser: case kAuthDecrypt: case kAuthSetLMHash: case kAuthNTLMv2SessionKey: *inOutHasValidAuth = inContext->last.methodCanSetPassword; break; default: *inOutHasValidAuth = false; } } else if ( inContext->nao.successfulAuth && strcmp( strippedUserName, inContext->nao.username ) == 0 ) { // if the name is a match for the saved authentication (but // not the last one), then the state needs to be reset memcpy( inContext->last.username, inContext->nao.username, kMaxUserNameLength + 1 ); if ( inContext->last.password != NULL ) { bzero( inContext->last.password, inContext->last.passwordLen ); free( inContext->last.password ); inContext->last.password = NULL; inContext->last.passwordLen = 0; } inContext->last.password = (char *) malloc( inContext->nao.passwordLen + 1 ); Throw_NULL( inContext->last.password, eMemoryError ); memcpy( inContext->last.password, inContext->nao.password, inContext->nao.passwordLen ); inContext->last.password[inContext->nao.passwordLen] = '\0'; inContext->last.passwordLen = inContext->nao.passwordLen; } } } catch( SInt32 error ) { siResult = error; } if ( strippedUserName != NULL ) free( strippedUserName ); return siResult; } // --------------------------------------------------------------------------- // * PackStepBuffer // // Loads the return buffer in standard format ( 4 byte length + data ... ). // The <inUseBuffPlus4> parameter will strip "+OK " from arg1. // --------------------------------------------------------------------------- SInt32 CPSPlugIn::PackStepBuffer( const char *inArg1, bool inUseBuffPlus4, const char *inArg2, const char *inArg3, const char *inArg4, tDataBufferPtr inOutDataBuffer ) { unsigned long bufferLength = 0; unsigned long lengthOfArg[4] = { 0, 0, 0, 0 }; const char *arg[4] = { inArg1, inArg2, inArg3, inArg4 }; int argIndex; if ( inArg1 == NULL ) return eParameterError; if ( inUseBuffPlus4 ) { if ( strncmp( inArg1, "+OK ", 4 ) != 0 ) return eDSAuthFailed; arg[0] = inArg1 + 4; } for ( argIndex = 0; argIndex < 4 && arg[argIndex] != NULL; argIndex++ ) { lengthOfArg[argIndex] = strlen( arg[argIndex] ); bufferLength = 4 + lengthOfArg[argIndex]; } if ( bufferLength > inOutDataBuffer->fBufferSize ) return eDSBufferTooSmall; inOutDataBuffer->fBufferLength = 0; for ( argIndex = 0; argIndex < 4 && arg[argIndex] != NULL; argIndex++ ) { // put a 4-byte length in the buffer memcpy( inOutDataBuffer->fBufferData + inOutDataBuffer->fBufferLength, &(lengthOfArg[argIndex]), 4 ); inOutDataBuffer->fBufferLength += 4; // put the data in the buffer memcpy( inOutDataBuffer->fBufferData + inOutDataBuffer->fBufferLength, arg[argIndex], lengthOfArg[argIndex] ); inOutDataBuffer->fBufferLength += lengthOfArg[argIndex]; } return eDSNoErr; } // --------------------------------------------------------------------------- // * UnpackUsernameAndPassword // // --------------------------------------------------------------------------- SInt32 CPSPlugIn::UnpackUsernameAndPassword( sPSContextData *inContext, UInt32 uiAuthMethod, tDataBufferPtr inAuthBuf, char **outUserName, char **outPassword, UInt32 *outPasswordLen, char **outChallenge ) { SInt32 siResult = eDSNoErr; unsigned char *challenge = NULL; unsigned char *digest = NULL; char *method = NULL; char *domain = NULL; char *user = NULL; UInt32 len = 0; // sanity if ( outUserName == NULL || outPassword == NULL || outPasswordLen == NULL || outChallenge == NULL ) return eParameterError; // init vars *outUserName = NULL; *outPassword = NULL; *outPasswordLen = 0; *outChallenge = NULL; try { switch (uiAuthMethod) { case kAuthPull: case kAuthPush: case kAuthProcessNoReply: case kAuthGetStats: case kAuthGetChangeList: // these methods do not have user names return eDSNoErr; break; case kAuthAPOP: siResult = Get2StringsFromAuthBuffer( inAuthBuf, outUserName, (char **)&challenge ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inAuthBuf, 3, (char **)&digest ); if ( siResult == eDSNoErr ) { if ( challenge == NULL || digest == NULL ) throw( (SInt32)eDSAuthParameterError ); long challengeLen = strlen((char *)challenge); long digestLen = strlen((char *)digest); if ( challengeLen > 0 && digestLen > 0 ) { *outPasswordLen = challengeLen + 1 + digestLen; *outPassword = (char *) malloc( *outPasswordLen + 1 ); Throw_NULL( (*outPassword), eMemoryAllocError ); strcpy( *outPassword, (char *)challenge ); strcat( *outPassword, " " ); strcat( *outPassword, (char *)digest ); } } break; case kAuthDIGEST_MD5_Reauth: siResult = GetStringFromAuthBuffer( inAuthBuf, 1, outUserName ); if ( siResult == eDSNoErr ) siResult = GetDataFromAuthBuffer( inAuthBuf, 2, &digest, &len ); if ( siResult == eDSNoErr && digest != NULL ) { *outPassword = (char *) malloc( len + 1 ); Throw_NULL( (*outPassword), eMemoryAllocError ); // put a leading null to tell the DIGEST-MD5 plug-in we're sending // a hash. **outPassword = '\0'; memcpy( (*outPassword) + 1, digest, len ); *outPasswordLen = len + 1; } break; case kAuthDIGEST_MD5: siResult = Get2StringsFromAuthBuffer( inAuthBuf, outUserName, (char **)&challenge ); if ( siResult == eDSNoErr ) siResult = GetDataFromAuthBuffer( inAuthBuf, 3, &digest, &len ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inAuthBuf, 4, &method ); if ( siResult == eDSNoErr && digest != NULL && method != NULL ) { *outPassword = (char *) malloc( len + 1 ); Throw_NULL( (*outPassword), eMemoryAllocError ); // put a leading null to tell the WEBDAV-DIGEST plug-in we're sending // a hash. **outPassword = '\0'; memcpy( (*outPassword) + 1, digest, len ); *outPasswordLen = len + 1; *outChallenge = (char *) malloc( strlen((char *)challenge) + sizeof(kPWSPlugInDigestMethodStr) + strlen(method) + 1 ); strcpy( *outChallenge, (char *)challenge ); strcat( *outChallenge, kPWSPlugInDigestMethodStr ); strcat( *outChallenge, method ); strcat( *outChallenge, "\"" ); } break; case kAuthMSCHAP2: siResult = Get2StringsFromAuthBuffer( inAuthBuf, outUserName, (char **)&challenge ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inAuthBuf, 3, &method ); if ( siResult == eDSNoErr ) siResult = GetDataFromAuthBuffer( inAuthBuf, 4, &digest, &len ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inAuthBuf, 5, &user ); if ( siResult == eDSNoErr && challenge != NULL && digest != NULL && method != NULL && len == 24 && user != NULL ) { *outPassword = (char *) calloc( 1, 65 + strlen(user) + 1 ); Throw_NULL( (*outPassword), eMemoryAllocError ); memcpy( (*outPassword), challenge, 16 ); memcpy( (*outPassword) + 16, method, 16 ); memcpy( (*outPassword) + 40, digest, len ); strcpy( (*outPassword) + 65, user ); *outPasswordLen = 65 + strlen( user ); } break; case kAuthNTLMv2: siResult = GetStringFromAuthBuffer( inAuthBuf, 1, outUserName ); if ( siResult == eDSNoErr ) { UInt32 challengeLen = 0; long userLen = 0; long domainLen = 0; siResult = GetDataFromAuthBuffer( inAuthBuf, 2, &challenge, &challengeLen ); if ( siResult == eDSNoErr ) siResult = GetDataFromAuthBuffer( inAuthBuf, 3, &digest, &len ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inAuthBuf, 4, &user ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inAuthBuf, 5, &domain ); // blob length is variable, but must contain at least: // [digest (16 bytes) + 0x01010000 (4 bytes) + 0x00000000 (4 bytes) + Timestamp (8 bytes) + // client challenge (8 bytes) + unknown (4 bytes)] // LMv2 blobs are only 8 bytes. if ( siResult == eDSNoErr && challenge != NULL && digest != NULL && len >= 24 && user != NULL && domain != NULL ) { userLen = strlen( user ); domainLen = strlen( domain ); *outPassword = (char *) calloc( 1, userLen + 1 + domainLen + 1 + challengeLen + len + 1 ); Throw_NULL( (*outPassword), eMemoryAllocError ); strcpy( (*outPassword), user ); strcpy( (*outPassword) + userLen + 1, domain ); memcpy( (*outPassword) + userLen + 1 + domainLen + 1, challenge, challengeLen ); memcpy( (*outPassword) + userLen + 1 + domainLen + 1 + challengeLen, digest, len ); *outPasswordLen = userLen + 1 + domainLen + 1 + challengeLen + len; } } break; case kAuthCRAM_MD5: siResult = Get2StringsFromAuthBuffer( inAuthBuf, outUserName, outChallenge ); if ( siResult == eDSNoErr ) siResult = GetDataFromAuthBuffer( inAuthBuf, 3, &digest, &len ); if ( siResult == eDSNoErr && digest != NULL ) { *outPassword = (char *) malloc( len + 1 ); Throw_NULL( (*outPassword), eMemoryAllocError ); // put a leading null to tell the CRAM-MD5 plug-in we're sending // a hash. **outPassword = '\0'; memcpy( (*outPassword) + 1, digest, len ); *outPasswordLen = len + 1; } break; case kAuthSMB_NT_Key: case kAuthSMB_LM_Key: siResult = GetStringFromAuthBuffer( inAuthBuf, 1, outUserName ); if ( siResult == eDSNoErr ) { *outPassword = (char *)malloc(32); Throw_NULL( (*outPassword), eMemoryAllocError ); *outPasswordLen = 32; siResult = GetDataFromAuthBuffer( inAuthBuf, 2, &challenge, &len ); if ( siResult != eDSNoErr || challenge == NULL || len != 8 ) throw( (SInt32)eDSInvalidBuffFormat ); siResult = GetDataFromAuthBuffer( inAuthBuf, 3, &digest, &len ); if ( siResult != eDSNoErr || digest == NULL || len != 24 ) throw( (SInt32)eDSInvalidBuffFormat ); memcpy( *outPassword, challenge, 8 ); memcpy( (*outPassword) + 8, digest, 24 ); free( challenge ); challenge = NULL; free( digest ); digest = NULL; } break; case kAuth2WayRandom: // for 2way random the first buffer is the username if ( inAuthBuf->fBufferLength > inAuthBuf->fBufferSize ) throw( (SInt32)eDSInvalidBuffFormat ); *outUserName = (char*)calloc( inAuthBuf->fBufferLength + 1, 1 ); strlcpy( *outUserName, inAuthBuf->fBufferData, inAuthBuf->fBufferLength + 1 ); break; case kAuth2WayRandomChangePass: siResult = GetStringFromAuthBuffer( inAuthBuf, 1, outUserName ); if ( siResult == eDSNoErr ) { char *tempPWStr = NULL; siResult = GetStringFromAuthBuffer( inAuthBuf, 2, &tempPWStr ); if ( siResult == eDSNoErr && tempPWStr != NULL && strlen(tempPWStr) == 8 ) { *outPasswordLen = 16; *outPassword = (char *)malloc(16); memcpy( *outPassword, tempPWStr, 8 ); free( tempPWStr ); siResult = GetStringFromAuthBuffer( inAuthBuf, 3, &tempPWStr ); if ( siResult == eDSNoErr && tempPWStr != NULL && strlen(tempPWStr) == 8 ) { memcpy( *outPassword + 8, tempPWStr, 8 ); free( tempPWStr ); } } } break; case kAuthSetPasswd: siResult = UnpackUsernameAndPasswordAtOffset( inAuthBuf, 3, outUserName, outPassword, outPasswordLen ); break; case kAuthNTSessionKey: siResult = UnpackUsernameAndPasswordAtOffset( inAuthBuf, 4, outUserName, outPassword, outPasswordLen ); break; case kAuthNTLMv2WithSessionKey: siResult = UnpackUsernameAndPasswordAtOffset( inAuthBuf, 6, outUserName, outPassword, outPasswordLen ); break; case kAuthSetPasswdAsRoot: case kAuthSetComputerAcctPasswdAsRoot: case kAuthSetPolicyAsRoot: case kAuthSMB_NTUserSessionKey: case kAuthSMBWorkstationCredentialSessionKey: case kAuthNTSetWorkstationPasswd: case kAuthVPN_PPTPMasterKeys: case kAuthEncryptToUser: case kAuthDecrypt: case kAuthMSLMCHAP2ChangePasswd: case kAuthSetLMHash: case kAuthNTLMv2SessionKey: // uses current credentials if ( inContext->nao.successfulAuth && inContext->nao.password != NULL ) { long pwLen; *outUserName = (char *)malloc(kUserIDLength + 1); strlcpy(*outUserName, inContext->nao.username, kUserIDLength + 1); pwLen = strlen(inContext->nao.password); *outPassword = (char *)malloc(pwLen + 1); strncpy(*outPassword, inContext->nao.password, pwLen + 1); *outPasswordLen = pwLen; siResult = eDSNoErr; } else { siResult = eDSNotAuthorized; } break; default: siResult = UnpackUsernameAndPasswordDefault( inAuthBuf, outUserName, outPassword, outPasswordLen ); } } catch ( SInt32 error ) { siResult = error; } catch (...) { DEBUGLOG( "PasswordServer PlugIn: uncasted throw" ); siResult = eDSAuthFailed; } if ( challenge != NULL ) { free( challenge ); challenge = NULL; } if ( digest != NULL ) { free( digest ); digest = NULL; } if ( method != NULL ) { free( method ); method = NULL; } if ( domain != NULL ) { free( domain ); domain = NULL; } if ( user != NULL ) { free( user ); user = NULL; } // user name is a required value // kAuth2WayRandom is multi-pass and only has a username for pass 1 if ( siResult == eDSNoErr && *outUserName == NULL && uiAuthMethod != kAuth2WayRandom ) siResult = eDSUserUnknown; return siResult; } SInt32 CPSPlugIn::UnpackUsernameAndPasswordDefault( tDataBufferPtr inAuthBuf, char **outUserName, char **outPassword, UInt32 *outPasswordLen ) { SInt32 siResult = eDSNoErr; siResult = Get2StringsFromAuthBuffer( inAuthBuf, outUserName, outPassword ); if ( siResult == eDSNoErr && *outPassword != NULL ) *outPasswordLen = strlen( *outPassword ); if ( *outPassword == NULL || *outPasswordLen == 0 ) { if ( *outPassword != NULL ) free( *outPassword ); *outPasswordLen = strlen( kEmptyPasswordAltStr ); *outPassword = strdup( kEmptyPasswordAltStr ); } return siResult; } SInt32 CPSPlugIn::UnpackUsernameAndPasswordAtOffset( tDataBufferPtr inAuthBuf, UInt32 inUserItem, char **outUserName, char **outPassword, UInt32 *outPasswordLen ) { SInt32 siResult = eDSNoErr; siResult = GetStringFromAuthBuffer( inAuthBuf, inUserItem, outUserName ); if ( siResult == eDSNoErr ) siResult = GetStringFromAuthBuffer( inAuthBuf, inUserItem + 1, outPassword ); if ( siResult == eDSNoErr && *outPassword != NULL ) *outPasswordLen = strlen( *outPassword ); if ( *outPassword == NULL || *outPasswordLen == 0 ) { if ( *outPassword != NULL ) free( *outPassword ); *outPasswordLen = strlen( kEmptyPasswordAltStr ); *outPassword = strdup( kEmptyPasswordAltStr ); } return siResult; } // --------------------------------------------------------------------------- // * GetAuthMethodConstant // // Returns a constant that represents a DirectoryServices auth method. // If the auth method is a native type, this function also returns // the SASL mech name in outNativeAuthMethodSASLName. // --------------------------------------------------------------------------- SInt32 CPSPlugIn::GetAuthMethodConstant( sPSContextData *inContext, tDataNode *inData, UInt32 *outAuthMethod, char *outNativeAuthMethodSASLName ) { SInt32 siResult = eDSNoErr; char *p = NULL; SInt32 prefixLen; if ( inData == NULL ) { *outAuthMethod = kAuthUnknownMethod; return( eDSAuthParameterError ); } if ( outNativeAuthMethodSASLName != NULL ) *outNativeAuthMethodSASLName = '\0'; p = (char *)inData->fBufferData; DEBUGLOG( "PasswordServer PlugIn: Attempting use of authentication method %s", p ); prefixLen = strlen(kDSNativeAuthMethodPrefix); if ( ::strncmp( p, kDSNativeAuthMethodPrefix, prefixLen ) == 0 ) { *outAuthMethod = kAuthUnknownMethod; siResult = eDSAuthMethodNotSupported; p += prefixLen; // check for GetIDByName if ( strcmp( p, "dsAuthGetIDByName" ) == 0 ) { *outAuthMethod = kAuthGetIDByName; return eDSNoErr; } else if ( strcmp( p, "dsAuthGetDisabledUsers" ) == 0 ) { *outAuthMethod = kAuthGetDisabledUsers; return eDSNoErr; } else if ( strcmp( p, "dsAuthSyncSetupReplica" ) == 0 ) { *outAuthMethod = kAuthSyncSetupReplica; return eDSNoErr; } else if ( strcmp( p, "dsAuthListReplicas" ) == 0 ) { *outAuthMethod = kAuthListReplicas; return eDSNoErr; } else if ( strcmp( p, "dsAuthGetStats" ) == 0 ) { *outAuthMethod = kAuthGetStats; return eDSNoErr; } else if ( strcmp( p, "dsAuthGetChangeList" ) == 0 ) { *outAuthMethod = kAuthGetChangeList; return eDSNoErr; } else if ( strcmp( p, "dsAuthPull" ) == 0 ) { *outAuthMethod = kAuthPull; return eDSNoErr; } else if ( strcmp( p, "dsAuthPush" ) == 0 ) { *outAuthMethod = kAuthPush; return eDSNoErr; } else if ( strcmp( p, "dsAuthProcessNoReply" ) == 0 ) { *outAuthMethod = kAuthProcessNoReply; return eDSNoErr; } /* * we don't get the mech list up-front anymore */ /* for ( SInt32 index = inContext->mechCount - 1; index >= 0; index-- ) { if ( strcmp( p, inContext->mech[index].method ) == 0 ) { if ( outNativeAuthMethodSASLName != NULL ) strcpy( outNativeAuthMethodSASLName, inContext->mech[index].method ); *outAuthMethod = kAuthNativeMethod; siResult = eDSNoErr; break; } } */ } else { siResult = dsGetAuthMethodEnumValue( inData, outAuthMethod ); if ( siResult == eDSAuthMethodNotSupported ) { siResult = eDSNoErr; if ( strcmp( p, "dsAuthMethodStandard:dsAuthNodeDIGEST-MD5-Reauth" ) == 0 ) { *outAuthMethod = kAuthDIGEST_MD5_Reauth; } else if ( strcmp( p, kDSStdAuthSMBNTv2UserSessionKey ) == 0 || strcmp( p, "dsAuthMethodStandard:dsAuthNodeNTLMv2SessionKey" ) == 0 ) { *outAuthMethod = kAuthNTLMv2SessionKey; } else if ( strcmp( p, "dsAuthMethodStandard:dsAuthMSLMCHAP2ChangePasswd" ) == 0 ) { *outAuthMethod = kAuthMSLMCHAP2ChangePasswd; } else if ( strcmp( p, "dsAuthMethodStandard:dsAuthEncryptToUser" ) == 0 ) { *outAuthMethod = kAuthEncryptToUser; } else if ( strcmp( p, "dsAuthMethodStandard:dsAuthDecrypt" ) == 0 ) { *outAuthMethod = kAuthDecrypt; } else if ( ::strcmp( p, "dsAuthMethodStandard:dsAuthNodePPS" ) == 0 ) { *outAuthMethod = kAuthPPS; } else { *outAuthMethod = kAuthUnknownMethod; siResult = eDSAuthMethodNotSupported; } } } return( siResult ); } // GetAuthMethodConstant // --------------------------------------------------------------------------- // * RequiresSASLAuthentication // --------------------------------------------------------------------------- bool CPSPlugIn::RequiresSASLAuthentication( UInt32 inAuthMethodConstant ) { switch( inAuthMethodConstant ) { case kAuthGetPolicy: case kAuthGetGlobalPolicy: case kAuthGetIDByName: case kAuthGetDisabledUsers: case kAuth2WayRandomChangePass: case kAuthListReplicas: case kAuthPull: case kAuthPush: case kAuthProcessNoReply: case kAuthGetEffectivePolicy: case kAuthGetKerberosPrincipal: case kAuthGetStats: case kAuthGetChangeList: return false; default: return true; } return true; } // --------------------------------------------------------------------------- // * GetAuthMethodSASLName // // Returns the name of a SASL mechanism for // standard (kDSStdAuthMethodPrefix) auth mehthods // --------------------------------------------------------------------------- SInt32 CPSPlugIn::GetAuthMethodSASLName ( sPSContextData *inContext, UInt32 inAuthMethodConstant, bool inAuthOnly, char *outMechName, bool *outMethodCanSetPassword ) { SInt32 result = eDSNoErr; bool isNewEnough = false; if ( outMechName == NULL || outMethodCanSetPassword == NULL ) return -1; *outMechName = '\0'; *outMethodCanSetPassword = false; switch ( inAuthMethodConstant ) { case kAuthClearText: if ( inAuthOnly ) { strcpy( outMechName, "PLAIN " ); strcat( outMechName, fSASLMechPriority ); } else { strcpy( outMechName, kDHX_SASL_Name ); *outMethodCanSetPassword = true; } break; case kAuthCrypt: strcpy( outMechName, "CRYPT" ); break; case kAuthSetPasswd: case kAuthChangePasswd: case kAuthSetPasswdAsRoot: case kAuthSetComputerAcctPasswdAsRoot: case kAuthSMB_NTUserSessionKey: case kAuthSMBWorkstationCredentialSessionKey: case kAuthNTSetWorkstationPasswd: case kAuthVPN_PPTPMasterKeys: case kAuthEncryptToUser: case kAuthDecrypt: case kAuthSetLMHash: strcpy( outMechName, kDHX_SASL_Name ); *outMethodCanSetPassword = true; break; case kAuthAPOP: strcpy( outMechName, "APOP" ); break; case kAuth2WayRandom: strcpy( outMechName, "TWOWAYRANDOM" ); break; case kAuthNativeClearTextOK: // If <inAuthOnly> == false, then a "kDSStdSetPasswdAsRoot" auth method // could be called later and will require DHX strcpy( outMechName, inAuthOnly ? fSASLMechPriority : kDHX_SASL_Name ); strcat( outMechName, " PLAIN" ); if ( ! inAuthOnly ) *outMethodCanSetPassword = true; break; case kAuthNativeNoClearText: // If <inAuthOnly> == false, then a "kDSStdSetPasswdAsRoot" auth method // could be called later and will require DHX strcpy( outMechName, inAuthOnly ? fSASLMechPriority : kDHX_SASL_Name ); if ( ! inAuthOnly ) *outMethodCanSetPassword = true; break; case kAuthNativeRetainCredential: strcpy( outMechName, kDHX_SASL_Name ); *outMethodCanSetPassword = true; break; case kAuthSMB_NT_Key: strcpy( outMechName, "SMB-NT" ); break; case kAuthSMB_LM_Key: strcpy( outMechName, "SMB-LAN-MANAGER" ); break; case kAuthDIGEST_MD5: case kAuthDIGEST_MD5_Reauth: strcpy( outMechName, "WEBDAV-DIGEST" ); break; case kAuthMSCHAP2: strcpy( outMechName, "MS-CHAPv2" ); break; case kAuthNTLMv2: strcpy( outMechName, "SMB-NTLMv2" ); break; case kAuthCRAM_MD5: strcpy( outMechName, "CRAM-MD5" ); break; case kAuthPPS: strcpy( outMechName, "PPS" ); break; case kAuthGetPolicy: case kAuthSetPolicy: case kAuthSetPolicyAsRoot: case kAuthGetGlobalPolicy: case kAuthSetGlobalPolicy: case kAuthGetUserName: case kAuthSetUserName: case kAuthGetUserData: case kAuthSetUserData: case kAuthDeleteUser: case kAuthListReplicas: case kAuthGetEffectivePolicy: case kAuthGetKerberosPrincipal: case kAuthMSLMCHAP2ChangePasswd: case kAuthGetStats: case kAuthGetChangeList: strcpy( outMechName, fSASLMechPriority ); break; case kAuthNewUser: case kAuthNewUserWithPolicy: case kAuthNewComputer: case kAuthSyncSetupReplica: strcpy( outMechName, kDHX_SASL_Name ); *outMethodCanSetPassword = true; break; case kAuthNTSessionKey: case kAuthNTLMv2SessionKey: case kAuthNTLMv2WithSessionKey: isNewEnough = CheckServerVersionMin( inContext->serverVers, 10, 4, 5, 0 ); strcpy( outMechName, isNewEnough ? "DIGEST-MD5" : kDHX_SASL_Name ); break; case kAuthUnknownMethod: case kAuthNativeMethod: default: result = eDSAuthMethodNotSupported; } return result; } //------------------------------------------------------------------------------------ // * GetAuthMethodFromSASLName //------------------------------------------------------------------------------------ void CPSPlugIn::GetAuthMethodFromSASLName( const char *inMechName, char *outDSType ) { if ( outDSType == NULL ) return; *outDSType = '\0'; if ( inMechName != NULL ) { for ( int index = 0; gMethodMap[index].saslName != NULL; index++ ) { if ( strcmp(inMechName, gMethodMap[index].saslName) == 0 ) { strcpy( outDSType, gMethodMap[index].odName ); break; } } } } //------------------------------------------------------------------------------------ // * DoSASLNew //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::DoSASLNew( sPSContextData *inContext, sPSContinueData *inContinue ) { SInt32 ret = eDSNoErr; const char *localaddr = NULL; const char *remoteaddr = NULL; if ( inContext == NULL ) return eDSBadContextData; if ( inContinue == NULL ) return eDSAuthContinueDataBad; // clean up the old conn if ( inContext->conn != NULL ) { gSASLMutex->Wait(); sasl_dispose(&inContext->conn); gSASLMutex->Signal(); inContext->conn = NULL; } // callbacks we support inContext->callbacks[0].id = SASL_CB_GETREALM; inContext->callbacks[0].proc = (sasl_cbproc *)&getrealm; inContext->callbacks[0].context = inContext; inContext->callbacks[1].id = SASL_CB_USER; inContext->callbacks[1].proc = (sasl_cbproc *)&simple; inContext->callbacks[1].context = inContinue; inContext->callbacks[2].id = SASL_CB_AUTHNAME; inContext->callbacks[2].proc = (sasl_cbproc *)&simple; inContext->callbacks[2].context = inContinue; inContext->callbacks[3].id = SASL_CB_PASS; inContext->callbacks[3].proc = (sasl_cbproc *)&getsecret; inContext->callbacks[3].context = inContinue; inContext->callbacks[4].id = SASL_CB_LIST_END; inContext->callbacks[4].proc = NULL; inContext->callbacks[4].context = NULL; localaddr = (inContext->localaddr[0]) ? inContext->localaddr : "127.0.0.1;3659"; remoteaddr = (inContext->remoteaddr[0]) ? inContext->remoteaddr : "127.0.0.1;3659"; gSASLMutex->Wait(); ret = sasl_client_new( "rcmd", inContext->psName, localaddr, remoteaddr, inContext->callbacks, 0, &inContext->conn); gSASLMutex->Signal(); return ret; } //------------------------------------------------------------------------------------ // * DoSASLAuth //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::DoSASLAuth( sPSContextData *inContext, char *userName, const char *password, long inPasswordLen, const char *inChallenge, const char *inMechName, sDoDirNodeAuth *inData, char **outStepData ) { SInt32 siResult = eDSAuthFailed; sPSContinueData *pContinue = NULL; char *tptr = NULL; char *commandBuf = NULL; UInt32 commandBufLen = 0; DEBUGLOG( "CPSPlugIn::Attempting SASL Authentication"); try { Throw_NULL( inContext, eDSBadContextData ); Throw_NULL( password, eParameterError ); Throw_NULL( inData, eParameterError ); pContinue = (sPSContinueData *) inData->fIOContinueData; Throw_NULL( pContinue, eDSAuthContinueDataBad ); if ( outStepData != NULL ) *outStepData = NULL; // need username length, password length, and username must be at least 1 character // yes do it here { char buf[4096]; const char *data; char dataBuf[4096]; unsigned long binLen = 0; const char *chosenmech = NULL; unsigned int len = 0; int r; PWServerError serverResult; sasl_security_properties_t secprops = {0,65535,4096,0,NULL,NULL}; // attach the username and password to the sasl connection's context // set these before calling sasl_client_start if ( userName != NULL ) { long userNameLen; char *userNameEnd = strchr( userName, ',' ); if ( userNameEnd != NULL ) { userNameLen = userNameEnd - userName; if ( userNameLen >= kMaxUserNameLength ) throw( (SInt32)eDSAuthInvalidUserName ); strlcpy(inContext->last.username, userName, userNameLen + 1 ); } else { strlcpy( inContext->last.username, userName, kMaxUserNameLength ); } strcpy( pContinue->fUsername, inContext->last.username ); } // if not enough space, toss if ( inContext->last.password != NULL ) { bzero( inContext->last.password, inContext->last.passwordLen ); if ( inPasswordLen > inContext->last.passwordLen ) { free( inContext->last.password ); inContext->last.password = NULL; inContext->last.passwordLen = 0; } } // if first allocation, or not enough space in the old one, allocate if ( inContext->last.password == NULL ) { inContext->last.password = (char *) malloc( inPasswordLen + 1 ); Throw_NULL( inContext->last.password, eMemoryError ); } memcpy( inContext->last.password, password, inPasswordLen ); inContext->last.password[inPasswordLen] = '\0'; inContext->last.passwordLen = inPasswordLen; // now, make the struct ptr for this sasl session pContinue->fSASLSecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + inPasswordLen + 1); Throw_NULL( pContinue->fSASLSecret, eMemoryError ); pContinue->fSASLSecret->len = inPasswordLen; memcpy( pContinue->fSASLSecret->data, password, inPasswordLen ); r = DoSASLNew( inContext, pContinue ); if ( r != SASL_OK || inContext->conn == NULL ) { DEBUGLOG( "sasl_client_new failed, err=%d.", r); throw( SASLErrToDirServiceError(r) ); } gSASLMutex->Wait(); r = sasl_setprop(inContext->conn, SASL_SEC_PROPS, &secprops); r = sasl_client_start( inContext->conn, inMechName, NULL, &data, &len, &chosenmech ); gSASLMutex->Signal(); #if DEBUG { char *tmpData = (char *)malloc(len+1); memcpy(tmpData, data, len); tmpData[len] = '\0'; DEBUGLOG( "sasl_client_start=%d, start data=%s", r, tmpData); free(tmpData); } #endif //DEBUGLOG( "chosenmech=%s, datalen=%u", chosenmech, len); if ( r != SASL_OK && r != SASL_CONTINUE ) { DEBUGLOG( "starting SASL negotiation, err=%d", r); throw( SASLErrToDirServiceError(r) ); } // send the auth method dataBuf[0] = 0; if ( inChallenge != NULL ) { // for CRAM-MD5 and potentially DIGEST-MD5, we can attach the nonce to the // initial data. if ( strcmp(chosenmech, "WEBDAV-DIGEST") == 0 ) { strcpy(dataBuf, "replay "); ConvertBinaryToHex( (const unsigned char *)inChallenge, strlen(inChallenge), dataBuf+7 ); len = strlen(dataBuf); } else { ConvertBinaryToHex( (const unsigned char *)inChallenge, strlen(inChallenge), dataBuf ); len = strlen(dataBuf); } } else if ( len > 0 ) ConvertBinaryToHex( (const unsigned char *)data, len, dataBuf ); // set a user StripRSAKey( userName ); // build the command commandBufLen = sizeof("USER INFO AUTH ") + strlen(userName) + SASL_MECHNAMEMAX + len*2; if ( inContext->serviceInfoStr != NULL ) commandBufLen += strlen( inContext->serviceInfoStr ); commandBuf = (char *) malloc( commandBufLen ); if ( commandBuf == NULL ) throw( (SInt32)eMemoryError ); snprintf( commandBuf, commandBufLen, "USER %s", userName ); if ( inContext->serviceInfoStr != NULL && CheckServerVersionMin(inContext->serverVers, 10, 5, 0, 0) ) { strlcat( commandBuf, " INFO ", commandBufLen ); strlcat( commandBuf, inContext->serviceInfoStr, commandBufLen ); } strlcat( commandBuf, " AUTH ", commandBufLen ); strlcat( commandBuf, chosenmech, commandBufLen ); if ( len > 0 ) { strlcat( commandBuf, " ", commandBufLen ); strlcat( commandBuf, dataBuf, commandBufLen ); } serverResult = SendFlushReadWithMutex( inContext, commandBuf, NULL, NULL, buf, sizeof(buf) ); if (serverResult.err != 0) { DEBUGLOG( "server returned an error, err=%d", serverResult.err); throw( PWSErrToDirServiceError(serverResult) ); } sasl_chop(buf); len = strlen(buf); // check for old server if ( (len >= 3 && strncmp(buf, "+OK", 3) == 0) || (len >= 4 && strncmp(buf, "-ERR", 4) == 0) ) { if ( len > 0 ) snprintf(buf, sizeof(buf), "AUTH %s %s", chosenmech, dataBuf); else snprintf(buf, sizeof(buf), "AUTH %s", chosenmech); serverResult = SendFlushReadWithMutex( inContext, buf, NULL, NULL, buf, sizeof(buf) ); if (serverResult.err != 0) { DEBUGLOG( "server returned an error, err=%d", serverResult.err); throw( PWSErrToDirServiceError(serverResult) ); } sasl_chop(buf); len = strlen(buf); } while ( r == SASL_CONTINUE ) { // skip the "+OK " at the begining of the response if ( (len >= 7 && strncmp(buf, "+AUTHOK ", 7) == 0) || (len >= 4 && strncmp(buf, "+OK ", 4) == 0) ) { tptr = strchr( buf, ' ' ); if ( tptr != NULL ) { ConvertHexToBinary( tptr + 1, (unsigned char *) dataBuf, &binLen ); #if DEBUG { char *tmpData = (char *)malloc(binLen+1); memcpy(tmpData, dataBuf, binLen); tmpData[binLen] = '\0'; DEBUGLOG( "server data=%s", tmpData); free(tmpData); } #endif gSASLMutex->Wait(); r = sasl_client_step(inContext->conn, dataBuf, binLen, NULL, &data, &len); #if DEBUG { char *tmpData = (char *)malloc(len+1); memcpy(tmpData, data, len); tmpData[len] = '\0'; DEBUGLOG( "step data=%s", tmpData); free(tmpData); } #endif gSASLMutex->Signal(); } else { // we are done data = NULL; len = 0; r = SASL_OK; } } else r = SASL_FAIL; if (r != SASL_OK && r != SASL_CONTINUE) { DEBUGLOG( "sasl_client_step=%d", r); throw( SASLErrToDirServiceError(r) ); } if (data && len != 0) { //DEBUGLOG( "sending response length %d", len); //DEBUGLOG( "client step data = %s", data ); ConvertBinaryToHex( (const unsigned char *)data, len, dataBuf ); DEBUGLOG( "AUTH2 %s", dataBuf); serverResult = SendFlushReadWithMutex( inContext, "AUTH2", dataBuf, NULL, buf, sizeof(buf) ); } else if (r==SASL_CONTINUE) { DEBUGLOG( "sending null response"); serverResult = SendFlushReadWithMutex( inContext, "AUTH2 ", NULL, NULL, buf, sizeof(buf) ); } else { break; } if ( serverResult.err != 0 ) { DEBUGLOG( "server returned an error, err=%d", serverResult.err); throw( PWSErrToDirServiceError(serverResult) ); } sasl_chop(buf); len = strlen(buf); if ( r != SASL_CONTINUE ) break; } if ( outStepData != NULL && binLen > 0 && dataBuf != NULL && (strcmp(chosenmech, "WEBDAV-DIGEST") == 0 || strcmp(chosenmech, "MS-CHAPv2") == 0) ) { *outStepData = (char *) malloc( binLen + 1 ); if ( *outStepData == NULL ) throw( (SInt32)eMemoryError ); memcpy( *outStepData, dataBuf, binLen ); (*outStepData)[binLen] = '\0'; //DEBUGLOG( "outStepData = %s", *outStepData ); } throw( SASLErrToDirServiceError(r) ); } } catch ( SInt32 err ) { DEBUGLOG( "PasswordServer PlugIn: SASL authentication error %l", err ); siResult = err; } catch ( ... ) { DEBUGLOG( "PasswordServer PlugIn: SASL uncasted authentication error" ); siResult = eDSAuthFailed; } if ( pContinue != NULL ) { // No 2-pass auths handled in this method so clean up now gContinue->RemoveItem( pContinue ); inData->fIOContinueData = 0; } DSFreeString( commandBuf ); return( siResult ); } // DoSASLAuth //------------------------------------------------------------------------------------ // * DoSASLTwoWayRandAuth //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::DoSASLTwoWayRandAuth( sPSContextData *inContext, const char *userName, const char *inMechName, sDoDirNodeAuth *inData ) { SInt32 siResult = eDSAuthFailed; char buf[4096]; const char *data; char dataBuf[4096]; const char *chosenmech = NULL; unsigned int len = 0; int r; PWServerError serverResult; sasl_security_properties_t secprops = {0,65535,4096,0,NULL,NULL}; sPSContinueData *pContinue = (sPSContinueData *) inData->fIOContinueData; tDataBufferPtr outAuthBuff = inData->fOutAuthStepDataResponse; tDataBufferPtr inAuthBuff = inData->fInAuthStepData; DEBUGLOG( "CPSPlugIn::DoSASLTwoWayRandAuth"); try { Throw_NULL( inContext, eDSBadContextData ); Throw_NULL( inMechName, eParameterError ); Throw_NULL( inData, eParameterError ); Throw_NULL( inAuthBuff, eDSNullAuthStepData ); Throw_NULL( outAuthBuff, eDSNullAuthStepDataResp ); Throw_NULL( pContinue, eDSAuthContinueDataBad ); if ( outAuthBuff->fBufferSize < 8 ) throw( (SInt32)eDSAuthResponseBufTooSmall ); // need username length, password length, and username must be at least 1 character // This information may not come in the first step, so check each step. DEBUGLOG( "PasswordServer PlugIn: Attempting Authentication" ); if ( pContinue->fAuthPass == 0 ) { // first pass contains the user name if ( userName != NULL ) { long userNameLen; char *userNameEnd = strchr( userName, ',' ); if ( userNameEnd != NULL ) { userNameLen = userNameEnd - userName; if ( userNameLen >= kMaxUserNameLength ) throw( (SInt32)eDSAuthInvalidUserName ); strlcpy(inContext->last.username, userName, userNameLen + 1 ); } else { strlcpy( inContext->last.username, userName, kMaxUserNameLength ); } } r = DoSASLNew( inContext, pContinue ); if ( r != SASL_OK || inContext->conn == NULL ) { DEBUGLOG( "sasl_client_new failed, err=%d.", r); throw( SASLErrToDirServiceError(r) ); } r = sasl_setprop(inContext->conn, SASL_SEC_PROPS, &secprops); // set a user snprintf(dataBuf, sizeof(dataBuf), "USER %s\r\n", userName); writeToServer(inContext->serverOut, dataBuf); // flush the read buffer serverResult = readFromServer( inContext->fd, buf, sizeof(buf) ); if (serverResult.err != 0) { DEBUGLOG( "server returned an error, err=%d", serverResult.err); throw( PWSErrToDirServiceError(serverResult) ); } // send the auth method snprintf(buf, sizeof(buf), "AUTH %s\r\n", inMechName); writeToServer(inContext->serverOut, buf); // get server response serverResult = readFromServer(inContext->fd, buf, sizeof(buf)); if (serverResult.err != 0) { DEBUGLOG( "server returned an error, err=%d", serverResult.err); throw( PWSErrToDirServiceError(serverResult) ); } sasl_chop(buf); len = strlen(buf); // skip the "+OK " at the begining of the response if ( len >= 3 && strncmp( buf, "+OK", 3 ) == 0 ) { if ( len > 4 ) { unsigned long binLen; unsigned long num1, num2; char *num2Ptr = NULL; unsigned char *saveData = NULL; ConvertHexToBinary( buf + 4, (unsigned char *) dataBuf, &binLen ); dataBuf[binLen] = '\0'; // save a copy for the next pass (do not trust the client) saveData = (unsigned char *) malloc(binLen + 1); Throw_NULL( saveData, eMemoryError ); memcpy(saveData, dataBuf, binLen+1); pContinue->fData = saveData; pContinue->fDataLen = binLen; // make an out buffer num2Ptr = strchr( dataBuf, ' ' ); if ( binLen < 3 || num2Ptr == NULL ) throw( (SInt32)eDSInvalidBuffFormat ); sscanf(dataBuf, "%lu", &num1); sscanf(num2Ptr+1, "%lu", &num2); outAuthBuff->fBufferLength = 8; memcpy(outAuthBuff->fBufferData, &num1, sizeof(long)); memcpy(outAuthBuff->fBufferData + sizeof(long), &num2, sizeof(long)); siResult = eDSNoErr; } else { // we're done, although it would be odd to finish here since // it's a multi-pass auth. data = NULL; len = 0; r = SASL_OK; } } else { r = SASL_FAIL; } } else if ( pContinue->fAuthPass == 1 ) { DEBUGLOG( "inAuthBuff->fBufferLength=%l", inAuthBuff->fBufferLength); // buffer should be: // 8 byte DES digest // 8 bytes of random if ( inAuthBuff->fBufferLength < 16 ) throw( (SInt32)eDSAuthInBuffFormatError ); // attach the username and password to the sasl connection's context // set these before calling sasl_client_start if ( inContext->last.password != NULL ) { bzero( inContext->last.password, inContext->last.passwordLen ); free( inContext->last.password ); inContext->last.password = NULL; inContext->last.passwordLen = 0; } inContext->last.password = (char *) malloc( inAuthBuff->fBufferLength ); Throw_NULL( inContext->last.password, eMemoryError ); memcpy( inContext->last.password, inAuthBuff->fBufferData, inAuthBuff->fBufferLength ); inContext->last.passwordLen = inAuthBuff->fBufferLength; // start sasling r = sasl_client_start( inContext->conn, inMechName, NULL, &data, &len, &chosenmech ); DEBUGLOG( "chosenmech=%s, datalen=%u", chosenmech, len); if ( r != SASL_OK && r != SASL_CONTINUE ) { DEBUGLOG( "starting SASL negotiation, err=%d", r); throw( SASLErrToDirServiceError(r) ); } r = sasl_client_step(inContext->conn, (const char *)pContinue->fData, pContinue->fDataLen, NULL, &data, &len); // clean up if ( pContinue->fData != NULL ) { free( pContinue->fData ); pContinue->fData = NULL; } pContinue->fDataLen = 0; if ( r != SASL_OK && r != SASL_CONTINUE ) { DEBUGLOG( "stepping SASL negotiation, err=%d", r); throw( SASLErrToDirServiceError(r) ); } if (data && len != 0) { ConvertBinaryToHex( (const unsigned char *)data, len, dataBuf ); DEBUGLOG( "AUTH2 %s", dataBuf); fprintf(inContext->serverOut, "AUTH2 %s\r\n", dataBuf ); fflush(inContext->serverOut); // get server response serverResult = readFromServer(inContext->fd, buf, sizeof(buf)); if (serverResult.err != 0) { DEBUGLOG( "server returned an error, err=%d", serverResult.err); throw( PWSErrToDirServiceError(serverResult) ); } sasl_chop(buf); len = strlen(buf); // make an out buffer if ( len > 4 ) { unsigned long binLen; ConvertHexToBinary( buf + 4, (unsigned char *)dataBuf, &binLen ); if ( binLen > outAuthBuff->fBufferSize ) throw( (SInt32)eDSAuthResponseBufTooSmall ); outAuthBuff->fBufferLength = binLen; memcpy(outAuthBuff->fBufferData, dataBuf, binLen); siResult = eDSNoErr; } } } else { // too many passes siResult = eDSAuthFailed; } } catch ( SInt32 err ) { DEBUGLOG( "PasswordServer PlugIn: SASL authentication error %l", err ); siResult = err; } catch ( ... ) { DEBUGLOG( "PasswordServer PlugIn: SASL uncasted authentication error" ); siResult = eDSAuthFailed; } if ( pContinue->fAuthPass == 1 ) { gContinue->RemoveItem( pContinue ); inData->fIOContinueData = 0; } else { pContinue->fAuthPass++; } return( siResult ); } //------------------------------------------------------------------------------------ // * DoSASLPPSAuth //------------------------------------------------------------------------------------ tDirStatus CPSPlugIn::DoSASLPPSAuth( sPSContextData *inContext, const char *inUserName, const char *inMechData, long inMechDataLen, sDoDirNodeAuth *inData ) { tDirStatus siResult = eDSAuthFailed; tDataBufferPtr inAuthBuff = inData->fInAuthStepData; tDataBufferPtr outAuthBuff = inData->fOutAuthStepDataResponse; sPSContinueData *pContinue = (sPSContinueData *) inData->fIOContinueData; PWServerError serverResult = {0}; unsigned long dataBufLen = 0; char *stepData = NULL; char buf[4096]; char dataBuf[4096]; DEBUGLOG( "CPSPlugIn::DoSASLPPSAuth" ); Return_if_NULL( inContext, eDSBadContextData ); Return_if_NULL( inData, eParameterError ); Return_if_NULL( inAuthBuff, eDSNullAuthStepData ); Return_if_NULL( outAuthBuff, eDSNullAuthStepDataResp ); Return_if_NULL( pContinue, eDSAuthContinueDataBad ); switch ( pContinue->fAuthPass++ ) { case 0: // start password server auth char *userCopy = strdup( inUserName ); StripRSAKey( userCopy ); snprintf( buf, sizeof(buf), "USER %s AUTH PPS %s", userCopy, inMechData ); free( userCopy ); DEBUGLOG( "sending %s", buf); serverResult = SendFlushReadWithMutex( inContext, buf, NULL, NULL, buf, sizeof(buf) ); if ( serverResult.err != 0 ) { DEBUGLOG( "server returned an error, err = %d", serverResult.err); return( (tDirStatus)PWSErrToDirServiceError(serverResult) ); } // set step buffer sasl_chop( buf ); DEBUGLOG( "received: %s", buf); // ensure we have "+AUTHOK " and data afterward if ( strlen(buf) > 8 ) { ConvertHexToBinary( buf + 8, (unsigned char *)dataBuf, &dataBufLen ); siResult = dsFillAuthBuffer( outAuthBuff, 1, dataBufLen, dataBuf ); } else { siResult = eDSAuthAccountDisabled; } break; case 1: siResult = (tDirStatus) GetStringFromAuthBuffer( inData->fInAuthStepData, 2, &stepData ); if ( siResult != eDSNoErr ) return(siResult); DEBUGLOG( "sending %s", stepData); serverResult = SendFlushReadWithMutex( inContext, "AUTH2", stepData, NULL, buf, sizeof(buf) ); free( stepData ); siResult = (tDirStatus) PWSErrToDirServiceError( serverResult ); if ( siResult == eDSNoErr ) { sasl_chop( buf ); ConvertHexToBinary( buf + 4, (unsigned char *)dataBuf, &dataBufLen ); siResult = dsFillAuthBuffer( outAuthBuff, 1, dataBufLen, dataBuf ); DEBUGLOG( "received: %s", buf); } gContinue->RemoveItem( pContinue ); inData->fIOContinueData = NULL; break; default: siResult = eDSAuthFailed; } return siResult; } // --------------------------------------------------------------------------- // * SendFlushReadWithMutex // --------------------------------------------------------------------------- PWServerError CPSPlugIn::SendFlushReadWithMutex( sPSContextData *inContext, const char *inCommandStr, const char *inArg1Str, const char *inArg2Str, char *inOutBuf, unsigned long inBufLen ) { PWServerError serverResult; gPWSConnMutex->Wait(); serverResult = SendFlushRead( inContext, inCommandStr, inArg1Str, inArg2Str, inOutBuf, inBufLen ); gPWSConnMutex->Signal(); return serverResult; } // --------------------------------------------------------------------------- // * GetServerListFromDSDiscovery // --------------------------------------------------------------------------- SInt32 CPSPlugIn::GetServerListFromDSDiscovery( CFMutableArrayRef inOutServerList ) { tDirReference dsRef = 0; tDataBuffer *tNodeListDataBuff = NULL; tDataBuffer *tDataBuff = NULL; tDirNodeReference nodeRef = 0; long status = eDSNoErr; tContextData context = NULL; UInt32 index = 0; UInt32 nodeCount = 0; tDataList *nodeName = NULL; tDataList *recordNameList = NULL; tDataList *recordTypeList = NULL; tDataList *attributeList = NULL; UInt32 recIndex = 0; UInt32 recCount = 0; UInt32 attrIndex = 0; UInt32 attrValueIndex = 0; tRecordEntry *recEntry = NULL; tAttributeListRef attrListRef = 0; tAttributeValueListRef valueRef = 0; tAttributeEntry *pAttrEntry = NULL; tAttributeValueEntry *pValueEntry = NULL; long nameLen = 0; sPSServerEntry anEntry; if ( inOutServerList == NULL ) return -1; try { status = dsOpenDirService( &dsRef ); if (status != eDSNoErr) throw( status ); tNodeListDataBuff = dsDataBufferAllocate( dsRef, 4096 ); if (tNodeListDataBuff == NULL) throw( (long)eMemoryError ); tDataBuff = dsDataBufferAllocate( dsRef, 4096 ); if (tDataBuff == NULL) throw( (long)eMemoryError ); // find and don't open status = dsFindDirNodes( dsRef, tNodeListDataBuff, NULL, eDSDefaultNetworkNodes, &nodeCount, &context ); if (status != eDSNoErr) throw( status ); if ( nodeCount < 1 ) throw( (long)eDSNodeNotFound ); recordNameList = dsBuildListFromStrings( dsRef, kDSRecordsAll, NULL ); recordTypeList = dsBuildListFromStrings( dsRef, "dsRecTypeNative:passwordserver", NULL ); attributeList = dsBuildListFromStrings( dsRef, kDSAttributesAll, NULL ); for ( index = 1; index <= nodeCount; index++ ) { status = dsGetDirNodeName( dsRef, tNodeListDataBuff, index, &nodeName ); if ( status != eDSNoErr ) break; status = dsOpenDirNode( dsRef, nodeName, &nodeRef ); dsDataListDeallocFree( dsRef, nodeName ); nodeName = NULL; if ( status != eDSNoErr ) break; do { recCount = 0; status = dsGetRecordList( nodeRef, tDataBuff, recordNameList, eDSExact, recordTypeList, attributeList, false, &recCount, &context ); if ( status != eDSNoErr ) break; for ( recIndex = 1; recIndex <= recCount; recIndex++ ) { bzero( &anEntry, sizeof(anEntry) ); status = dsGetRecordEntry( nodeRef, tDataBuff, recIndex, &attrListRef, &recEntry ); if ( status != eDSNoErr || recEntry == NULL ) continue; for ( attrIndex = 1; (attrIndex <= recEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++ ) { status = dsGetAttributeEntry( nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry ); if ( status == eDSNoErr && pAttrEntry != NULL ) { for ( attrValueIndex = 1; (attrValueIndex <= pAttrEntry->fAttributeValueCount) && (status == eDSNoErr); attrValueIndex++ ) { status = dsGetAttributeValue( nodeRef, tDataBuff, attrValueIndex, valueRef, &pValueEntry ); if ( status == eDSNoErr && pValueEntry != NULL ) { if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName ) == 0 ) { nameLen = strlen( pValueEntry->fAttributeValueData.fBufferData ); if ( nameLen >= 32 ) strncpy( anEntry.id, pValueEntry->fAttributeValueData.fBufferData, 32 ); strcpy( anEntry.port, kPasswordServerPortStr ); } // DEBUG if ( true ) { DEBUGLOG( " %l - %l: (%s) %s", attrIndex, attrValueIndex, pAttrEntry->fAttributeSignature.fBufferData, pValueEntry->fAttributeValueData.fBufferData ); } dsDeallocAttributeValueEntry( dsRef, pValueEntry ); pValueEntry = NULL; } else { //PrintError( kErrGetAttributeEntry, error ); } } dsDeallocAttributeEntry( dsRef, pAttrEntry ); pAttrEntry = NULL; dsCloseAttributeValueList(valueRef); valueRef = 0; } } } if ( nodeRef != 0 ) { dsCloseDirNode( nodeRef ); nodeRef = 0; } } while ( context != 0 ); } } catch( long error ) { status = error; } catch( ... ) { status = eDSAuthFailed; } if ( recordNameList != NULL ) dsDataListDeallocFree( dsRef, recordNameList ); if ( recordTypeList != NULL ) dsDataListDeallocFree( dsRef, recordTypeList ); if ( attributeList != NULL ) dsDataListDeallocFree( dsRef, attributeList ); if (tNodeListDataBuff != NULL) { dsDataBufferDeAllocate( dsRef, tNodeListDataBuff ); tNodeListDataBuff = NULL; } if (tDataBuff != NULL) { dsDataBufferDeAllocate( dsRef, tDataBuff ); tDataBuff = NULL; } if (nodeRef != 0) { dsCloseDirNode(nodeRef); nodeRef = 0; } if (dsRef != 0) { dsCloseDirService(dsRef); dsRef = 0; } DEBUGLOG( "GetServerListFromDSDiscovery = %l", status); return status; } //------------------------------------------------------------------------------------ // * DoPlugInCustomCall //------------------------------------------------------------------------------------ SInt32 CPSPlugIn::DoPlugInCustomCall ( sDoPlugInCustomCall *inData ) { SInt32 siResult = eDSNoErr; sPSContextData *pContext = nil; //seems that the client needs to have a tDirNodeReference //to make the custom call even though it will likely be non-dirnode specific related DEBUGLOG( "CPSPlugIn::DoPlugInCustomCall" ); try { if ( inData == nil ) throw( (SInt32)eDSNullParameter ); if ( inData->fInRequestData == nil ) throw( (SInt32)eDSNullDataBuff ); if ( inData->fInRequestData->fBufferData == nil ) throw( (SInt32)eDSEmptyBuffer ); pContext = (sPSContextData *)gPSContextTable->GetItemData( inData->fInNodeRef ); if ( pContext == nil ) throw( (SInt32)eDSBadContextData ); switch( inData->fInRequestCode ) { case 1: gPWSConnMutex->Wait(); if ( pContext->replicaFile != nil ) { [(ReplicaFile *)pContext->replicaFile free]; pContext->replicaFile = nil; } //DEBUGLOG( "CPSPlugIn::DoPlugInCustomCall received replica list: %s", inData->fInRequestData->fBufferData ); pContext->replicaFile = [[ReplicaFile alloc] initWithXMLStr:inData->fInRequestData->fBufferData]; gPWSConnMutex->Signal(); break; default: break; } } catch ( SInt32 err ) { siResult = err; } catch (...) { siResult = eDSAuthFailed; } return( siResult ); } // DoPlugInCustomCall // --------------------------------------------------------------------------- // * ContinueDeallocProc // --------------------------------------------------------------------------- void CPSPlugIn::ContinueDeallocProc ( void* inContinueData ) { sPSContinueData *pContinue = NULL; gPWSConnMutex->Wait(); pContinue = (sPSContinueData *)inContinueData; if ( pContinue != nil ) { if ( pContinue->fData != NULL ) { free( pContinue->fData ); pContinue->fData = NULL; } if ( pContinue->fSASLSecret != NULL ) { bzero( pContinue->fSASLSecret->data, pContinue->fSASLSecret->len ); free( pContinue->fSASLSecret ); pContinue->fSASLSecret = NULL; } free( pContinue ); pContinue = nil; } gPWSConnMutex->Signal(); } // ContinueDeallocProc // --------------------------------------------------------------------------- // * ContextDeallocProc // --------------------------------------------------------------------------- void CPSPlugIn::ContextDeallocProc ( void* inContextData ) { sPSContextData *pContext = (sPSContextData *) inContextData; if ( pContext != NULL ) { CleanContextData( pContext ); free( pContext ); pContext = NULL; } } // ContextDeallocProc // --------------------------------------------------------------------------- // * ReleaseCloseWaitConnections // --------------------------------------------------------------------------- void CPSPlugIn::ReleaseCloseWaitConnections( void* inContextData ) { sPSContextData *pContext = (sPSContextData *)inContextData; if ( pContext != nil && Connected(pContext) == false ) EndServerSession( pContext, false ); }