//---------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1996 // // File: ldapsch.cxx // // Contents: LDAP Schema Parser // // History: //---------------------------------------------------------------------------- #include "ldapc.hxx" #pragma hdrstop #define ADSI_LDAP_KEY TEXT("SOFTWARE\\Microsoft\\ADs\\Providers\\LDAP") #define SCHEMA_DIR_NAME TEXT("SchCache\\") #define SCHEMA_FILE_NAME_EXT TEXT(".sch") #define DEFAULT_SCHEMA_FILE_NAME TEXT("Default") #define DEFAULT_SCHEMA_FILE_NAME_WITH_EXT TEXT("Default.sch") #define SCHEMA_FILE_NAME TEXT("File") #define SCHEMA_TIME TEXT("Time") #define SCHEMA_PROCESSAUX TEXT("ProcessAUX") #define MAX_LOOP_COUNT 30 // Maximum depth of schema class tree #define ENTER_SCHEMA_CRITSECT() EnterCriticalSection(&g_SchemaCritSect) #define LEAVE_SCHEMA_CRITSECT() LeaveCriticalSection(&g_SchemaCritSect) #define ENTER_SUBSCHEMA_CRITSECT() EnterCriticalSection(&g_SubSchemaCritSect) #define LEAVE_SUBSCHEMA_CRITSECT() LeaveCriticalSection(&g_SubSchemaCritSect) #define ENTER_DEFAULTSCHEMA_CRITSECT() EnterCriticalSection(&g_DefaultSchemaCritSect) #define LEAVE_DEFAULTSCHEMA_CRITSECT() LeaveCriticalSection(&g_DefaultSchemaCritSect) #define ID_ATTRTYPES 1 #define ID_OBJCLASSES 2 #define ID_DITCONTENTRULES 3 #ifdef WIN95 int ConvertToAscii( WCHAR *pszUnicode, char **pszAscii ); #endif // // Constants used to determine what elements of string array to free. // const int FREE_ALL = 0; const int FREE_ARRAY_NOT_ELEMENTS = 1; const int FREE_ALL_BUT_FIRST = 2; // // RFC 2252 // KWDLIST g_aSchemaKeywordList[] = { { TOKEN_NAME, TEXT("NAME") }, { TOKEN_DESC, TEXT("DESC") }, { TOKEN_OBSOLETE, TEXT("OBSOLETE") }, { TOKEN_SUP, TEXT("SUP") }, { TOKEN_EQUALITY, TEXT("EQUALITY") }, { TOKEN_ORDERING, TEXT("ORDERING") }, { TOKEN_SUBSTR, TEXT("SUBSTR") }, { TOKEN_SYNTAX, TEXT("SYNTAX") }, { TOKEN_SINGLE_VALUE, TEXT("SINGLE-VALUE") }, { TOKEN_COLLECTIVE, TEXT("COLLECTIVE") }, { TOKEN_DYNAMIC, TEXT("DYNAMIC") }, { TOKEN_NO_USER_MODIFICATION, TEXT("NO-USER-MODIFICATION") }, { TOKEN_USAGE, TEXT("USAGE") }, { TOKEN_ABSTRACT, TEXT("ABSTRACT") }, { TOKEN_STRUCTURAL, TEXT("STRUCTURAL") }, { TOKEN_AUXILIARY, TEXT("AUXILIARY") }, { TOKEN_MUST, TEXT("MUST") }, { TOKEN_MAY, TEXT("MAY") }, { TOKEN_AUX, TEXT("AUX") }, { TOKEN_NOT, TEXT("NOT") } // FORM }; DWORD g_dwSchemaKeywordListSize = sizeof(g_aSchemaKeywordList)/sizeof(KWDLIST); CRITICAL_SECTION g_SchemaCritSect; CRITICAL_SECTION g_DefaultSchemaCritSect; CRITICAL_SECTION g_SubSchemaCritSect; SCHEMAINFO *g_pSchemaInfoList = NULL; // Link list of cached schema info SCHEMAINFO *g_pDefaultSchemaInfo = NULL; // // Non-AD sd control. // #define ADSI_LDAP_OID_SECDESC_OLD L"1.2.840.113556.1.4.416" typedef struct _subschemalist { LPWSTR pszLDAPServer; LPWSTR pszSubSchemaEntry; BOOL fPagingSupported; BOOL fSortingSupported; BOOL fDomScopeSupported; BOOL fTalkingToAD; BOOL fTalkingToEnhancedAD; BOOL fVLVSupported; BOOL fAttribScopedSupported; struct _subschemalist *pNext; BOOL fNoDataGot; DWORD dwSecDescType; } SCHEMALIST, *PSCHEMALIST; // // The fNoDataReturned will be set for v2 servers that do not // have a subSchemaSubEntry, this will prevent hitting the server // multiple times for the same data. // typedef SCHEMALIST ROOTDSENODE, *PROOTDSENODE; PSCHEMALIST gpSubSchemaList = NULL; static DWORD dwSubSchemaSubEntryCount = 0; HRESULT GetSchemaInfoTime( LPTSTR pszServer, LPTSTR pszSubSchemaSubEntry, LPTSTR *ppszTimeReg, LPTSTR *ppszTimeDS, CCredentials& Credentials, DWORD dwPort ); HRESULT LdapReadSchemaInfoFromServer( LPTSTR pszLDAPPath, LPTSTR pszSubSchemaSubEntry, LPTSTR pszTimeReg, LPTSTR pszTimeDS, SCHEMAINFO **ppSchemaInfo, CCredentials& Credentials, DWORD dwPort ); HRESULT ReadRootDSENode( LPWSTR pszLDAPServer, PROOTDSENODE pRootDSENode, OUT BOOL * pfBoundOk, // optional, can be NULL CCredentials& Credentials, DWORD dwPort ); HRESULT LdapReadDefaultSchema( LPTSTR pszServer, CCredentials &Credentials, SCHEMAINFO **ppSchemaInfo ); HRESULT FillPropertyInfoArray( LPTSTR *aAttrTypes, DWORD dwCount, PROPERTYINFO **paProperties, DWORD *pnProperties, SEARCHENTRY **paSearchTable ); HRESULT FillClassInfoArray( LPTSTR *aObjectClasses, DWORD dwCount, SEARCHENTRY *aPropSearchTable, DWORD dwSearchTableCount, CLASSINFO **paClasses, DWORD *pnClasses, SEARCHENTRY **paSearchTable ); HRESULT FillAuxClassInfoArray( LPTSTR *aDITContentRules, DWORD dwCount, SEARCHENTRY *aPropSearchTable, DWORD dwSearchTableCount, CLASSINFO *aClasses, DWORD nClasses, SEARCHENTRY *aSearchTable ); HRESULT ProcessClassInfoArray( CLASSINFO *aClasses, DWORD nClasses, SEARCHENTRY *paSearchTable, BOOL fProcessAUX = FALSE ); HRESULT ProcessPropertyInfoArray( PROPERTYINFO *aProperties, DWORD nProperties, SEARCHENTRY **paSearchTable ); DWORD ReadSchemaInfoFromRegistry( HKEY hKey, LPWSTR pszServer, LPTSTR **paValuesAttribTypes, int *pnCountAttribTypes, LPTSTR **paValuesObjClasses, int *pnCountObjClasses, LPTSTR **paValuesRules, int *pnCountRules, LPBYTE *pBuffer ); DWORD StoreSchemaInfoInRegistry( HKEY hKey, LPTSTR pszServer, LPTSTR pszTime, LPTSTR *aValuesAttribTypes, int nCountAttribTypes, LPTSTR *aValuesObjClasses, int nCountObjClasses, LPTSTR *aValuesRules, int nCountRules, BOOL fProcessAUX ); HRESULT AttributeTypeDescription( LPTSTR pszAttrType, PPROPERTYINFO pPropertyInfo, LPWSTR **pppszNames, PDWORD pdwNameCount ); HRESULT ObjectClassDescription( LPTSTR pszDescription, PCLASSINFO pClassInfo, SEARCHENTRY *aPropSearchTable, DWORD dwSearchTableCount, LPWSTR **pppszNames, PDWORD pdwNameCount ); HRESULT DITContentRuleDescription( LPTSTR pszObjectClass, PCLASSINFO pClassInfo, SEARCHENTRY *aPropSearchTable, DWORD dwSearchTableCount ); // // Helper routine that adds new elements to the property info array. // HRESULT AddNewNamesToPropertyArray( PROPERTYINFO **ppPropArray, DWORD dwCurPos, DWORD dwCount, LPWSTR *ppszNewNames, DWORD dwNewNameCount ); // // Helper routine that adds new elements to the class info array. // HRESULT AddNewNamesToClassArray( CLASSINFO **ppClassArray, DWORD dwCurPos, DWORD dwCount, LPWSTR *ppszNewNames, DWORD dwNewNameCount ); // // The 3rd param was added to work around bad schema data. // HRESULT Oid( CSchemaLexer * pTokenizer, LPTSTR *ppszOID, BOOL fNoGuid = FALSE ); HRESULT Oids( CSchemaLexer * pTokenizer, LPTSTR **pOIDs, DWORD *pnNumOfOIDs ); HRESULT PropOids( CSchemaLexer * pTokenizer, int **pOIDs, DWORD *pnNumOfOIDs, SEARCHENTRY *aPropSearchTable, DWORD dwSearchTableCount ); HRESULT DirectoryString( CSchemaLexer * pTokenizer, LPTSTR *ppszDirString ); // // Returns *pdwCount strings in ppszDirStrings. // HRESULT DirectoryStrings( CSchemaLexer * pTokenizer, LPTSTR **pppszDirStrings, PDWORD pdwCount ); void FreeDirectoryStrings( LPTSTR *ppszDirStrings, DWORD dwCount, DWORD dwElementsToFree= FREE_ALL ); VOID SortAndRemoveDuplicateOIDs( int *pOIDs, DWORD *pnNumOfOIDs ); int _cdecl searchentrycmp( const void *s1, const void *s2 ); long CompareUTCTime( LPTSTR pszTime1, LPTSTR pszTime2 ); BOOL EquivalentServers( LPWSTR pszTargetServer, LPWSTR pszSourceServer ); BOOL EquivalentUsers( LPWSTR pszTargetServer, LPWSTR pszSourceServer ); DWORD GetDefaultServer( DWORD dwPort, BOOL fVerify, LPWSTR szDomainDnsName, LPWSTR szServerName, BOOL fWriteable ); // // Makes a copy of a string array that has NULL as the last element. // If the copy failed because of lack of memory NULL is returned. // LPTSTR * CopyStringArray( LPTSTR * ppszStr ) { LPTSTR * ppszRetVal = NULL; DWORD dwCount = 0; if (!ppszStr) { BAIL_ON_FAILURE(E_FAIL); } // // Get the count first. // while (ppszStr && ppszStr[dwCount]) { dwCount++; } // // Alloc memory for the array, + 1, is for the NULL string that // acts as the delimiter for the array. // ppszRetVal = (LPTSTR *) AllocADsMem((dwCount+1) * sizeof(LPTSTR)); if (!ppszRetVal) { BAIL_ON_FAILURE(E_OUTOFMEMORY); } for (DWORD dwCtr = 0; dwCtr <= dwCount; dwCtr++) { if (ppszStr[dwCtr]) { ppszRetVal[dwCtr] = AllocADsStr(ppszStr[dwCtr]); if (!ppszRetVal[dwCtr]) { BAIL_ON_FAILURE(E_OUTOFMEMORY); } } } return ppszRetVal; error: if (ppszRetVal) { for (DWORD i = 0; i < dwCtr; i++) { if (ppszRetVal[i]) { FreeADsStr(ppszRetVal[i]); } } FreeADsMem(ppszRetVal); ppszRetVal = NULL; } // // Null from this routine means there was a failure. // return NULL; } VOID SchemaInit( VOID ) { InitializeCriticalSection( &g_SchemaCritSect ); InitializeCriticalSection(&g_SubSchemaCritSect); InitializeCriticalSection(&g_DefaultSchemaCritSect); } VOID SchemaCleanup( VOID ) { SCHEMAINFO *pList = g_pSchemaInfoList; while ( pList ) { SCHEMAINFO *pNext = pList->Next; delete pList; pList = pNext; } delete g_pDefaultSchemaInfo; // // Delete the schema list containing the server infos // PSCHEMALIST pSubSchemaList = gpSubSchemaList; while ( pSubSchemaList ) { PSCHEMALIST pNext = pSubSchemaList->pNext; if ( pSubSchemaList->pszLDAPServer ) FreeADsStr( pSubSchemaList->pszLDAPServer ); if ( pSubSchemaList->pszSubSchemaEntry ) FreeADsStr( pSubSchemaList->pszSubSchemaEntry ); FreeADsMem( pSubSchemaList ); pSubSchemaList = pNext; } // // Delete critsects initialized in SchemaInit // DeleteCriticalSection(&g_SchemaCritSect); DeleteCriticalSection(&g_SubSchemaCritSect); DeleteCriticalSection(&g_DefaultSchemaCritSect); } HRESULT LdapGetSchema( LPTSTR pszLDAPServer, SCHEMAINFO **ppSchemaInfo, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; LPTSTR pszTemp = NULL; SCHEMAINFO *pList = NULL; SCHEMAINFO *pPrev = NULL; LPTSTR pszTimeReg = NULL; LPTSTR pszTimeDS = NULL; BOOL fNotCurrent = FALSE; WCHAR szDomainDnsName[MAX_PATH]; *ppSchemaInfo = NULL; DWORD nCount =0; LPWSTR pszSubSchemaEntry = NULL; BOOL fBoundOk = FALSE; // has once bound to domain okay? BOOL fReuseSchema = FALSE; BOOL fTalktoAD = FALSE; // // In the case of a serverless path, we want to substitute the name // of the domain for the serverName. This is because we can get more // than one file called default.sch if a person logs on from different // forests on to the same domain. // if (!pszLDAPServer) { WCHAR szServerName[MAX_PATH]; DWORD dwErr; dwErr = GetDefaultServer( dwPort, FALSE, // do not force verify szDomainDnsName, szServerName, !(Credentials.GetAuthFlags() & ADS_READONLY_SERVER) ? TRUE : FALSE ); if (dwErr == NO_ERROR) { // // Use the domainName returned. // pszLDAPServer = szDomainDnsName; } } // // Check if the server uses default schema and return the schema info // hr = Credentials.GetUserName(&pszTemp); BAIL_IF_ERROR(hr); ENTER_SCHEMA_CRITSECT(); pList = g_pSchemaInfoList; pPrev = NULL; while ( pList ) { // // Checking for Schemas can now use NULL and NULL // // // If the server is equivalent, and we've cached it as using // a default (V2) schema, then we want to immediately return // that cached schema, UNLESS (1) the server in question // appeared to be a V3 server when we tried to retrieve the schema // (i.e., it had a rootDSE with a subschemasubentry), AND (2) // we're currently using different user credentials then when // we cached the server schema. This is because we might be going // against a V3 server that has security restrictions on its schema. // If we previously tried to read the schema, but didn't have // sufficient access permissions to do so, we would have defaulted // to treating it as a v2 schema. Now, if we're using different // credentials, we try again, in case we now have sufficient // access permissions to read the schema. // if (EquivalentServers(pList->pszServerName, pszLDAPServer)) { if ( pList->fDefaultSchema && !(pList->fAppearsV3 && !EquivalentUsers(pszTemp, pList->pszUserName) ) ) { *ppSchemaInfo = pList; (*ppSchemaInfo)->AddRef(); LEAVE_SCHEMA_CRITSECT(); goto cleanup; } else if (pList->fDefaultSchema && pList->fAppearsV3 && !EquivalentUsers(pszTemp, pList->pszUserName)) { // // Dump the cached schema in preparation for reading // it again. // if ( pList->IsRefCountZero()) { if ( pPrev == NULL ) g_pSchemaInfoList = pList->Next; else pPrev->Next = pList->Next; delete pList; break; } } } pPrev = pList; pList = pList->Next; } LEAVE_SCHEMA_CRITSECT(); // // Read the schema path from the root of the DS // hr = ReadSubSchemaSubEntry( pszLDAPServer, &pszSubSchemaEntry, &fBoundOk, Credentials, dwPort ) ; if ( SUCCEEDED(hr)) // pszSubSchemaEntry!=NULL if hr = S_OK. Checked. { ENTER_SCHEMA_CRITSECT(); pPrev = NULL; pList = g_pSchemaInfoList; while ( pList ) { hr = ReadServerSupportsIsADControl(pszLDAPServer, &fTalktoAD, Credentials, dwPort); if (FAILED(hr)) { // // Assume it is not AD and continue, there is no // good reason for this to fail on AD. // fTalktoAD = FALSE; } if(fTalktoAD) { // we talking to the server with AD, so then we don't have to compare the servername fReuseSchema = EquivalentServers(pList->pszSubSchemaSubEntry, pszSubSchemaEntry ); } else { // otherwise, we need to compare the server name fReuseSchema = EquivalentServers(pList->pszServerName, pszLDAPServer) && EquivalentServers(pList->pszSubSchemaSubEntry, pszSubSchemaEntry ); } if ( fReuseSchema ) { if ( pList->IsObsolete()) { hr = GetSchemaInfoTime( pszLDAPServer, pszSubSchemaEntry, &pszTimeReg, &pszTimeDS, Credentials, dwPort ); if ( FAILED(hr)) { // Cannot get the time, assume the cache is not // current and read again. fNotCurrent = TRUE; break; } else { // // If the servers are not the same, then we should // not comparet the times. This is because // each server has a ModifyTimeStamp that is not // based on its update time not that of the domain. // Note that at this point we know that the // subSchemaSubEntry is the same. // if (!EquivalentServers( pList->pszServerName, pszLDAPServer ) ) { fNotCurrent = TRUE; break; } // Compare the time to see if we need to read // the schema info from the file or from the DS if ( CompareUTCTime( pList->pszTime, pszTimeReg ) >= 0 ) { if ( CompareUTCTime( pszTimeReg, pszTimeDS ) < 0 ) { fNotCurrent = TRUE; break; } } else { // The schema in memory is not as current as the // the one stored in the registry, hence, we // need to read it anyway. fNotCurrent = TRUE; break; } } pList->MakeCurrent(); } *ppSchemaInfo = pList; (*ppSchemaInfo)->AddRef(); LEAVE_SCHEMA_CRITSECT(); goto cleanup; } pPrev = pList; pList = pList->Next; } if ( fNotCurrent && pList != NULL ) { if ( pList->IsRefCountZero()) { SCHEMAINFO *pDelete = pList; if ( pPrev == NULL ) g_pSchemaInfoList = pDelete->Next; else pPrev->Next = pDelete->Next; delete pDelete; } pList = NULL; } LEAVE_SCHEMA_CRITSECT(); // pList should be NULL at this point hr = LdapReadSchemaInfoFromServer( pszLDAPServer, pszSubSchemaEntry, // SubSchemaSubEntry pszTimeReg, pszTimeDS, ppSchemaInfo, Credentials, dwPort ); if (SUCCEEDED(hr)) { ENTER_SCHEMA_CRITSECT(); (*ppSchemaInfo)->Next = g_pSchemaInfoList; g_pSchemaInfoList = *ppSchemaInfo; (*ppSchemaInfo)->AddRef(); LEAVE_SCHEMA_CRITSECT(); } else { // // There was some problem in reading from the DS. If it was // because of some error like the attributes were not // obtained or were not of the proper form, we will fall // back to the default schema // hr = LdapReadDefaultSchema(pszLDAPServer, Credentials, ppSchemaInfo); BAIL_IF_ERROR(hr); // // We leave fAppearsV3 == TRUE because this server has a // subschemasubentry --- it's just that we can't read the // schema (e.g., maybe we don't have permission) // ENTER_SCHEMA_CRITSECT(); (*ppSchemaInfo)->Next = g_pSchemaInfoList; g_pSchemaInfoList = *ppSchemaInfo; (*ppSchemaInfo)->AddRef(); LEAVE_SCHEMA_CRITSECT(); } } // end of if read of subSchemaSubEntry succeeded else if ( fBoundOk ) { // // If we cannot get subschemasubentry, use default schema if // fBoundOk; that is, we have at least // once bound to the domain successfully before. // hr = LdapReadDefaultSchema( pszLDAPServer, Credentials, ppSchemaInfo ); BAIL_IF_ERROR(hr); (*ppSchemaInfo)->fAppearsV3 = FALSE; ENTER_SCHEMA_CRITSECT(); (*ppSchemaInfo)->Next = g_pSchemaInfoList; g_pSchemaInfoList = *ppSchemaInfo; (*ppSchemaInfo)->AddRef(); LEAVE_SCHEMA_CRITSECT(); } else { // // we cannot read subschemasubentry, but we are not using // default schema since we have no indication that the // we had ever bound to the domain before // if ( SUCCEEDED(hr)) // i.e. we could not read the schema { hr = E_ADS_BAD_PATHNAME; } BAIL_IF_ERROR(hr); } cleanup: if (pszSubSchemaEntry) { FreeADsStr(pszSubSchemaEntry); } if ( pszTimeReg ) FreeADsMem( pszTimeReg ); if ( pszTimeDS ) FreeADsMem( pszTimeDS ); if ( pszTemp ) FreeADsStr( pszTemp ); RRETURN(hr); } HRESULT LdapRemoveSchemaInfoOnServer( LPTSTR pszLDAPPath, CCredentials& Credentials, DWORD dwPort, BOOL fForce ) { HRESULT hr = S_OK; SCHEMAINFO *pList = NULL; LPWSTR pszSubSchemaSubEntry = NULL; BOOL fBoundOk = FALSE; // // Read the subschemaSubEntry only once. // hr = ReadSubSchemaSubEntry( pszLDAPPath, &pszSubSchemaSubEntry, &fBoundOk, Credentials, dwPort ) ; // // If we cannot read the subSchemaSubEntry it is not a // V3 server and we cannot refresh. // BAIL_ON_FAILURE(hr); ENTER_SCHEMA_CRITSECT(); pList = g_pSchemaInfoList; while ( pList ) { // // Both NULL and NULL and also check for the servers // if (!pList->pszServerName && !pszLDAPPath) { pList->MakeObsolete(); if (fForce) { // // Will reset time to something ancient so we // will always pick up the schema from server. // LPWSTR pszTempTime; pszTempTime = AllocADsStr(L"19800719000000.0Z"); if (pszTempTime && pList->pszTime) { FreeADsStr(pList->pszTime); pList->pszTime = pszTempTime; } } } else { // // The match at this point has to be made based on the // subschemaSubEntry and not on the server names. // if (EquivalentServers( pList->pszSubSchemaSubEntry, pszSubSchemaSubEntry ) ) { pList->MakeObsolete(); if (fForce) { // // Will reset time to something ancient so we // will always pick up the schema from server. // LPWSTR pszTempTime; pszTempTime = AllocADsStr(L"19800719000000.0Z"); if (pszTempTime && pList->pszTime) { FreeADsStr(pList->pszTime); pList->pszTime = pszTempTime; } } } } // the server name is not NULL pList = pList->Next; } LEAVE_SCHEMA_CRITSECT(); error : if (pszSubSchemaSubEntry) { FreeADsStr(pszSubSchemaSubEntry); } RRETURN(hr); } HRESULT GetSchemaInfoTime( LPTSTR pszLDAPServer, LPTSTR pszSubSchemaSubEntry, LPTSTR *ppszTimeReg, LPTSTR *ppszTimeDS, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; DWORD dwStatus = NO_ERROR; LPTSTR pszLDAPPath = NULL; LPTSTR pszRegPath = NULL; LPTSTR *aValues = NULL; int nCount = 0; TCHAR szTimeReg[64]; HKEY hKey = NULL; DWORD dwLength; DWORD dwType; // // Read the schema timestamp on the DS server // hr = LdapReadAttribute2( pszLDAPServer, NULL, pszSubSchemaSubEntry, TEXT("modifyTimeStamp"), &aValues, &nCount, Credentials, dwPort, L"(objectClass=subschema)" ); if (nCount==0) { // // cannot get to time stamp or get to a time stamp with no values: // both treat as E_FAIL // hr = E_FAIL; } BAIL_IF_ERROR(hr); ADsAssert( nCount == 1 ); *ppszTimeDS = AllocADsStr( aValues[0] ); LdapValueFree( aValues ); if ( *ppszTimeDS == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } // // See if we can find the schema info in the registry // pszRegPath = (LPTSTR) AllocADsMem( (_tcslen(ADSI_LDAP_KEY) + _tcslen(pszSubSchemaSubEntry) + 2 ) * sizeof(TCHAR)); // includes "\\" if ( pszRegPath == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } _tcscpy( pszRegPath, ADSI_LDAP_KEY ); _tcscat( pszRegPath, TEXT("\\")); _tcscat( pszRegPath, pszSubSchemaSubEntry ); dwStatus = RegOpenKeyEx( HKEY_LOCAL_MACHINE, pszRegPath, 0, KEY_READ, &hKey ); if ( dwStatus != NO_ERROR ) { hr = HRESULT_FROM_WIN32(dwStatus); BAIL_IF_ERROR(hr); } // // Read the time stamp of the schema in registry. // dwLength = sizeof(szTimeReg); dwStatus = RegQueryValueEx( hKey, SCHEMA_TIME, NULL, &dwType, (LPBYTE) szTimeReg, &dwLength ); if ( dwStatus ) { hr = HRESULT_FROM_WIN32(dwStatus); BAIL_IF_ERROR(hr); } else { *ppszTimeReg = AllocADsStr( szTimeReg ); if ( *ppszTimeReg == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } } cleanup: if ( hKey ) RegCloseKey( hKey ); if ( pszLDAPPath != NULL ) FreeADsStr( pszLDAPPath ); if ( pszRegPath != NULL ) FreeADsStr( pszRegPath ); if ( FAILED(hr)) { if ( *ppszTimeDS ) { FreeADsMem( *ppszTimeDS ); *ppszTimeDS = NULL; } if ( *ppszTimeReg ) { FreeADsMem( *ppszTimeReg ); *ppszTimeReg = NULL; } } RRETURN(hr); } HRESULT LdapReadSchemaInfoFromServer( LPTSTR pszLDAPServer, LPTSTR pszSubSchemaSubEntry, LPTSTR pszTimeReg, LPTSTR pszTimeDS, SCHEMAINFO **ppSchemaInfo, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; DWORD dwStatus = NO_ERROR; LPTSTR pszRegPath = NULL; SCHEMAINFO *pSchemaInfo = NULL; LPTSTR *aValues = NULL; int nCount = 0; TCHAR szTimeReg[64]; LPWSTR aStrings[4] = { L"attributeTypes", L"objectClasses", L"ditContentRules", NULL }; LPWSTR szNTFilter = L"objectClass=*"; LPWSTR szGenericFilter = L"objectClass=subSchema"; BOOL fNTDS = FALSE; DWORD dwSecDescType = 0; LPTSTR *aValuesAttribTypes = NULL; int nCountAttribTypes = 0; LPTSTR *aValuesObjClasses = NULL; int nCountObjClasses = 0; LPTSTR *aValuesRules = NULL; int nCountRules = 0; LPBYTE Buffer = NULL; HKEY hKeySchema = NULL; HKEY hKey = NULL; DWORD dwDisposition; BOOL fReadFromDS = TRUE; BOOL fProcessAUX = FALSE; DWORD dwRegPathLen = 0; *ppSchemaInfo = NULL; DWORD dwRegAUXType = REG_DWORD; DWORD dwRegProcessAUX = 0; DWORD dwRegLength = sizeof(dwRegProcessAUX); // // Allocate an entry for the schema info that we are going to read // #if DBG static BOOL fSchemaRead = FALSE; static BOOL fGoSchemaLess = FALSE; WCHAR pszRegPathDbg[MAX_PATH]; DWORD dwType = 0; DWORD dwRetVal = 0; DWORD dwLength = 0; if (!fSchemaRead) { _tcscpy( pszRegPathDbg, ADSI_LDAP_KEY ); _tcscat( pszRegPathDbg, TEXT("\\")); _tcscat( pszRegPathDbg, TEXT("DBGSchema")); //DebugDisabled // If DBG, try and read the schema key and return // value if that is set to 1. dwStatus = RegOpenKeyEx( HKEY_LOCAL_MACHINE, pszRegPathDbg, 0, KEY_READ, &hKeySchema ); if (dwStatus != NO_ERROR) { // Do not want to keep coming back to this. fSchemaRead = TRUE; } else { dwLength = sizeof(DWORD); // Read the value of the DWORD DebugDisabled dwStatus = RegQueryValueEx( hKeySchema, L"DebugDisabled", 0, &dwType, (LPBYTE) &dwRetVal, &dwLength ); if (dwStatus != NO_ERROR) { fSchemaRead = TRUE; } else { // Look at the value and proceed if (dwRetVal == 0) { fGoSchemaLess = TRUE; hr = E_FAIL; } } // else - we were able to read the DebugDisabled key } // else - we were able to open the key } // if fSchemaRead if ( hKeySchema ) RegCloseKey( hKeySchema ); // hr will be set only if we have schema disabled. // Note that hr is initialised to S_OK so default case // will fall through BAIL_IF_ERROR(hr); #endif pSchemaInfo = new SCHEMAINFO; if ( pSchemaInfo == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } memset(pSchemaInfo, 0, sizeof(SCHEMAINFO)); // // Store the server name // if (pszLDAPServer) { pSchemaInfo->pszServerName = AllocADsStr( pszLDAPServer ); if ( pSchemaInfo->pszServerName == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } } // // Store the name of the user under whose credentials // we're reading the schema // hr = Credentials.GetUserName(&(pSchemaInfo->pszUserName)); BAIL_IF_ERROR(hr); // // Store the subSchemaSubEntry path // pSchemaInfo->pszSubSchemaSubEntry = AllocADsStr( pszSubSchemaSubEntry ); if ( pSchemaInfo->pszSubSchemaSubEntry == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } // // Try and see if this is NTDS or not to optimize schema calls. // This is very likely to be satisfied from our cache as we would // have already read the RootDSE at this point. // hr = ReadSecurityDescriptorControlType( pszLDAPServer, &dwSecDescType, Credentials, dwPort ); if (SUCCEEDED(hr) && (dwSecDescType == ADSI_LDAPC_SECDESC_NT)) fNTDS = TRUE; if ( pszTimeDS == NULL ) { hr = LdapReadAttribute2( pszLDAPServer, NULL, pszSubSchemaSubEntry, TEXT("modifyTimeStamp"), &aValues, &nCount, Credentials, dwPort, fNTDS ? szNTFilter : szGenericFilter ); if (FAILED(hr) || nCount==0) { // // cannot read modifyTimeStamp or modifyTimeStamp has no values: // - treat as same // hr = S_OK; } else { ADsAssert( nCount == 1 ); pSchemaInfo->pszTime = AllocADsStr( aValues[0] ); LdapValueFree( aValues ); if ( pSchemaInfo->pszTime == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } } } else { pSchemaInfo->pszTime = AllocADsStr( pszTimeDS ); if ( pSchemaInfo->pszTime == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } } // // See if we can find the schema info in the registry // dwRegPathLen = _tcslen(ADSI_LDAP_KEY) + _tcslen(pszSubSchemaSubEntry) + (pszLDAPServer ? _tcslen(pszLDAPServer) : 0) + 3; // includes "\\" and . for serverName pszRegPath = (LPTSTR) AllocADsMem( dwRegPathLen * sizeof(TCHAR)); if ( pszRegPath == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } _tcscpy( pszRegPath, ADSI_LDAP_KEY ); _tcscat( pszRegPath, TEXT("\\")); _tcscat( pszRegPath, pszSubSchemaSubEntry ); // // If the server is not NTDS, and it has the subSchemaSubEntry cn=Schema, // to avoid schema key conflicts, we will add .ServerName to the key. // if (!fNTDS && pszSubSchemaSubEntry && pszLDAPServer // should alwasy be true && !_tcsicmp(pszSubSchemaSubEntry, TEXT("cn=Schema")) ) { _tcscat( pszRegPath, TEXT(".")); _tcscat( pszRegPath, pszLDAPServer); } dwStatus = RegCreateKeyEx( HKEY_LOCAL_MACHINE, pszRegPath, 0, TEXT(""), REG_OPTION_NON_VOLATILE, // or volatile KEY_READ | KEY_WRITE, NULL, &hKey, &dwDisposition ); if (dwStatus == NO_ERROR) { if ( ( dwDisposition == REG_OPENED_EXISTING_KEY ) && ( pSchemaInfo->pszTime != NULL ) && ( pszTimeReg == NULL ) ) { // // Read the time stamp of the schema in cache and the time stamp // of the schema on the server. If the time stamp on the server is // newer, then we need to read the info from the server. Else // the info in the cache is current and hence don't need to read // it again. // DWORD dwLength = sizeof(szTimeReg); DWORD dwType; dwStatus = RegQueryValueEx( hKey, SCHEMA_TIME, NULL, &dwType, (LPBYTE) szTimeReg, &dwLength ); if ( dwStatus ) { dwStatus = NO_ERROR; } else { // Compare the two time if ( CompareUTCTime( szTimeReg, pSchemaInfo->pszTime ) >= 0 ) fReadFromDS = FALSE; } } else if ( ( pSchemaInfo->pszTime != NULL ) && ( pszTimeReg != NULL )) { if ( CompareUTCTime( pszTimeReg, pSchemaInfo->pszTime ) >= 0 ) fReadFromDS = FALSE; } }else { fReadFromDS = TRUE; } if ( !fReadFromDS ) { // // Read from registry, if we failed to read from the registry, // then read it from the DS. // // // We can av while reading bad info from a file // or while processing it // __try { dwStatus = ReadSchemaInfoFromRegistry( hKey, pszLDAPServer, &aValuesAttribTypes, &nCountAttribTypes, &aValuesObjClasses, &nCountObjClasses, &aValuesRules, &nCountRules, &Buffer ); if ( dwStatus == NO_ERROR) { // // At this stage we need to try and process the info // we got from the file. There is always a chance that // the read was successful but the schema data is bad // // // First we need to read from the registry to find whether we need to process // AUX class or not. // dwStatus = RegQueryValueExW( hKey, SCHEMA_PROCESSAUX, NULL, &dwRegAUXType, (LPBYTE) &dwRegProcessAUX, &dwRegLength); if(ERROR_SUCCESS == dwStatus) { fProcessAUX = (BOOL) dwRegProcessAUX; hr = ProcessSchemaInfo( pSchemaInfo, aValuesAttribTypes, nCountAttribTypes, aValuesObjClasses, nCountObjClasses, aValuesRules, nCountRules, fProcessAUX ); } } } __except (EXCEPTION_EXECUTE_HANDLER) { dwStatus = GetExceptionCode(); if (dwStatus != EXCEPTION_ACCESS_VIOLATION) { ADsDebugOut((DEB_ERROR, "Processing Schema Info:Unknown Exception %d\n", dwStatus)); } hr = E_FAIL; } // end of exception handler if (FAILED(hr) || dwStatus) { // // We can read the schema from the ds and upgrade our // local copy to get rid of the bad file // fReadFromDS = TRUE; // // Need to cleanup here so that we wont leak mem. // if ( aValuesAttribTypes ){ FreeADsMem( aValuesAttribTypes ); aValuesAttribTypes = NULL; } if ( aValuesObjClasses ) { FreeADsMem( aValuesObjClasses ); aValuesObjClasses = NULL; } if ( aValuesRules ) { FreeADsMem( aValuesRules ); aValuesRules = NULL; } if ( Buffer ) { FreeADsMem( Buffer ); Buffer = NULL; } hr = E_FAIL; fReadFromDS = TRUE; } } // if !fReadFromDS if ( fReadFromDS ) { // // At this point, the info in the DS is newer or we have failed // to read the info from the registry, hence we need to read // from the DS and then store it in the registry. // // // As per the LDAP spec if the server does not know about // an attribute then it will ignore the attribute. So it should // be ok to ask for the ditContentRules even though the server // may not know about them. // hr = HelperReadLDAPSchemaInfo( pszLDAPServer, pszSubSchemaSubEntry, aStrings, fNTDS ? szNTFilter : szGenericFilter, &aValuesAttribTypes, &aValuesObjClasses, &aValuesRules, &nCountAttribTypes, &nCountObjClasses, &nCountRules, Credentials, dwPort); BAIL_IF_ERROR(hr); if (nCountAttribTypes == 0 || nCountObjClasses == 0) { BAIL_IF_ERROR(hr = E_FAIL); } // // We need to know if we need to process the aux classes // or not at this stage. If the server is enhanced AD (build 2220+), // we should not. Also if it is anything other than AD on Win2k we should // not as we will end up interpreting the schema incorrectly. // BOOL fLaterThanAD, fAD; hr = ReadServerSupportsIsEnhancedAD( pszLDAPServer, &fLaterThanAD, &fAD, Credentials, dwPort ); if (FAILED(hr)) { // // We will not process the aux classes. // fProcessAUX = FALSE; } if (fLaterThanAD) { fProcessAUX = FALSE; } else if (!fLaterThanAD && fAD) { fProcessAUX = TRUE; } // // This is not expected to AV as this is info from the // server that is why it is not in a try except block // hr = ProcessSchemaInfo( pSchemaInfo, aValuesAttribTypes, nCountAttribTypes, aValuesObjClasses, nCountObjClasses, aValuesRules, nCountRules, fProcessAUX ); BAIL_IF_ERROR(hr); } // if fReadFromDS // // Store all the info in the registry only if the time stamp // is present and we have read just read it from the server. // Ignore the error since if we failed to store it, we can // still read it from the DS. // if ( fReadFromDS && pSchemaInfo->pszTime ) { StoreSchemaInfoInRegistry( hKey, pszLDAPServer, pSchemaInfo->pszTime, aValuesAttribTypes, nCountAttribTypes, aValuesObjClasses, nCountObjClasses, aValuesRules, nCountRules, fProcessAUX); } *ppSchemaInfo = pSchemaInfo; cleanup: if ( fReadFromDS ) { if ( aValuesAttribTypes ) LdapValueFree( aValuesAttribTypes ); if ( aValuesObjClasses ) LdapValueFree( aValuesObjClasses ); if ( aValuesRules ) LdapValueFree( aValuesRules ); } else { if ( aValuesAttribTypes ) FreeADsMem( aValuesAttribTypes ); if ( aValuesObjClasses ) FreeADsMem( aValuesObjClasses ); if ( aValuesRules ) FreeADsMem( aValuesRules ); if ( Buffer ) FreeADsMem( Buffer ); } if ( hKey ) RegCloseKey( hKey ); if ( pszRegPath != NULL ) FreeADsStr( pszRegPath ); if ( FAILED(hr) && pSchemaInfo ) delete pSchemaInfo; RRETURN(hr); } HRESULT ProcessSchemaInfo( SCHEMAINFO *pSchemaInfo, LPTSTR *aValuesAttribTypes, DWORD dwAttribCount, LPTSTR *aValuesObjClasses, DWORD dwObjClassesCount, LPTSTR *aValuesRules, DWORD dwRulesCount, BOOL fProcessAUX ) { HRESULT hr = S_OK; hr = FillPropertyInfoArray( aValuesAttribTypes, dwAttribCount, &(pSchemaInfo->aProperties), &(pSchemaInfo->nNumOfProperties), &(pSchemaInfo->aPropertiesSearchTable) ); BAIL_IF_ERROR(hr); hr = FillClassInfoArray( aValuesObjClasses, dwObjClassesCount, pSchemaInfo->aPropertiesSearchTable, pSchemaInfo->nNumOfProperties * 2, &(pSchemaInfo->aClasses), &(pSchemaInfo->nNumOfClasses), &(pSchemaInfo->aClassesSearchTable) ); BAIL_IF_ERROR(hr); if ( aValuesRules ) { hr = FillAuxClassInfoArray( aValuesRules, dwRulesCount, pSchemaInfo->aPropertiesSearchTable, pSchemaInfo->nNumOfProperties * 2, pSchemaInfo->aClasses, pSchemaInfo->nNumOfClasses, pSchemaInfo->aClassesSearchTable ); BAIL_IF_ERROR(hr); } // // fProcssAUX tells us if we need to add the list of must // contain on each of the classes in the AUX list to the appopriate // classes list. Say : // 1.2.3.4 NAME 'OrganizationalUnit' AUX ($Class1 $CLASS2) MUST (List) // May (List). Then if the flag is true, we will add the Must and May // of class1 and class2 to the must and may of class OrganizationalUnit // (the must and may list is always processed - they are lists // of attributes). // hr = ProcessClassInfoArray( pSchemaInfo->aClasses, pSchemaInfo->nNumOfClasses, pSchemaInfo->aClassesSearchTable, fProcessAUX ); BAIL_IF_ERROR(hr); cleanup : // // Nothing to do for now // RRETURN(hr); } // // Helper to read the schema information from subSchemaSubEntry // HRESULT HelperReadLDAPSchemaInfo( LPWSTR pszLDAPServer, LPWSTR pszSubSchemaSubEntry, LPWSTR szAttributes[], LPWSTR pszFilter, LPTSTR **aValuesAttribTypes, LPTSTR **aValuesObjClasses, LPTSTR **aValuesRules, int *nCountAttributes, int *nCountObjClasses, int *nCountRules, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; ADS_LDP *ld = NULL; LDAPMessage *res = NULL; LDAPMessage *e = NULL; hr = LdapOpenObject2( pszLDAPServer, NULL, pszSubSchemaSubEntry, &ld, Credentials, dwPort ); BAIL_ON_FAILURE(hr); ADsAssert(ld && ld->LdapHandle); hr = LdapSearchS( ld, pszSubSchemaSubEntry, LDAP_SCOPE_BASE, pszFilter, szAttributes, 0, &res ); BAIL_ON_FAILURE(hr); BAIL_ON_FAILURE((hr = LdapFirstEntry(ld, res, &e))); hr = LdapGetValues( ld, e, szAttributes[0], aValuesAttribTypes, nCountAttributes ); BAIL_ON_FAILURE(hr); hr = LdapGetValues( ld, e, szAttributes[1], aValuesObjClasses, nCountObjClasses ); BAIL_ON_FAILURE(hr); hr = LdapGetValues( ld, e, szAttributes[2], aValuesRules, nCountRules ); if (FAILED(hr)) { // // This is non critical // *aValuesRules = NULL; nCountRules = 0; hr = S_OK; } error: if (res) { LdapMsgFree(res); } if (ld) { LdapCloseObject(ld); } RRETURN(hr); } HRESULT LdapReadDefaultSchema( LPTSTR pszServer, CCredentials &Credentials, SCHEMAINFO **ppSchemaInfo ) { HRESULT hr = S_OK; SCHEMAINFO *pSchemaInfo = NULL; *ppSchemaInfo = NULL; ENTER_DEFAULTSCHEMA_CRITSECT(); if ( g_pDefaultSchemaInfo == NULL ) { g_pDefaultSchemaInfo = new SCHEMAINFO; if ( g_pDefaultSchemaInfo == NULL ) { LEAVE_DEFAULTSCHEMA_CRITSECT(); hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } hr = FillPropertyInfoArray( g_aDefaultAttributeTypes, g_cDefaultAttributeTypes, &(g_pDefaultSchemaInfo->aProperties), &(g_pDefaultSchemaInfo->nNumOfProperties), &(g_pDefaultSchemaInfo->aPropertiesSearchTable)); // // Now read the object classes from the schema // if ( SUCCEEDED(hr)) { hr = FillClassInfoArray( g_aDefaultObjectClasses, g_cDefaultObjectClasses, g_pDefaultSchemaInfo->aPropertiesSearchTable, g_pDefaultSchemaInfo->nNumOfProperties * 2, &(g_pDefaultSchemaInfo->aClasses), &(g_pDefaultSchemaInfo->nNumOfClasses), &(g_pDefaultSchemaInfo->aClassesSearchTable)); if ( SUCCEEDED(hr)) { hr = ProcessClassInfoArray( g_pDefaultSchemaInfo->aClasses, g_pDefaultSchemaInfo->nNumOfClasses, g_pDefaultSchemaInfo->aClassesSearchTable ); } } if (FAILED(hr)) { delete g_pDefaultSchemaInfo; g_pDefaultSchemaInfo = NULL; LEAVE_DEFAULTSCHEMA_CRITSECT(); } BAIL_IF_ERROR(hr); } LEAVE_DEFAULTSCHEMA_CRITSECT(); // // Allocate an entry for the schema info // pSchemaInfo = new SCHEMAINFO; if ( pSchemaInfo == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } // // Store the server name // if (pszServer) { pSchemaInfo->pszServerName = AllocADsStr( pszServer ); if ( pSchemaInfo->pszServerName == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } } // // Store the name of the user under whose credentials // we're reading the schema // hr = Credentials.GetUserName(&(pSchemaInfo->pszUserName)); BAIL_IF_ERROR(hr); pSchemaInfo->aClasses = g_pDefaultSchemaInfo->aClasses; pSchemaInfo->nNumOfClasses = g_pDefaultSchemaInfo->nNumOfClasses; pSchemaInfo->aClassesSearchTable = g_pDefaultSchemaInfo->aClassesSearchTable; pSchemaInfo->aProperties = g_pDefaultSchemaInfo->aProperties; pSchemaInfo->nNumOfProperties = g_pDefaultSchemaInfo->nNumOfProperties; pSchemaInfo->aPropertiesSearchTable = g_pDefaultSchemaInfo->aPropertiesSearchTable; pSchemaInfo->fDefaultSchema = TRUE; *ppSchemaInfo = pSchemaInfo; cleanup: if ( FAILED(hr) && pSchemaInfo ) delete pSchemaInfo; RRETURN(hr); } HRESULT FillPropertyInfoArray( LPTSTR *aAttrTypes, DWORD dwCount, PROPERTYINFO **paProperties, DWORD *pnProperties, SEARCHENTRY **paPropertiesSearchTable ) { HRESULT hr = S_OK; DWORD i = 0; PROPERTYINFO * pPropArray = NULL; PROPERTYINFO * pNewPropArray = NULL; LPWSTR *ppszNewNames = NULL; DWORD dwNewNameCount = 0; DWORD dwDisplacement = 0; BOOL fFreeNames = TRUE; *paProperties = NULL; *pnProperties = 0; *paPropertiesSearchTable = NULL; if ( dwCount == 0 ) RRETURN(S_OK); pPropArray = (PROPERTYINFO *)AllocADsMem( sizeof(PROPERTYINFO) * dwCount); if (!pPropArray) { hr = E_OUTOFMEMORY; BAIL_ON_FAILURE(hr); } for ( i = 0; i < dwCount; i++) { fFreeNames = FREE_ALL_BUT_FIRST; dwNewNameCount = 0; pPropArray[i].dwUsage = ATTR_USAGE_USERAPPLICATIONS; hr = AttributeTypeDescription( aAttrTypes[i], pPropArray + (i+dwDisplacement), &ppszNewNames, &dwNewNameCount ); BAIL_ON_FAILURE(hr); if (ppszNewNames) { if (dwNewNameCount > 1) { hr = AddNewNamesToPropertyArray( &pPropArray, i + dwDisplacement, // current position in array dwCount + dwDisplacement, // total number already in array ppszNewNames, // array of names. dwNewNameCount // number of names ); // // In the failure case, we can still continue, just // that the additional names will not be recognized. // if (SUCCEEDED(hr)) { // // In this case the new array has the data needed. // The count is updated suitably only on success. // fFreeNames = FALSE; dwDisplacement += (dwNewNameCount - 1); } } FreeDirectoryStrings( ppszNewNames, dwNewNameCount, fFreeNames ? FREE_ALL_BUT_FIRST : FREE_ARRAY_NOT_ELEMENTS ); ppszNewNames = NULL; // freed in the above call. } } // // Reduce i by one in case we fail and call FreePropertyInfoArray below. // i--; dwCount += dwDisplacement; hr = ProcessPropertyInfoArray(pPropArray, dwCount, paPropertiesSearchTable); BAIL_ON_FAILURE(hr); *paProperties = pPropArray; *pnProperties = dwCount; RRETURN(S_OK); error: FreePropertyInfoArray( pPropArray, i + 1); // // Need to free the new names list if it is valid. // if (ppszNewNames) { // // This function frees pszNames too. // FreeDirectoryStrings( ppszNewNames, dwNewNameCount, FREE_ALL_BUT_FIRST // do not free first element ); } RRETURN(hr); } HRESULT GetInfoFromSuperiorProperty( PROPERTYINFO *pPropertyInfo, PROPERTYINFO *pPropertyInfoSuperior ) { HRESULT hr = S_OK; if ( pPropertyInfo->pszSyntax == NULL ) { pPropertyInfo->pszSyntax = AllocADsStr( pPropertyInfoSuperior->pszSyntax ); if ( pPropertyInfo->pszSyntax == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } } #if 0 if ( pPropertyInfo->lMaxRange == 0 && pPropertyInfo->lMinRange == 0 ) { pPropertyInfo->lMaxRange = pPropertyInfoSuperior->lMaxRange; pPropertyInfo->lMinRange = pPropertyInfoSuperior->lMinRange; } #endif if ( pPropertyInfoSuperior->fSingleValued && !pPropertyInfo->fSingleValued ) { pPropertyInfo->fSingleValued = pPropertyInfoSuperior->fSingleValued; } if ( pPropertyInfo->pszOIDEquality == NULL && pPropertyInfoSuperior->pszOIDEquality != NULL ) { pPropertyInfo->pszOIDEquality = AllocADsStr( pPropertyInfoSuperior->pszOIDEquality ); if ( pPropertyInfo->pszOIDEquality == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } } if ( pPropertyInfo->pszOIDOrdering == NULL && pPropertyInfoSuperior->pszOIDOrdering != NULL ) { pPropertyInfo->pszOIDOrdering = AllocADsStr( pPropertyInfoSuperior->pszOIDOrdering ); if ( pPropertyInfo->pszOIDOrdering == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } } if ( pPropertyInfo->pszOIDSubstr == NULL && pPropertyInfoSuperior->pszOIDSubstr != NULL ) { pPropertyInfo->pszOIDSubstr = AllocADsStr( pPropertyInfoSuperior->pszOIDSubstr ); if ( pPropertyInfo->pszOIDSubstr == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } } cleanup: RRETURN(hr); } HRESULT ProcessPropertyInfoArray( PROPERTYINFO *aProperties, DWORD nProperties, SEARCHENTRY **paPropertiesSearchTable ) { HRESULT hr = S_OK; SEARCHENTRY *aSearchTable = NULL; SEARCHENTRY searchEntry; SEARCHENTRY *matchedEntry; DWORD i; BOOL fProcessedAll = TRUE; DWORD nLoop = 0; *paPropertiesSearchTable = NULL; // // First, build a binary search table for faster lookup // aSearchTable = (SEARCHENTRY *) AllocADsMem( sizeof(SEARCHENTRY) * nProperties * 2); if (!aSearchTable) { hr = E_OUTOFMEMORY; BAIL_ON_FAILURE(hr); } for ( i = 0; i < nProperties; i++ ) { // OIDs can be specified in 2.5.6.0 format or name. // So, we need both entries in the search table. // // Special case to handle no names or OID's // if (aProperties[i].pszPropertyName == NULL) { aProperties[i].pszPropertyName = AllocADsStr(aProperties[i].pszOID); } if (aProperties[i].pszOID == NULL) { aProperties[i].pszOID = AllocADsStr(aProperties[i].pszPropertyName); } aSearchTable[2*i].pszName = aProperties[i].pszPropertyName; aSearchTable[2*i].nIndex = i; aSearchTable[2*i+1].pszName = aProperties[i].pszOID; aSearchTable[2*i+1].nIndex = i; } // // Sort the table // qsort( aSearchTable, 2*nProperties, sizeof(SEARCHENTRY), searchentrycmp ); do { fProcessedAll = TRUE; for ( DWORD i = 0; i < nProperties; i++ ) { LPTSTR pszOIDSup = NULL; if ( aProperties[i].fProcessedSuperiorClass ) continue; pszOIDSup = aProperties[i].pszOIDSup; if ( pszOIDSup == NULL ) { aProperties[i].fProcessedSuperiorClass = TRUE; continue; } searchEntry.pszName = pszOIDSup; matchedEntry = (SEARCHENTRY *) bsearch( (SEARCHENTRY *) &searchEntry, aSearchTable, 2*nProperties, sizeof(SEARCHENTRY), searchentrycmp ); if ( matchedEntry ) // found the superior class { if (!aProperties[matchedEntry->nIndex].fProcessedSuperiorClass) { fProcessedAll = FALSE; continue; } hr = GetInfoFromSuperiorProperty( &(aProperties[i]), &(aProperties[matchedEntry->nIndex])); BAIL_ON_FAILURE(hr); } aProperties[i].fProcessedSuperiorClass = TRUE; } nLoop++; } while ( (nLoop < MAX_LOOP_COUNT) && !fProcessedAll ); *paPropertiesSearchTable = aSearchTable; RRETURN(S_OK); error: if ( aSearchTable ) FreeADsMem( aSearchTable ); RRETURN(hr); } VOID FreePropertyInfoArray( PROPERTYINFO *aProperties, DWORD nProperties ) { if ( aProperties ) { DWORD j; for ( j = 0; j < nProperties; j++ ) { FreeADsStr( aProperties[j].pszPropertyName ); FreeADsStr( aProperties[j].pszOID ); FreeADsStr( aProperties[j].pszSyntax ); FreeADsStr( aProperties[j].pszDescription ); FreeADsStr( aProperties[j].pszOIDSup ); FreeADsStr( aProperties[j].pszOIDEquality ); FreeADsStr( aProperties[j].pszOIDOrdering ); FreeADsStr( aProperties[j].pszOIDSubstr ); } FreeADsMem( aProperties ); } } HRESULT FillClassInfoArray( LPTSTR *aObjectClasses, DWORD dwCount, SEARCHENTRY *aPropSearchTable, DWORD dwSearchTableCount, CLASSINFO **paClasses, DWORD *pnClasses, SEARCHENTRY **paClassesSearchTable ) { HRESULT hr = S_OK; DWORD i = 0; CLASSINFO * pClassArray = NULL; SEARCHENTRY *aSearchTable = NULL; LPWSTR *ppszNewNames = NULL; DWORD dwNewNameCount = 0; DWORD dwDisplacement = 0; BOOL fFreeNames = FALSE; *paClasses= NULL; *pnClasses= 0; *paClassesSearchTable = NULL; if ( dwCount == 0 ) RRETURN(S_OK); pClassArray = (CLASSINFO *)AllocADsMem( sizeof(CLASSINFO) * dwCount ); if (!pClassArray) { hr = E_OUTOFMEMORY; BAIL_ON_FAILURE(hr); } for ( i = 0; i < dwCount; i++) { ppszNewNames = NULL; dwNewNameCount = 0; fFreeNames = TRUE; pClassArray[i].IsContainer = -1; pClassArray[i].dwType = CLASS_TYPE_STRUCTURAL; hr = ObjectClassDescription( aObjectClasses[i], pClassArray + (i + dwDisplacement), aPropSearchTable, dwSearchTableCount, &ppszNewNames, &dwNewNameCount ); BAIL_ON_FAILURE(hr); if (ppszNewNames) { if (dwNewNameCount > 1) { // // ********************** IMPORTANT NOTE ************ // In this routine, we specifically do not duplicate // the pCLSID and pPrimaryInterfaceGUID as these are not // freed when we exit and do not appear to be used anywehre. // If ObjectClasDescription is changed to add this info, // then the fn below needs to be update accordingly. // ************************************************** // hr = AddNewNamesToClassArray( &pClassArray, i + dwDisplacement, // current position in array dwCount + dwDisplacement, // total number already in array ppszNewNames, // array of names. dwNewNameCount // number of names ); // // In the failure case, we can still continue, just // that the additional names will not be recognized. // if (SUCCEEDED(hr)) { // // In this case the new array has the data needed. // The count is updated suitably only on success. // fFreeNames = FALSE; dwDisplacement += (dwNewNameCount - 1); } } FreeDirectoryStrings( ppszNewNames, dwNewNameCount, fFreeNames ? FREE_ALL_BUT_FIRST : FREE_ARRAY_NOT_ELEMENTS ); ppszNewNames = NULL; // freed in the above call. } // if newNames is valid. } // for // // Count should now include the new elements added. // dwCount += dwDisplacement; // // Build a binary search table for faster lookup // aSearchTable = (SEARCHENTRY *) AllocADsMem( sizeof(SEARCHENTRY) * dwCount * 2); if (!aSearchTable) { hr = E_OUTOFMEMORY; // // i is now dwCount but should be one less as // the free call is made with i+1 // i--; BAIL_ON_FAILURE(hr); } for ( i = 0; i < dwCount; i++ ) { // // Take care of the NULL name/OID in case of some servers // if (pClassArray[i].pszName == NULL) { pClassArray[i].pszName = AllocADsStr(pClassArray[i].pszOID); } if (pClassArray[i].pszOID == NULL) { pClassArray[i].pszOID = AllocADsStr(pClassArray[i].pszName); } // OIDs can be specified in 2.5.6.0 format or name. // So, we need both entries in the search table. aSearchTable[2*i].pszName = pClassArray[i].pszName; aSearchTable[2*i].nIndex = i; aSearchTable[2*i+1].pszName = pClassArray[i].pszOID; aSearchTable[2*i+1].nIndex = i; } // // Sort the table // qsort( aSearchTable, 2*dwCount, sizeof(SEARCHENTRY), searchentrycmp ); *paClasses = pClassArray; *pnClasses = dwCount; *paClassesSearchTable = aSearchTable; RRETURN(S_OK); error: FreeClassInfoArray( pClassArray, i + 1 ); if ( aSearchTable ) FreeADsMem( aSearchTable ); if (ppszNewNames) { FreeDirectoryStrings( ppszNewNames, dwNewNameCount, FREE_ALL_BUT_FIRST // first taken care of above. ); } RRETURN(hr); } HRESULT FillAuxClassInfoArray( LPTSTR *aDITContentRules, DWORD dwCount, SEARCHENTRY *aPropSearchTable, DWORD dwSearchTableCount, CLASSINFO *aClasses, DWORD nClasses, SEARCHENTRY *aClassesSearchTable ) { HRESULT hr = S_OK; DWORD i = 0; CLASSINFO ClassInfo; int index; if ( dwCount == 0 ) RRETURN(S_OK); for ( i = 0; i < dwCount; i++) { memset( &ClassInfo, 0, sizeof(ClassInfo)); hr = DITContentRuleDescription( aDITContentRules[i], &ClassInfo, aPropSearchTable, dwSearchTableCount ); BAIL_ON_FAILURE(hr); index = FindEntryInSearchTable( ClassInfo.pszOID, aClassesSearchTable, nClasses * 2 ); if ( index == -1 ) continue; // Cannot find the class, ignore and continue aClasses[index].pOIDsNotContain = ClassInfo.pOIDsNotContain; aClasses[index].nNumOfNotContain = ClassInfo.nNumOfNotContain; aClasses[index].pOIDsAuxClasses = ClassInfo.pOIDsAuxClasses; if ( ClassInfo.pOIDsMustContain ) { DWORD nMustContain = aClasses[index].nNumOfMustContain; if ( nMustContain == 0 ) { aClasses[index].pOIDsMustContain = ClassInfo.pOIDsMustContain; aClasses[index].nNumOfMustContain = ClassInfo.nNumOfMustContain; } else { aClasses[index].pOIDsMustContain = (int *) ReallocADsMem( aClasses[index].pOIDsMustContain, sizeof(int) * nMustContain, sizeof(int) * ( nMustContain + ClassInfo.nNumOfMustContain + 1)); if ( aClasses[index].pOIDsMustContain == NULL ) { hr = E_OUTOFMEMORY; BAIL_ON_FAILURE(hr); } memcpy( &(aClasses[index].pOIDsMustContain[nMustContain]), ClassInfo.pOIDsMustContain, ClassInfo.nNumOfMustContain * sizeof(int)); aClasses[index].nNumOfMustContain += ClassInfo.nNumOfMustContain; // // Need to terminate with -1. // aClasses[index].pOIDsMustContain[nMustContain+ClassInfo.nNumOfMustContain] = -1; FreeADsMem( ClassInfo.pOIDsMustContain ); ClassInfo.pOIDsMustContain = NULL; } } if ( ClassInfo.pOIDsMayContain ) { DWORD nMayContain = aClasses[index].nNumOfMayContain; if ( nMayContain == 0 ) { aClasses[index].pOIDsMayContain = ClassInfo.pOIDsMayContain; aClasses[index].nNumOfMayContain = ClassInfo.nNumOfMayContain; } else { aClasses[index].pOIDsMayContain = (int *) ReallocADsMem( aClasses[index].pOIDsMayContain, sizeof(int) * nMayContain, sizeof(int) * ( nMayContain + ClassInfo.nNumOfMayContain + 1)); if ( aClasses[index].pOIDsMayContain == NULL ) { hr = E_OUTOFMEMORY; BAIL_ON_FAILURE(hr); } memcpy( &(aClasses[index].pOIDsMayContain[nMayContain]), ClassInfo.pOIDsMayContain, ClassInfo.nNumOfMayContain * sizeof(int)); aClasses[index].nNumOfMayContain += ClassInfo.nNumOfMayContain; // // Need to terminate with -1. // aClasses[index].pOIDsMayContain[nMayContain+ClassInfo.nNumOfMayContain] = -1; FreeADsMem( ClassInfo.pOIDsMayContain ); ClassInfo.pOIDsMayContain = NULL; } } FreeADsStr( ClassInfo.pszOID ); ClassInfo.pszOID = NULL; FreeADsStr( ClassInfo.pszName ); FreeADsStr( ClassInfo.pszDescription ); } RRETURN(S_OK); error: if ( ClassInfo.pszOID ) { FreeADsStr( ClassInfo.pszOID ); FreeADsStr( ClassInfo.pszName ); FreeADsStr( ClassInfo.pszDescription ); if ( ClassInfo.pOIDsMustContain ) { FreeADsMem( ClassInfo.pOIDsMustContain ); } if ( ClassInfo.pOIDsMayContain ) { FreeADsMem( ClassInfo.pOIDsMayContain ); } } RRETURN(hr); } HRESULT GetInfoFromSuperiorClasses( CLASSINFO *pClassInfo, CLASSINFO *pClassInfoSuperior ) { HRESULT hr = S_OK; DWORD i; int *pOIDsMustSup = pClassInfoSuperior->pOIDsMustContain; int *pOIDsMaySup = pClassInfoSuperior->pOIDsMayContain; DWORD nCountMustSup = pClassInfoSuperior->nNumOfMustContain; DWORD nCountMaySup = pClassInfoSuperior->nNumOfMayContain; int *pOIDsMust = pClassInfo->pOIDsMustContain; int *pOIDsMay = pClassInfo->pOIDsMayContain; DWORD nCountMust = pClassInfo->nNumOfMustContain; DWORD nCountMay = pClassInfo->nNumOfMayContain; int *pOIDsMustNew = NULL; int *pOIDsMayNew = NULL; if ( pOIDsMaySup == NULL && pOIDsMustSup == NULL ) RRETURN(S_OK); if ( nCountMustSup > 0 ) { pOIDsMustNew = (int *) AllocADsMem( sizeof(int) * (nCountMust + nCountMustSup + 1)); if ( pOIDsMustNew == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } for ( i = 0; i < nCountMustSup; i++ ) { pOIDsMustNew[i] = pOIDsMustSup[i]; } for ( i = 0; i < nCountMust; i++ ) { pOIDsMustNew[i+nCountMustSup] = pOIDsMust[i]; } pOIDsMustNew[nCountMustSup+nCountMust] = -1; pClassInfo->pOIDsMustContain = pOIDsMustNew; pClassInfo->nNumOfMustContain = nCountMust + nCountMustSup; if ( pOIDsMust ) FreeADsMem( pOIDsMust ); pOIDsMustNew = NULL; } if ( nCountMaySup > 0 ) { pOIDsMayNew = (int *) AllocADsMem( sizeof(int) * ( nCountMay + nCountMaySup + 1 )); if ( pOIDsMayNew == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } for ( i = 0; i < nCountMaySup; i++ ) { pOIDsMayNew[i] = pOIDsMaySup[i]; } for ( i = 0; i < nCountMay; i++ ) { pOIDsMayNew[i+nCountMaySup] = pOIDsMay[i]; } pOIDsMayNew[nCountMaySup+nCountMay] = -1; pClassInfo->pOIDsMayContain = pOIDsMayNew; pClassInfo->nNumOfMayContain = nCountMay + nCountMaySup; if ( pOIDsMay ) FreeADsMem( pOIDsMay ); pOIDsMayNew = NULL; } cleanup: if (FAILED(hr)) { if ( pOIDsMustNew ) FreeADsMem( pOIDsMustNew ); if ( pOIDsMayNew ) FreeADsMem( pOIDsMayNew ); } RRETURN(hr); } HRESULT ProcessClassInfoArray( CLASSINFO *aClasses, DWORD nClasses, SEARCHENTRY *aSearchTable, BOOL fProcessAUX // defaulted to false ) { HRESULT hr = S_OK; SEARCHENTRY searchEntry; SEARCHENTRY *matchedEntry; DWORD i; BOOL fProcessedAll = TRUE; DWORD nLoop = 0; do { fProcessedAll = TRUE; for ( DWORD i = 0; i < nClasses; i++ ) { LPTSTR *pOIDsSup = NULL; LPTSTR *pOIDsAux = NULL; DWORD j = 0; if ( aClasses[i].fProcessedSuperiorClasses ) continue; pOIDsSup = aClasses[i].pOIDsSuperiorClasses; // // If fProcessAUX then we ne need to add the aux // class lists must and may to the classes own list. // Please look at where this flag is being set for // more details. // if (fProcessAUX) { pOIDsAux = aClasses[i].pOIDsAuxClasses; } else { pOIDsAux = NULL; } if ( pOIDsSup == NULL ) { aClasses[i].fProcessedSuperiorClasses = TRUE; continue; } // This is here just in case the schema description for class "top" // has other superior classes. "top" should not have any superior // classes. if ( _tcsicmp( aClasses[i].pszName, TEXT("top")) == 0 ) { aClasses[i].fProcessedSuperiorClasses = TRUE; continue; } j = 0; while ( pOIDsSup[j] ) { searchEntry.pszName = pOIDsSup[j]; matchedEntry = (SEARCHENTRY *) bsearch( (SEARCHENTRY *) &searchEntry, aSearchTable, 2*nClasses, sizeof(SEARCHENTRY), searchentrycmp ); if ( matchedEntry ) // found the superior class { if (!aClasses[matchedEntry->nIndex].fProcessedSuperiorClasses) { fProcessedAll = FALSE; break; } hr = GetInfoFromSuperiorClasses( &(aClasses[i]), &(aClasses[matchedEntry->nIndex])); BAIL_ON_FAILURE(hr); } j++; } if ( pOIDsSup[j] == NULL ) { if ( pOIDsAux == NULL ) { aClasses[i].fProcessedSuperiorClasses = TRUE; } else { j = 0; while ( pOIDsAux[j] ) { searchEntry.pszName = pOIDsAux[j]; matchedEntry = (SEARCHENTRY *) bsearch( (SEARCHENTRY *) &searchEntry, aSearchTable, 2*nClasses, sizeof(SEARCHENTRY), searchentrycmp); if ( matchedEntry ) // found the superior class { if (!aClasses[matchedEntry->nIndex].fProcessedSuperiorClasses) { fProcessedAll = FALSE; break; } hr = GetInfoFromSuperiorClasses( &(aClasses[i]), &(aClasses[matchedEntry->nIndex])); BAIL_ON_FAILURE(hr); } j++; } if ( pOIDsAux[j] == NULL ) aClasses[i].fProcessedSuperiorClasses = TRUE; } } } nLoop++; } while ( (nLoop < MAX_LOOP_COUNT) && !fProcessedAll ); for ( i = 0; i < nClasses; i++ ) { CLASSINFO *pClass = &(aClasses[i]); DWORD j, k; // // Eliminate duplicates in MustContain // if ( pClass->pOIDsMustContain != NULL ) { SortAndRemoveDuplicateOIDs( pClass->pOIDsMustContain, &pClass->nNumOfMustContain ); } // // Eliminate duplicates in MayContain // if ( pClass->pOIDsMayContain != NULL ) { SortAndRemoveDuplicateOIDs( pClass->pOIDsMayContain, &pClass->nNumOfMayContain ); } // // Eliminate duplicates between MustContain and MayContain // if ( ( pClass->pOIDsMustContain == NULL ) || ( pClass->pOIDsMayContain == NULL ) ) { continue; } j = 0; k = 0; while ( ( pClass->pOIDsMustContain[j] != -1 ) && ( pClass->pOIDsMayContain[k] != -1 ) ) { int nMust = pClass->pOIDsMustContain[j]; int nMay = pClass->pOIDsMayContain[k]; if ( nMust < nMay ) { j++; } else if ( nMust > nMay ) { k++; } else // nMust == nMay { j++; DWORD m = k; do { pClass->pOIDsMayContain[m] = pClass->pOIDsMayContain[m+1]; m++; } while ( pClass->pOIDsMayContain[m] != -1 ); } } // // Removes the NotContain from the MustContain or MayContain list // if ( pClass->pOIDsNotContain != NULL ) { qsort( pClass->pOIDsNotContain, pClass->nNumOfNotContain, sizeof(pClass->pOIDsNotContain[0]), intcmp ); j = 0; k = 0; while ( ( pClass->pOIDsMustContain[j] != -1 ) && ( pClass->pOIDsNotContain[k] != -1 ) ) { int nMust = pClass->pOIDsMustContain[j]; int nNot = pClass->pOIDsNotContain[k]; if ( nMust < nNot ) { j++; } else if ( nMust > nNot ) { k++; } else // nMust == nNot { k++; DWORD m = j; do { pClass->pOIDsMustContain[m] = pClass->pOIDsMustContain[m+1]; m++; } while ( pClass->pOIDsMustContain[m] != -1 ); } } j = 0; k = 0; while ( ( pClass->pOIDsMayContain[j] != -1 ) && ( pClass->pOIDsNotContain[k] != -1 ) ) { int nMay = pClass->pOIDsMayContain[j]; int nNot = pClass->pOIDsNotContain[k]; if ( nMay < nNot ) { j++; } else if ( nMay > nNot ) { k++; } else // nMay == nNot { k++; DWORD m = j; do { pClass->pOIDsMayContain[m] = pClass->pOIDsMayContain[m+1]; m++; } while ( pClass->pOIDsMayContain[m] != -1 ); } } FreeADsMem( pClass->pOIDsNotContain ); pClass->pOIDsNotContain = NULL; } } RRETURN(S_OK); error: RRETURN(hr); } VOID SortAndRemoveDuplicateOIDs( int *aOIDs, DWORD *pnNumOfOIDs ) { DWORD j, k; qsort( aOIDs, *pnNumOfOIDs, sizeof( aOIDs[0]), intcmp ); // The following code removes duplicate strings in place. // index j is the index to walk the array, and index k is the // index of the last non-duplicate entry. The array is sorted // and so we compare the string at index j and index k. // (1) If they are the same, then j++ // (2) If not the same, increment k, free the string at k and // make k point to the string at j. Then increment j and // continue. // // NOTE: aOIDs must be an array of integers that ends with -1. j = 1; k = 0; while ( aOIDs[j] != -1 ) { while ( aOIDs[j] == aOIDs[k] ) { j++; if ( aOIDs[j] == -1 ) break; } if ( aOIDs[j] != -1 ) { k++; if ( k != j ) { aOIDs[k] = aOIDs[j]; aOIDs[j] = -1; } j++; } } k++; aOIDs[k] = -1; *pnNumOfOIDs = k; } VOID FreeClassInfoArray( CLASSINFO *aClasses, DWORD nClasses ) { if ( aClasses ) { DWORD j; for ( j = 0; j < nClasses; j++ ) { FreeADsStr( aClasses[j].pszName ); FreeADsStr( aClasses[j].pszOID ); FreeADsStr( aClasses[j].pszHelpFileName ); FreeADsStr( aClasses[j].pszDescription ); DWORD k = 0; if ( aClasses[j].pOIDsSuperiorClasses ) { while ( aClasses[j].pOIDsSuperiorClasses[k] ) FreeADsStr( aClasses[j].pOIDsSuperiorClasses[k++]); FreeADsMem( aClasses[j].pOIDsSuperiorClasses ); } k = 0; if ( aClasses[j].pOIDsAuxClasses ) { while ( aClasses[j].pOIDsAuxClasses[k] ) FreeADsStr( aClasses[j].pOIDsAuxClasses[k++]); FreeADsMem( aClasses[j].pOIDsAuxClasses ); } if ( aClasses[j].pOIDsMustContain ) { FreeADsMem( aClasses[j].pOIDsMustContain ); } if ( aClasses[j].pOIDsMayContain ) { FreeADsMem( aClasses[j].pOIDsMayContain ); } if ( aClasses[j].pOIDsNotContain ) { FreeADsMem( aClasses[j].pOIDsNotContain ); } } FreeADsMem( aClasses ); } } DWORD ReadSchemaInfoFromFileWithHandle( HANDLE hFile, LPTSTR **paValuesAttrTypes, int *pnCountAttrTypes, LPTSTR **paValuesObjClasses, int *pnCountObjClasses, LPTSTR **paValuesRules, int *pnCountRules, LPBYTE *pFileBuffer ) { DWORD err = NO_ERROR; DWORD dwFileSize = 0; DWORD dwBytesRead = 0; LPTSTR pLine = NULL; LPTSTR pEndOfLine = NULL; DWORD nCount; DWORD nType; DWORD dwStatus = NO_ERROR; // // Even though the calling routine has an exception handler, // we need one over here also as this we need to close the file // over here, we do not have the file handle in the calling routine. // __try { dwFileSize = GetFileSize( hFile, NULL ); if ( dwFileSize == -1 ) { err = GetLastError(); goto cleanup; } else if ( dwFileSize == 0 ) { err = ERROR_FILE_INVALID; goto cleanup; } *pFileBuffer = (LPBYTE) AllocADsMem( dwFileSize ); if ( *pFileBuffer == NULL ) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } if ( !ReadFile( hFile, *pFileBuffer, dwFileSize, &dwBytesRead, NULL )) { err = GetLastError(); goto cleanup; } for ( pLine = ((LPTSTR) *pFileBuffer) + 1; // go past Unicode BOM marker pLine < (LPTSTR) ( *pFileBuffer + dwFileSize ); pLine = pEndOfLine + 1 ) { if ( pEndOfLine = _tcschr( pLine, TEXT('\n'))) *pEndOfLine = 0; if ( _tcsicmp( pLine, TEXT("[attributeTypes]")) == 0 ) { nType = ID_ATTRTYPES; continue; } else if ( _tcsicmp( pLine, TEXT("[objectClasses]")) == 0 ) { nType = ID_OBJCLASSES; continue; } else if ( _tcsicmp( pLine, TEXT("[DITContentRules]")) == 0 ) { nType = ID_DITCONTENTRULES; continue; } switch ( nType ) { case ID_ATTRTYPES: (*pnCountAttrTypes)++; break; case ID_OBJCLASSES: (*pnCountObjClasses)++; break; case ID_DITCONTENTRULES: (*pnCountRules)++; break; } } *paValuesAttrTypes = (LPTSTR *) AllocADsMem( *pnCountAttrTypes * sizeof(LPTSTR)); if ( *paValuesAttrTypes == NULL ) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } *paValuesObjClasses = (LPTSTR *) AllocADsMem( *pnCountObjClasses * sizeof(LPTSTR)); if ( *paValuesObjClasses == NULL ) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } *paValuesRules = (LPTSTR *) AllocADsMem( *pnCountRules * sizeof(LPTSTR)); if ( *paValuesRules == NULL ) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } for ( pLine = ((LPTSTR) *pFileBuffer) + 1;// go past Unicode BOM marker pLine < (LPTSTR) ( *pFileBuffer + dwFileSize ); pLine += ( _tcslen(pLine) + 1) ) { if ( _tcsicmp( pLine, TEXT("[attributeTypes]")) == 0 ) { nCount = 0; nType = ID_ATTRTYPES; continue; } else if ( _tcsicmp( pLine, TEXT("[objectClasses]")) == 0 ) { nCount = 0; nType = ID_OBJCLASSES; continue; } else if ( _tcsicmp( pLine, TEXT("[DITContentRules]")) == 0 ) { nCount = 0; nType = ID_DITCONTENTRULES; continue; } switch ( nType ) { case ID_ATTRTYPES: (*paValuesAttrTypes)[nCount++] = pLine; break; case ID_OBJCLASSES: (*paValuesObjClasses)[nCount++] = pLine; break; case ID_DITCONTENTRULES: (*paValuesRules)[nCount++] = pLine; break; } } } __except (EXCEPTION_EXECUTE_HANDLER) { err = GetExceptionCode(); if (dwStatus != EXCEPTION_ACCESS_VIOLATION) { ADsDebugOut((DEB_ERROR, "Processing Schema Info:Unknown Exception %d\n", err)); } } cleanup: CloseHandle( hFile ); if ( err ) { *pnCountAttrTypes = 0; *pnCountObjClasses = 0; *pnCountRules = 0; if ( *paValuesAttrTypes ) FreeADsMem( *paValuesAttrTypes ); if ( *paValuesObjClasses ) FreeADsMem( *paValuesObjClasses ); if ( *paValuesRules ) FreeADsMem( *paValuesRules ); if ( *pFileBuffer ) FreeADsMem( *pFileBuffer ); *paValuesAttrTypes = NULL; *paValuesObjClasses = NULL; *paValuesRules = NULL; *pFileBuffer = NULL; } return err; } #ifdef WIN95 DWORD ReadSchemaInfoFromFileA( LPSTR pszFile, LPTSTR **paValuesAttrTypes, int *pnCountAttrTypes, LPTSTR **paValuesObjClasses, int *pnCountObjClasses, LPTSTR **paValuesRules, int *pnCountRules, LPBYTE *pFileBuffer ) { DWORD err = NO_ERROR; HANDLE hFile = NULL; *pnCountAttrTypes = 0; *pnCountObjClasses = 0; *pnCountRules = 0; hFile = CreateFileA( pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) return GetLastError(); return ( ReadSchemaInfoFromFileWithHandle( hFile, paValuesAttrTypes, pnCountAttrTypes, paValuesObjClasses, pnCountObjClasses, paValuesRules, pnCountRules, pFileBuffer ) ); } #endif DWORD ReadSchemaInfoFromFileW( LPTSTR pszFile, LPTSTR **paValuesAttrTypes, int *pnCountAttrTypes, LPTSTR **paValuesObjClasses, int *pnCountObjClasses, LPTSTR **paValuesRules, int *pnCountRules, LPBYTE *pFileBuffer ) { DWORD err = NO_ERROR; HANDLE hFile = NULL; *pnCountAttrTypes = 0; *pnCountObjClasses = 0; *pnCountRules = 0; hFile = CreateFile( pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) return GetLastError(); return ( ReadSchemaInfoFromFileWithHandle( hFile, paValuesAttrTypes, pnCountAttrTypes, paValuesObjClasses, pnCountObjClasses, paValuesRules, pnCountRules, pFileBuffer ) ); } DWORD StoreSchemaInfoToFileWithHandle( HANDLE hFile, LPTSTR *aValuesAttribTypes, int nCountAttribTypes, LPTSTR *aValuesObjClasses, int nCountObjClasses, LPTSTR *aValuesRules, int nCountRules ) { DWORD err = NO_ERROR; TCHAR szEndOfLine[2] = TEXT("\n"); TCHAR szSection[MAX_PATH]; DWORD dwBytesWritten; int i; szSection[0] = 0xFEFF; // Unicode BOM marker if ( !WriteFile( hFile, szSection, sizeof(TCHAR), &dwBytesWritten, NULL )) { err = GetLastError(); goto cleanup; } _tcscpy( szSection, TEXT("[attributeTypes]\n")); if ( !WriteFile( hFile, szSection, _tcslen( szSection ) * sizeof(TCHAR), &dwBytesWritten, NULL )) { err = GetLastError(); goto cleanup; } for ( i = 0; i < nCountAttribTypes; i++ ) { if ( !WriteFile( hFile, aValuesAttribTypes[i], _tcslen( aValuesAttribTypes[i] ) * sizeof(TCHAR), &dwBytesWritten, NULL )) { err = GetLastError(); goto cleanup; } if ( !WriteFile( hFile, szEndOfLine, sizeof(TCHAR), &dwBytesWritten, NULL )) { err = GetLastError(); goto cleanup; } } _tcscpy( szSection, TEXT("[objectClasses]\n")); if ( !WriteFile( hFile, szSection, _tcslen( szSection ) * sizeof(TCHAR), &dwBytesWritten, NULL )) { err = GetLastError(); goto cleanup; } for ( i = 0; i < nCountObjClasses; i++ ) { if ( !WriteFile( hFile, aValuesObjClasses[i], _tcslen( aValuesObjClasses[i] ) * sizeof(TCHAR), &dwBytesWritten, NULL )) { err = GetLastError(); goto cleanup; } if ( !WriteFile( hFile, szEndOfLine, sizeof(TCHAR), &dwBytesWritten, NULL )) { err = GetLastError(); goto cleanup; } } _tcscpy( szSection, TEXT("[DITContentRules]\n")); if ( !WriteFile( hFile, szSection, _tcslen( szSection ) * sizeof(TCHAR), &dwBytesWritten, NULL )) { err = GetLastError(); goto cleanup; } for ( i = 0; i < nCountRules; i++ ) { if ( !WriteFile( hFile, aValuesRules[i], _tcslen( aValuesRules[i] ) * sizeof(TCHAR), &dwBytesWritten, NULL )) { err = GetLastError(); goto cleanup; } if ( !WriteFile( hFile, szEndOfLine, sizeof(TCHAR), &dwBytesWritten, NULL )) { err = GetLastError(); goto cleanup; } } cleanup: CloseHandle( hFile ); return err; } #ifdef WIN95 DWORD StoreSchemaInfoToFileA( LPSTR pszFile, LPTSTR *aValuesAttribTypes, int nCountAttribTypes, LPTSTR *aValuesObjClasses, int nCountObjClasses, LPTSTR *aValuesRules, int nCountRules ) { DWORD err = NO_ERROR; HANDLE hFile = NULL; hFile = CreateFileA( pszFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) { err = GetLastError(); if ( err == ERROR_PATH_NOT_FOUND ) { // // The directory is not created yet, create it now. // LPSTR pszTemp = strstr( pszFile, "SchCache\\"); pszTemp += strlen("SchCache"); *pszTemp = 0; if ( !CreateDirectoryA( pszFile, NULL )) return GetLastError(); *pszTemp = '\\'; hFile = CreateFileA( pszFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) return GetLastError(); err = NO_ERROR; } else { return err; } } err = StoreSchemaInfoToFileWithHandle( hFile, aValuesAttribTypes, nCountAttribTypes, aValuesObjClasses, nCountObjClasses, aValuesRules, nCountRules ); if ( err != NO_ERROR ) DeleteFileA( pszFile ); return err; } #endif DWORD StoreSchemaInfoToFileW( LPTSTR pszFile, LPTSTR *aValuesAttribTypes, int nCountAttribTypes, LPTSTR *aValuesObjClasses, int nCountObjClasses, LPTSTR *aValuesRules, int nCountRules ) { DWORD err = NO_ERROR; HANDLE hFile = NULL; hFile = CreateFile( pszFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) { err = GetLastError(); if ( err == ERROR_PATH_NOT_FOUND ) { // The directory is not created yet, create it now. LPTSTR pszTemp = _tcsrchr( pszFile, TEXT('\\')); *pszTemp = 0; if ( !CreateDirectory( pszFile, NULL )) return GetLastError(); *pszTemp = TEXT('\\'); hFile = CreateFile( pszFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) return GetLastError(); err = NO_ERROR; } else { return err; } } err = StoreSchemaInfoToFileWithHandle( hFile, aValuesAttribTypes, nCountAttribTypes, aValuesObjClasses, nCountObjClasses, aValuesRules, nCountRules ); if ( err != NO_ERROR ) DeleteFile( pszFile ); return err; } DWORD ReadSchemaInfoFromRegistry( HKEY hKey, LPWSTR pszServer, LPTSTR **paValuesAttribTypes, int *pnCountAttribTypes, LPTSTR **paValuesObjClasses, int *pnCountObjClasses, LPTSTR **paValuesRules, int *pnCountRules, LPBYTE *pBuffer ) { DWORD err = NO_ERROR; LPTSTR pszPath = NULL; LPTSTR pszExpandedPath = NULL; LPSTR pszPathAscii = NULL; LPSTR pszExpandedPathAscii = NULL; LPTSTR pszTempPath = NULL; DWORD dwLength = 0; DWORD dwType; // // On chk bits would be good to make sure that this never // happens as some schema stuff is messed up in this case. // if (!pszServer) { ADsAssert(!"No server name was passed"); } // // Get the file name that is used to store the schema info // from the registry. // err = RegQueryValueEx( hKey, SCHEMA_FILE_NAME, NULL, &dwType, NULL, &dwLength ); #ifndef WIN95 if ( err ) goto cleanup; #else if (err == ERROR_MORE_DATA ) { // // Continue cause Win9x is dumb. // err = 0; } else goto cleanup; #endif pszPath = (LPTSTR) AllocADsMem( dwLength ); if ( pszPath == NULL ) return ERROR_NOT_ENOUGH_MEMORY; err = RegQueryValueEx( hKey, SCHEMA_FILE_NAME, NULL, &dwType, (LPBYTE) pszPath, &dwLength ); if ( err ) goto cleanup; // // Expand the path // pszExpandedPath = (LPTSTR) AllocADsMem( MAX_PATH * sizeof(WCHAR)); if ( pszExpandedPath == NULL ) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } // // At this point we want to rename all files called default.sch // We look for default.sch in the string and then create a new // string. For example if the string is %systemroot%\SCHEMA_DIR\ // Default.sch we will replace with %systemroot%\SCHEMA_DIR\ // pszServer.sch. This change will force schema to be dropped // and we will end up picking up and storing the schema under the // correct name. This will ensure that 2 different forests do // not end up creating conflicting default.sch files that we // never recover from internally. This excercise is futile unless // a server name was passed in (should be the case always). // if (pszPath && *pszPath && pszServer) { // // Look for default.sch // pszTempPath = wcsstr( pszPath, DEFAULT_SCHEMA_FILE_NAME_WITH_EXT); if (pszTempPath) { // // We now need to replace this string. // DWORD dwLenStr, dwLenNewPath; dwLenStr = pszTempPath - pszPath; // // Now build the new string from the old one. Length is // pszServer + the old piece upto schema file name. // dwLenNewPath = wcslen(pszServer) + wcslen(SCHEMA_FILE_NAME_EXT) + dwLenStr + 1; pszTempPath = (LPTSTR) AllocADsMem(dwLenNewPath * sizeof(WCHAR)); if (!pszTempPath) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } wcsncpy(pszTempPath, pszPath, dwLenStr); wcscat(pszTempPath, pszServer); wcscat(pszTempPath, SCHEMA_FILE_NAME_EXT); FreeADsMem(pszPath); pszPath = pszTempPath; pszTempPath = NULL; // // Now update the key in the registry. Ignore the error // cause there is nothing we can really do about it. // err = RegSetValueEx( hKey, SCHEMA_FILE_NAME, 0, REG_EXPAND_SZ, (CONST BYTE *) pszPath, (_tcslen(pszPath) + 1 ) * sizeof(TCHAR) ); } } dwLength = ExpandEnvironmentStrings( pszPath, pszExpandedPath, MAX_PATH ); #ifdef WIN95 // // Just in case 3 bytes for each WCHAR rather than just 2. // pszExpandedPathAscii = (LPSTR) AllocADsMem( MAX_PATH * sizeof(char) * 3); if (!pszExpandedPathAscii) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } if (err = ConvertToAscii(pszPath, &pszPathAscii)) { goto cleanup; } dwLength = ExpandEnvironmentStringsA( pszPathAscii, pszExpandedPathAscii, MAX_PATH * sizeof(char) * 3 ); #endif if ( dwLength == 0 ) { err = GetLastError(); goto cleanup; } else if ( dwLength > MAX_PATH ) { FreeADsMem( pszExpandedPath ); pszExpandedPath = (LPTSTR) AllocADsMem( dwLength * sizeof(WCHAR)); if ( pszExpandedPath == NULL ) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } dwLength = ExpandEnvironmentStrings( pszPath, pszExpandedPath, dwLength ); if ( dwLength == 0 ) { err = GetLastError(); goto cleanup; } } // // Now, read the info from the file // #ifndef WIN95 err = ReadSchemaInfoFromFileW( pszExpandedPath, paValuesAttribTypes, pnCountAttribTypes, paValuesObjClasses, pnCountObjClasses, paValuesRules, pnCountRules, pBuffer ); #else err = ReadSchemaInfoFromFileA( pszExpandedPathAscii, paValuesAttribTypes, pnCountAttribTypes, paValuesObjClasses, pnCountObjClasses, paValuesRules, pnCountRules, pBuffer ); #endif cleanup: if ( pszPath ) FreeADsMem( pszPath ); if ( pszExpandedPath ) FreeADsMem( pszExpandedPath ); if (pszTempPath) { FreeADsMem(pszTempPath); } #ifdef WIN95 if (pszPathAscii) { FreeADsMem(pszPathAscii); } if (pszExpandedPathAscii) { FreeADsMem(pszExpandedPathAscii); } #endif return err; } DWORD StoreSchemaInfoInRegistry( HKEY hKey, LPTSTR pszServer, LPTSTR pszTime, LPTSTR *aValuesAttribTypes, int nCountAttribTypes, LPTSTR *aValuesObjClasses, int nCountObjClasses, LPTSTR *aValuesRules, int nCountRules, BOOL fProcessAUX ) { DWORD err = NO_ERROR; LPTSTR pszPath = NULL; LPTSTR pszExpandedPath = NULL; LPSTR pszPathAscii = NULL; LPSTR pszExpandedPathAscii = NULL; DWORD dwLength = 0; DWORD dwType; DWORD dwProcessAUX; // // See if we can find the file name that is used to store the schema info // in the registry. // err = RegQueryValueEx( hKey, SCHEMA_FILE_NAME, NULL, &dwType, NULL, &dwLength ); if ( err == NO_ERROR ) { pszPath = (LPTSTR) AllocADsMem( dwLength ); if ( pszPath == NULL ) return ERROR_NOT_ENOUGH_MEMORY; err = RegQueryValueEx( hKey, SCHEMA_FILE_NAME, NULL, &dwType, (LPBYTE) pszPath, &dwLength ); if ( err ) goto cleanup; } err = NO_ERROR; if ( pszPath == NULL ) // Cannot read from the registry { // // Allocate pszPath to be either MAX_PATH chars or sufficient // to hold %SystemRoot%//.sch, whichever // is larger. // if (pszServer) { DWORD cbPath = (MAX_PATH > (_tcslen(TEXT("%SystemRoot%\\")) + _tcslen(SCHEMA_DIR_NAME) + _tcslen(pszServer) + _tcslen(SCHEMA_FILE_NAME_EXT))) ? (MAX_PATH * sizeof(WCHAR)) : (2 * _tcslen(pszServer) * sizeof(WCHAR)); pszPath = (LPTSTR) AllocADsMem(cbPath); } else{ // // pszServe can be NULL in the ldapc layer users case // pszPath = (LPTSTR) AllocADsMem(MAX_PATH * sizeof(WCHAR)); } if ( pszPath == NULL ) return ERROR_NOT_ENOUGH_MEMORY; // // Build the path of the schema info file // #ifndef WIN95 _tcscpy( pszPath, TEXT("%SystemRoot%\\")); #else _tcscpy( pszPath, TEXT("%WINDIR%\\")); #endif _tcscat( pszPath, SCHEMA_DIR_NAME); if (pszServer) { // // Server strings may have a port number in them, // e.g., "ntdev:389". We need to change this to // "ntdev_389", otherwise the colon will give us trouble // in the filename. // LPTSTR pszColon = _tcschr(pszServer, (TCHAR)':'); if (!pszColon) { _tcscat( pszPath, pszServer); } else { _tcsncat( pszPath, pszServer, pszColon-pszServer); _tcscat ( pszPath, TEXT("_")); _tcscat ( pszPath, pszColon+1); // the number after the colon } } else { _tcscat( pszPath, DEFAULT_SCHEMA_FILE_NAME); } _tcscat( pszPath, SCHEMA_FILE_NAME_EXT ); } pszExpandedPath = (LPTSTR) AllocADsMem( MAX_PATH * sizeof(WCHAR) ); if ( pszExpandedPath == NULL ) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } dwLength = ExpandEnvironmentStrings( pszPath, pszExpandedPath, MAX_PATH ); #ifdef WIN95 pszExpandedPathAscii = (LPSTR) AllocADsMem(MAX_PATH * sizeof(char) * 3); if (!pszExpandedPathAscii) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } if (err = ConvertToAscii(pszPath, &pszPathAscii)) { goto cleanup; } dwLength = ExpandEnvironmentStringsA( pszPathAscii, pszExpandedPathAscii, MAX_PATH * sizeof(char) * 3 ); #endif if ( dwLength == 0 ) { err = GetLastError(); goto cleanup; } else if ( dwLength > MAX_PATH ) { FreeADsMem( pszExpandedPath ); pszExpandedPath = (LPTSTR) AllocADsMem( dwLength * sizeof(WCHAR) ); if ( pszExpandedPath == NULL ) { err = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } dwLength = ExpandEnvironmentStrings( pszPath, pszExpandedPath, dwLength ); if ( dwLength == 0 ) { err = GetLastError(); goto cleanup; } } // // Write the schema information into the file // #ifndef WIN95 err = StoreSchemaInfoToFileW( pszExpandedPath, aValuesAttribTypes, nCountAttribTypes, aValuesObjClasses, nCountObjClasses, aValuesRules, nCountRules ); #else err = StoreSchemaInfoToFileA( pszExpandedPathAscii, aValuesAttribTypes, nCountAttribTypes, aValuesObjClasses, nCountObjClasses, aValuesRules, nCountRules ); #endif if ( err ) goto cleanup; // // Write the information into the registry // err = RegSetValueEx( hKey, SCHEMA_FILE_NAME, 0, REG_EXPAND_SZ, (CONST BYTE *) pszPath, (_tcslen(pszPath) + 1 ) * sizeof(TCHAR)); if ( err ) goto cleanup; err = RegSetValueEx( hKey, SCHEMA_TIME, 0, REG_SZ, (CONST BYTE *) pszTime, (_tcslen(pszTime) + 1 ) * sizeof(TCHAR)); if ( err ) goto cleanup; dwProcessAUX = (TRUE == fProcessAUX) ? 1: 0; err = RegSetValueExW( hKey, SCHEMA_PROCESSAUX, 0, REG_DWORD, (CONST BYTE *) &dwProcessAUX, sizeof(dwProcessAUX)); cleanup: if ( pszPath ) FreeADsMem( pszPath ); if ( pszExpandedPath ) FreeADsMem( pszExpandedPath ); #ifdef WIN95 if (pszPathAscii) { FreeADsMem(pszPathAscii); } if (pszExpandedPathAscii) { FreeADsMem(pszExpandedPathAscii); } #endif return err; } //+--------------------------------------------------------------------------- // Function: AttributeTypeDescription // // Synopsis: Parses an attribute type description. // It parses the following grammar rules // // ::= "(" // -- AttributeType identifier // [ "NAME" ] -- name used in AttributeType // [ "DESC" ] // [ "OBSOLETE" ] // [ "SUP" ] -- derived from this other AttributeType // [ "EQUALITY" ] -- Matching Rule name // [ "ORDERING" ] -- Matching Rule name // [ "SUBSTR" ] -- Matching Rule name // [ "SYNTAX" ] -- see section 4.2 // [ "SINGLE-VALUE" ] -- default multi-valued // [ "COLLECTIVE" ] -- default not collective // [ "DYNAMIC" ] -- default not dynamic // [ "NO-USER-MODIFICATION" ] -- default user modifiable // [ "USAGE" ] -- default user applications // ")" // // ::= // "userApplications" // | "directoryOperation" // | "distributedOperation" -- DSA-shared // | "dSAOperation" -- DSA-specific, value depends on server // // // Arguments: [LPTSTR] pszAttrType : The string to parse // // Returns: [HRESULT] 0 if successful, error HRESULT if not // // Modifies: pTokenizer (consumes the input buffer) // // History: 9-3-96 yihsins Created. // //---------------------------------------------------------------------------- HRESULT AttributeTypeDescription( LPTSTR pszAttrType, PPROPERTYINFO pPropertyInfo, LPWSTR **pppszNames, PDWORD pdwNameCount ) { TCHAR szToken[MAX_TOKEN_LENGTH]; DWORD dwToken; HRESULT hr; CSchemaLexer Tokenizer( pszAttrType ); hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken != TOKEN_OPENBRACKET ) RRETURN(HRESULT_FROM_WIN32(ERROR_INVALID_DATA)); // // use TRUE flag as there is a chance that from // some schemas, we get bad data that has no GUID // hr = Oid( &Tokenizer, &(pPropertyInfo->pszOID), TRUE); BAIL_IF_ERROR(hr); while ( TRUE ) { LPWSTR *ppszDirStrings; DWORD dwCount,dwCtr; ppszDirStrings = NULL; dwCount = 0; hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken == TOKEN_IDENTIFIER ) Tokenizer.IsKeyword( szToken, &dwToken ); switch ( dwToken ) { case TOKEN_CLOSEBRACKET: RRETURN(S_OK); case TOKEN_NAME: hr = DirectoryStrings( &Tokenizer, &ppszDirStrings, &dwCount ); BAIL_IF_ERROR(hr); if (!ppszDirStrings) { // // We need at least one name. // BAIL_IF_ERROR( hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA) ); } // // For now we will only support the first name in the list. // pPropertyInfo->pszPropertyName = ppszDirStrings[0]; // // The remaining values if any will require additional // processing in FillPropertyInfoArray. // *pppszNames = ppszDirStrings; *pdwNameCount = dwCount; break; case TOKEN_DESC: hr = DirectoryString( &Tokenizer, &(pPropertyInfo->pszDescription)); break; case TOKEN_OBSOLETE: // attribute is obsolete (RFC 2252) pPropertyInfo->fObsolete = TRUE; break; case TOKEN_SUP: hr = Oid( &Tokenizer, &(pPropertyInfo->pszOIDSup)); break; case TOKEN_EQUALITY: hr = Oid( &Tokenizer, &(pPropertyInfo->pszOIDEquality)); break; case TOKEN_ORDERING: hr = Oid( &Tokenizer, &(pPropertyInfo->pszOIDOrdering)); break; case TOKEN_SUBSTR: hr = Oid( &Tokenizer, &(pPropertyInfo->pszOIDSubstr)); break; case TOKEN_SYNTAX: hr = DirectoryString( &Tokenizer, &(pPropertyInfo->pszSyntax)); // // It need not necessarily be a DirectoryString can also be // an OID. So if DirectoryString fails, we should try OID. // if (FAILED(hr) && (hr == HRESULT_FROM_WIN32(ERROR_INVALID_DATA)) ) { Tokenizer.PushBackToken(); hr = Oid( &Tokenizer, &(pPropertyInfo->pszSyntax)); } break; case TOKEN_SINGLE_VALUE: pPropertyInfo->fSingleValued = TRUE; break; case TOKEN_COLLECTIVE: pPropertyInfo->fCollective = TRUE; break; case TOKEN_DYNAMIC: pPropertyInfo->fDynamic = TRUE; break; case TOKEN_NO_USER_MODIFICATION: pPropertyInfo->fNoUserModification = TRUE; break; case TOKEN_USAGE: hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if (_tcsicmp(szToken, TEXT("userApplications")) == 0) pPropertyInfo->dwUsage = ATTR_USAGE_USERAPPLICATIONS; else if (_tcsicmp(szToken, TEXT("directoryOperation")) == 0) pPropertyInfo->dwUsage = ATTR_USAGE_DIRECTORYOPERATION; else if (_tcsicmp(szToken, TEXT("distributedOperation")) == 0) pPropertyInfo->dwUsage = ATTR_USAGE_DISTRIBUTEDOPERATION; else if (_tcsicmp(szToken, TEXT("dSAOperation")) == 0) pPropertyInfo->dwUsage = ATTR_USAGE_DSAOPERATION; break; case TOKEN_OPEN_CURLY : hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if (dwToken != TOKEN_IDENTIFIER) { BAIL_IF_ERROR(hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA)); } hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if (dwToken != TOKEN_CLOSE_CURLY) { BAIL_IF_ERROR(hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA)); } break; case TOKEN_X : // // This means that this token and the following // DirectoryStrings token (which can be empty string) // need to be ignored. // hr = DirectoryStrings( &Tokenizer, &ppszDirStrings, &dwCount ); // // If we could not process this then we need to BAIL // as the Tokenizer is not in a recoverable state. // BAIL_IF_ERROR(hr); // // Free the strings that came back. // FreeDirectoryStrings( ppszDirStrings, dwCount ); ppszDirStrings = NULL; break; default: hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); break; } BAIL_IF_ERROR(hr); } cleanup: RRETURN(hr); } //+--------------------------------------------------------------------------- // Function: ObjectClassDescription // // Synopsis: Parses an object class description. // It parses the following grammar rules // // ::= "(" // -- ObjectClass identifier // [ "NAME" ] // [ "DESC" ] // [ "OBSOLETE" ] // [ "SUP" ] -- Superior ObjectClasses // [ ( "ABSTRACT" | "STRUCTURAL" | "AUXILIARY" )] -- default structural // [ "MUST" ] -- AttributeTypes // [ "MAY" ] -- AttributeTypes // ")" // // Arguments: [LPTSTR] pszObjectClass : The string to parse // // Returns: [HRESULT] 0 if successful, error HRESULT if not // // Modifies: pTokenizer (consumes the input buffer) // // History: 9-3-96 yihsins Created. // //---------------------------------------------------------------------------- HRESULT ObjectClassDescription( LPTSTR pszObjectClass, PCLASSINFO pClassInfo, SEARCHENTRY *aPropSearchTable, DWORD dwSearchTableCount, LPWSTR **pppszNewNames, PDWORD pdwNameCount ) { TCHAR szToken[MAX_TOKEN_LENGTH]; LPWSTR pszTemp; DWORD dwToken; HRESULT hr; CSchemaLexer Tokenizer( pszObjectClass ); hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken != TOKEN_OPENBRACKET ) RRETURN(HRESULT_FROM_WIN32(ERROR_INVALID_DATA)); // // use TRUE flag as there is a chance that from // some schemas, we get bad data that has no GUID // hr = Oid( &Tokenizer, &(pClassInfo->pszOID), TRUE); BAIL_IF_ERROR(hr); while ( TRUE ) { LPWSTR *ppszDirStrings; DWORD dwCount,dwCtr; ppszDirStrings = NULL; dwCount = 0; hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken == TOKEN_IDENTIFIER ) Tokenizer.IsKeyword( szToken, &dwToken ); switch ( dwToken ) { case TOKEN_CLOSEBRACKET: RRETURN(S_OK); case TOKEN_NAME: hr = DirectoryStrings( &Tokenizer, &ppszDirStrings, &dwCount ); BAIL_IF_ERROR(hr); if (!ppszDirStrings) { // // We need at least one name. // BAIL_IF_ERROR( hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA) ); } // // For now we will only support the first name in the list. // pClassInfo->pszName = ppszDirStrings[0]; // // The remaining strings will need additional processing // in fillClassInfoArray // *pppszNewNames = ppszDirStrings; *pdwNameCount = dwCount; break; case TOKEN_DESC: hr = DirectoryString(&Tokenizer,&(pClassInfo->pszDescription)); break; case TOKEN_OBSOLETE: // class is obsolete (RFC 2252) pClassInfo->fObsolete = TRUE; break; case TOKEN_SUP: hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); Tokenizer.PushBackToken(); if ( dwToken == TOKEN_QUOTE ) { DWORD dwNumStrings = 0; LPWSTR *ppszTmp = NULL; while (dwToken == TOKEN_QUOTE) { hr = DirectoryString( &Tokenizer, &(pszTemp)); BAIL_IF_ERROR(hr); if (dwNumStrings == 0) { pClassInfo->pOIDsSuperiorClasses = (LPWSTR *) AllocADsMem(sizeof(LPWSTR) * 2); } else { ppszTmp = (LPWSTR *) ReallocADsMem( pClassInfo->pOIDsSuperiorClasses, sizeof(LPWSTR) * (dwNumStrings + 1), sizeof(LPWSTR) * (dwNumStrings + 2) ); pClassInfo->pOIDsSuperiorClasses = ppszTmp; } if ( pClassInfo->pOIDsSuperiorClasses == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } pClassInfo->pOIDsSuperiorClasses[dwNumStrings] = pszTemp; pClassInfo->pOIDsSuperiorClasses[++dwNumStrings] = NULL; hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); Tokenizer.PushBackToken(); } // while } // the token was not a quote else { hr = Oids(&Tokenizer, &(pClassInfo->pOIDsSuperiorClasses),NULL); } break; case TOKEN_ABSTRACT: pClassInfo->dwType = CLASS_TYPE_ABSTRACT; break; case TOKEN_STRUCTURAL: pClassInfo->dwType = CLASS_TYPE_STRUCTURAL; break; case TOKEN_AUXILIARY: pClassInfo->dwType = CLASS_TYPE_AUXILIARY; break; case TOKEN_MUST: hr = PropOids(&Tokenizer, &(pClassInfo->pOIDsMustContain), &(pClassInfo->nNumOfMustContain), aPropSearchTable, dwSearchTableCount ); break; case TOKEN_MAY: hr = PropOids(&Tokenizer, &(pClassInfo->pOIDsMayContain), &(pClassInfo->nNumOfMayContain), aPropSearchTable, dwSearchTableCount ); break; case TOKEN_X: // // This is provider specific info - parse and ignore. // hr = DirectoryStrings( &Tokenizer, &ppszDirStrings, &dwCount ); // // If we could not process this then we need to BAIL // as the Tokenizer is not in a recoverable state. // BAIL_IF_ERROR(hr); if (ppszDirStrings) { FreeDirectoryStrings( ppszDirStrings, dwCount ); ppszDirStrings = NULL; } break; default: hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); break; } BAIL_IF_ERROR(hr); } cleanup: RRETURN(hr); } //+--------------------------------------------------------------------------- // Function: DITContentRuleDescription // // Synopsis: Parses an DIT content rule description. // It parses the following grammar rules // // ::= "(" // -- ObjectClass identifier // [ "NAME" ] // [ "DESC" ] // [ "OBSOLETE" ] // [ "AUX" ] -- Auxiliary ObjectClasses // [ "MUST" ] -- AttributeTypes // [ "MAY" ] -- AttributeTypes // [ "NOT" ] -- AttributeTypes // ")" // // Arguments: [LPTSTR] pszObjectClass : The string to parse // // Returns: [HRESULT] 0 if successful, error HRESULT if not // // Modifies: pTokenizer (consumes the input buffer) // // History: 9-3-96 yihsins Created. // //---------------------------------------------------------------------------- HRESULT DITContentRuleDescription( LPTSTR pszObjectClass, PCLASSINFO pClassInfo, SEARCHENTRY *aPropSearchTable, DWORD dwSearchTableCount ) { TCHAR szToken[MAX_TOKEN_LENGTH]; DWORD dwToken; HRESULT hr; CSchemaLexer Tokenizer( pszObjectClass ); hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken != TOKEN_OPENBRACKET ) RRETURN(HRESULT_FROM_WIN32(ERROR_INVALID_DATA)); hr = Oid( &Tokenizer, &(pClassInfo->pszOID)); BAIL_IF_ERROR(hr); while ( TRUE ) { hr = Tokenizer.GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken == TOKEN_IDENTIFIER ) Tokenizer.IsKeyword( szToken, &dwToken ); switch ( dwToken ) { case TOKEN_CLOSEBRACKET: RRETURN(S_OK); case TOKEN_NAME: hr = DirectoryString( &Tokenizer, NULL); // DirectoryStrings break; case TOKEN_DESC: hr = DirectoryString( &Tokenizer, NULL); break; case TOKEN_OBSOLETE: // rule is obsolete (RFC 2252) pClassInfo->fObsolete = TRUE; break; case TOKEN_AUX: hr = Oids(&Tokenizer, &(pClassInfo->pOIDsAuxClasses), NULL); break; case TOKEN_MUST: hr = PropOids(&Tokenizer, &(pClassInfo->pOIDsMustContain), &(pClassInfo->nNumOfMustContain), aPropSearchTable, dwSearchTableCount ); break; case TOKEN_MAY: hr = PropOids(&Tokenizer, &(pClassInfo->pOIDsMayContain), &(pClassInfo->nNumOfMayContain), aPropSearchTable, dwSearchTableCount ); break; case TOKEN_NOT: hr = PropOids(&Tokenizer, &(pClassInfo->pOIDsNotContain), &(pClassInfo->nNumOfNotContain), aPropSearchTable, dwSearchTableCount ); break; default: hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); break; } BAIL_IF_ERROR(hr); } cleanup: RRETURN(hr); } HRESULT Oid(CSchemaLexer * pTokenizer, LPTSTR *ppszOID, BOOL fNoGuid ) { TCHAR szToken[MAX_TOKEN_LENGTH]; DWORD dwToken; HRESULT hr; *ppszOID = NULL; hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken != TOKEN_IDENTIFIER ) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); BAIL_IF_ERROR(hr); } // // Since some people do not like to have // an OID on all attributes, we need to work around them. // This should be changed once all schemas are compliant // AjayR 11-12-98. // if (fNoGuid && _wcsicmp(szToken, L"NAME") == 0) { *ppszOID = AllocADsStr(L""); pTokenizer->PushBackToken(); } else *ppszOID = AllocADsStr( szToken ); if ( *ppszOID == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } cleanup: if ( FAILED(hr)) { if ( *ppszOID ) { FreeADsStr( *ppszOID ); *ppszOID = NULL; } } RRETURN(hr); } HRESULT Oids(CSchemaLexer * pTokenizer, LPTSTR **pOIDs, DWORD *pnNumOfOIDs ) { TCHAR szToken[MAX_TOKEN_LENGTH]; DWORD dwToken; HRESULT hr; DWORD nCount = 0; DWORD nCurrent = 0; *pOIDs = NULL; if ( pnNumOfOIDs ) *pnNumOfOIDs = 0; hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken == TOKEN_IDENTIFIER ) { // All classes are subclasses of "top", and hence must contain // "objectClass" attribute. Add the "objectClass" attribute here // to prevent processing later. nCount = 2; *pOIDs = (LPTSTR *) AllocADsMem( sizeof(LPTSTR) * nCount); if ( *pOIDs == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } (*pOIDs)[nCurrent] = AllocADsStr( szToken ); if ( (*pOIDs)[nCurrent] == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } (*pOIDs)[++nCurrent] = NULL; } else if ( dwToken == TOKEN_OPENBRACKET ) { nCount = 10; *pOIDs = (LPTSTR *) AllocADsMem( sizeof(LPTSTR) * nCount); if ( *pOIDs == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } do { hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken == TOKEN_IDENTIFIER ) { if ( nCurrent == nCount ) { *pOIDs = (LPTSTR *) ReallocADsMem( *pOIDs, sizeof(LPTSTR) * nCount, sizeof(LPTSTR) * nCount * 2); if ( *pOIDs == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } nCount *= 2; } (*pOIDs)[nCurrent] = AllocADsStr( szToken ); if ( (*pOIDs)[nCurrent] == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } nCurrent++; } hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); } while ( dwToken == TOKEN_DOLLAR ); if ( dwToken != TOKEN_CLOSEBRACKET ) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); BAIL_IF_ERROR(hr); } if ( nCurrent == nCount ) { // Need one extra NULL entry at the end of the array *pOIDs = (LPTSTR *) ReallocADsMem( *pOIDs, sizeof(LPTSTR) * nCount, sizeof(LPTSTR) * (nCount + 1)); if ( *pOIDs == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } nCount += 1; } } else { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); BAIL_IF_ERROR(hr); } if ( pnNumOfOIDs ) *pnNumOfOIDs = nCurrent; cleanup: if ( FAILED(hr)) { if ( *pOIDs ) { for ( DWORD i = 0; i < nCount; i++ ) { if ( (*pOIDs)[i] ) FreeADsStr( (*pOIDs)[i] ); } FreeADsMem( *pOIDs ); *pOIDs = NULL; } } RRETURN(hr); } HRESULT PropOids(CSchemaLexer * pTokenizer, int **pOIDs, DWORD *pnNumOfOIDs, SEARCHENTRY *aPropSearchTable, DWORD dwSearchTableCount ) { TCHAR szToken[MAX_TOKEN_LENGTH]; DWORD dwToken; HRESULT hr; DWORD nCount = 0; DWORD nCurrent = 0; *pOIDs = NULL; if ( pnNumOfOIDs ) *pnNumOfOIDs = 0; hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken == TOKEN_IDENTIFIER ) { int nIndex = FindSearchTableIndex( szToken, aPropSearchTable, dwSearchTableCount ); if ( nIndex != -1 ) { nCount = 2; *pOIDs = (int *) AllocADsMem( sizeof(int) * nCount); if ( *pOIDs == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } (*pOIDs)[nCurrent] = nIndex; (*pOIDs)[++nCurrent] = -1; } } else if ( dwToken == TOKEN_OPENBRACKET ) { nCount = 10; *pOIDs = (int *) AllocADsMem( sizeof(int) * nCount); if ( *pOIDs == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } do { hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken == TOKEN_CLOSEBRACKET ) { FreeADsMem( *pOIDs ); *pOIDs = NULL; goto cleanup; } if ( dwToken == TOKEN_IDENTIFIER ) { int nIndex = FindSearchTableIndex( szToken, aPropSearchTable, dwSearchTableCount ); if ( nIndex != -1 ) { if ( nCurrent == nCount ) { *pOIDs = (int *) ReallocADsMem( *pOIDs, sizeof(int) * nCount, sizeof(int) * nCount * 2); if ( *pOIDs == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } nCount *= 2; } (*pOIDs)[nCurrent++] = nIndex; } // else we cannot find the property, so skip over it. } hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); } while ( dwToken == TOKEN_DOLLAR ); if ( dwToken != TOKEN_CLOSEBRACKET ) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); BAIL_IF_ERROR(hr); } if ( nCurrent == nCount ) { // Need one extra NULL entry at the end of the array *pOIDs = (int *) ReallocADsMem( *pOIDs, sizeof(int) * nCount, sizeof(int) * (nCount + 1)); if ( *pOIDs == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } nCount += 1; } (*pOIDs)[nCurrent] = -1; } else { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); BAIL_IF_ERROR(hr); } if ( pnNumOfOIDs ) *pnNumOfOIDs = nCurrent; cleanup: if ( FAILED(hr)) { if ( *pOIDs ) { FreeADsMem( *pOIDs ); *pOIDs = NULL; } } RRETURN(hr); } HRESULT DirectoryString(CSchemaLexer * pTokenizer, LPTSTR *ppszDirString ) { TCHAR szToken[MAX_TOKEN_LENGTH]; DWORD dwToken; HRESULT hr; if ( ppszDirString ) *ppszDirString = NULL; hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken == TOKEN_QUOTE ) { hr = pTokenizer->GetNextToken2(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken == TOKEN_IDENTIFIER ) { if ( ppszDirString ) { *ppszDirString = AllocADsStr( szToken ); if ( *ppszDirString == NULL ) { hr = E_OUTOFMEMORY; BAIL_IF_ERROR(hr); } } hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_IF_ERROR(hr); if ( dwToken == TOKEN_QUOTE ) RRETURN(S_OK); } } hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); cleanup: if ( FAILED(hr)) { if ( ppszDirString && *ppszDirString ) { FreeADsStr( *ppszDirString ); *ppszDirString = NULL; } } RRETURN(hr); } //+--------------------------------------------------------------------------- // Function: DirectoryStrings // // Synopsis: This function is used to process ldap schema elements // of the form qdstrings. This is defined in the RFC in detail : // // space = 1*" " // whsp = [ space ] // utf8 = // dstring = 1*utf8 // qdstring = whsp "'" dstring "'" whsp // qdstringlist = [ qdstring *( qdstring ) ] // qdstrings = qdstring / ( whsp "(" qdstringlist ")" whsp ) // // Arguments: pTokenizer - The schema lexer object to use. // pppszDirStrings - Return value for strings. // pdwCount - Return value of number of strings. // // // Returns: HRESULT - S_OK or any failure error code. // // Modifies: N/A. // // History: 7-12-2000 AjayR created. // //---------------------------------------------------------------------------- HRESULT DirectoryStrings( CSchemaLexer * pTokenizer, LPTSTR **pppszDirStrings, PDWORD pdwCount ) { HRESULT hr = S_OK; TCHAR szToken[MAX_TOKEN_LENGTH]; LPWSTR *ppszTmp = NULL; LPWSTR pszTmp = NULL; DWORD dwToken, dwNumStrings = 0; BOOL fNeedCloseBracket = FALSE; ADsAssert(pTokenizer); ADsAssert(pdwCount); DWORD dwDummy = sizeof(ADS_ATTR_INFO); *pdwCount = 0; // // Get the token type of the first token. // hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_ON_FAILURE(hr); if (dwToken == TOKEN_OPENBRACKET) { // // In this case we know that there is more than one string. // We can ignore the open bracket and continue to the next // token which should be a quote. // hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_ON_FAILURE(hr); fNeedCloseBracket = TRUE; } // // Need to push what should be the quote in either case, // back into the tokenizer (only then will the dirString // routine work correctly. // hr = pTokenizer->PushBackToken(); BAIL_ON_FAILURE(hr); if ( dwToken != TOKEN_QUOTE ) { BAIL_ON_FAILURE(hr = E_FAIL); } // // While there remain strings to be processed. // while (dwToken == TOKEN_QUOTE) { hr = DirectoryString( pTokenizer, &pszTmp ); BAIL_ON_FAILURE(hr); if (dwNumStrings == 0) { // // Since we NULL terminate the array it should have // at least 2 elements in this case. // ppszTmp = (LPWSTR *) AllocADsMem(sizeof(LPWSTR) * 2); if (!ppszTmp) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } } else { LPWSTR *ppszLocal; // // To avoid passing the variable itself to local alloc. // ppszLocal = (LPWSTR *) ReallocADsMem( ppszTmp, sizeof(LPWSTR) * (dwNumStrings + 1), sizeof(LPWSTR) * (dwNumStrings + 2) ); if (ppszLocal) { ppszTmp = ppszLocal; } else { // // Realloc failed, the old ptr is still valid. // BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } } ppszTmp[dwNumStrings] = pszTmp; ppszTmp[++dwNumStrings] = NULL; hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_ON_FAILURE(hr); pTokenizer->PushBackToken(); } // end of while. // // If this was qdescrs and not just qdstring. // if (fNeedCloseBracket) { hr = pTokenizer->GetNextToken(szToken, &dwToken); BAIL_ON_FAILURE(hr); if (dwToken != TOKEN_CLOSEBRACKET) { // // Not properly formed - should be just ignore ? // BAIL_ON_FAILURE(hr = E_FAIL); } } // // The count is the actual number not including the NULL string. // *pdwCount = dwNumStrings; *pppszDirStrings = ppszTmp; error: if (FAILED(hr)) { if (ppszTmp) { // // Free the strings if any and then the array itself. // for (DWORD dwCount = 0; dwCount < dwNumStrings; dwCount++) { if (ppszTmp[dwCount]) { FreeADsStr(ppszTmp[dwCount]); } } FreeADsMem(ppszTmp); ppszTmp = NULL; } // // Need to reset the count. // *pdwCount = 0; } RRETURN(hr); } //+--------------------------------------------------------------------------- // Function: FreeDirectoryStrings // // Synopsis: This function is used to free the entries returned from // the DirectoryStrings routine. // // Arguments: ppszDirStrings - List of strings to free. // dwCount - Number of strings to free. // fSkipFirstElement - If true, do not free the 1st element. // // // Returns: N/A. // // Modifies: ppszDirStrings contents is freed including the array. // // History: 8-01-2000 AjayR created. // //---------------------------------------------------------------------------- void FreeDirectoryStrings( LPTSTR *ppszDirStrings, DWORD dwCount, DWORD dwElementsToFree ) { DWORD dwCtr; if (!ppszDirStrings) { return; } switch (dwElementsToFree) { case FREE_ALL_BUT_FIRST : dwCtr = 1; break; case FREE_ALL : dwCtr = 0; break; case FREE_ARRAY_NOT_ELEMENTS : dwCtr = dwCount; break; } for (; dwCtr < dwCount; dwCtr++) { if (ppszDirStrings[dwCtr]) { FreeADsStr(ppszDirStrings[dwCtr]); ppszDirStrings[dwCtr] = NULL; } } FreeADsMem(ppszDirStrings); return; } //+--------------------------------------------------------------------------- // Function: AddNewNamesToPropertyArray --- Helper function. // // Synopsis: This function adds new entries to the property info array. // Specifically, this fn is called when there are multiple names // associated with the description of a single property. The new // entries will have the same information as the current element // but the appropriate new name. // // Arguments: ppPropArray - Property array containing current // elements. This array is updated to contain // the new elements on success and is // untouched otherwise. // dwCurPos - The current element being processed. // dwCount - The current count of elements. // ppszNewNames - Array of names to add (1st element is // already a part of the property array). // // Returns: S_OK or E_OUTOFMEMORY. // // Modifies: *ppPropArray is modified in all success cases and some failure // cases (realloc succeeds but not the subsequent string allocs). // // History: 10-03-2000 AjayR created. // //---------------------------------------------------------------------------- HRESULT AddNewNamesToPropertyArray( PROPERTYINFO **ppPropArray, DWORD dwCurPos, DWORD dwCount, LPWSTR *ppszNewNames, DWORD dwNewNameCount ) { HRESULT hr = S_OK; PPROPERTYINFO pNewPropArray = NULL; DWORD dwAdditions = 0; DWORD dwCurCount = dwCount; // // The first element is already in the array. // dwAdditions = --dwNewNameCount; if (!dwNewNameCount) { RRETURN(hr); } // // We need to realloc the new array and copy over the new elements. // pNewPropArray = (PROPERTYINFO *) ReallocADsMem( *ppPropArray, (dwCurCount) * sizeof(PROPERTYINFO), (dwCurCount + dwAdditions) * sizeof(PROPERTYINFO) ); if (!pNewPropArray) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } // // If the alloc succeeded we must return the new array. // *ppPropArray = pNewPropArray; for (DWORD dwCtr = 0; dwCtr < dwAdditions; dwCtr++ ) { PROPERTYINFO propOriginal = pNewPropArray[dwCurPos]; PROPERTYINFO *pPropNew = pNewPropArray + (dwCurPos + dwCtr + 1); // // Copy over the property. First all data that is not ptrs. // pPropNew->lMaxRange = propOriginal.lMaxRange; pPropNew->lMinRange = propOriginal.lMinRange; pPropNew->fSingleValued = propOriginal.fSingleValued; pPropNew->fObsolete = propOriginal.fObsolete; pPropNew->fCollective = propOriginal.fCollective; pPropNew->fDynamic = propOriginal.fDynamic; pPropNew->fNoUserModification = propOriginal.fNoUserModification; pPropNew->dwUsage = propOriginal.dwUsage; pPropNew->fProcessedSuperiorClass = propOriginal.fProcessedSuperiorClass; // // Now the strings. // pPropNew->pszOID = AllocADsStr(propOriginal.pszOID); if (propOriginal.pszOID && !pPropNew->pszOID) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } pPropNew->pszSyntax = AllocADsStr(propOriginal.pszSyntax); if (propOriginal.pszSyntax && !pPropNew->pszSyntax) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } pPropNew->pszDescription = AllocADsStr(propOriginal.pszDescription); if (propOriginal.pszDescription && !pPropNew->pszDescription) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } pPropNew->pszOIDSup = AllocADsStr(propOriginal.pszOIDSup); if (propOriginal.pszOIDSup && !pPropNew->pszOIDSup) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } pPropNew->pszOIDEquality = AllocADsStr(propOriginal.pszOIDEquality); if (propOriginal.pszOIDEquality && !pPropNew->pszOIDEquality) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } pPropNew->pszOIDOrdering = AllocADsStr(propOriginal.pszOIDOrdering); if (propOriginal.pszOIDOrdering && !pPropNew->pszOIDOrdering) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } pPropNew->pszOIDSubstr = AllocADsStr(propOriginal.pszOIDSubstr); if (propOriginal.pszOIDSubstr && !pPropNew->pszOIDSubstr) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } // // This is just a copy from the array. // pPropNew->pszPropertyName = ppszNewNames[dwCtr + 1]; } // // Success case. // RRETURN(hr); error: // // Something failed, try and cleanup some pieces // if (pNewPropArray && (dwCtr != (DWORD)-1) ) { // // Cleanup the new elements added. // for (DWORD i = 0; i <= dwCtr; i++) { PROPERTYINFO *pPropFree = pNewPropArray + (dwCurPos + i + 1); // // Free all the strings in this element except name. // if (pPropFree->pszOID) { FreeADsStr(pPropFree->pszOID); pPropFree->pszOID = NULL; } if (pPropFree->pszSyntax) { FreeADsStr(pPropFree->pszSyntax); pPropFree->pszSyntax = NULL; } if (pPropFree->pszDescription) { FreeADsStr(pPropFree->pszDescription); pPropFree->pszDescription = NULL; } if (pPropFree->pszOIDSup) { FreeADsStr(pPropFree->pszOIDSup); pPropFree->pszOIDSup = NULL; } if (pPropFree->pszOIDEquality) { FreeADsStr(pPropFree->pszOIDEquality); pPropFree->pszOIDEquality = NULL; } if (pPropFree->pszOIDOrdering) { FreeADsStr(pPropFree->pszOIDOrdering); pPropFree->pszOIDOrdering = NULL; } if (pPropFree->pszOIDSubstr) { FreeADsStr(pPropFree->pszOIDSubstr); pPropFree->pszOIDSubstr = NULL; } } // for } // if we have elements to free RRETURN(hr); } //+--------------------------------------------------------------------------- // Function: AddNewNamesToClassArray --- Helper function. // // Synopsis: This function adds new entries to the class info array. // Specifically, this fn is called when there are multiple names // associated with the description of a single class. The new // entries will have the same information as the current element // but the appropriate new name. // // Arguments: ppClassArray - Class array containing current // elements. This array is updated to contain // the new elements on success and is // untouched otherwise. // dwCurPos - The current element being processed. // dwCount - The current count of elements. // ppszNewNames - Array of names to add (1st element is // already a part of the property array). // dwNewNameCount - Number of elements in the new array. // // Returns: N/A. // // Modifies: *ppClassArray always on success and in some failure cases. // // History: 10-06-2000 AjayR created. // //---------------------------------------------------------------------------- HRESULT AddNewNamesToClassArray( CLASSINFO **ppClassArray, DWORD dwCurPos, DWORD dwCount, LPWSTR *ppszNewNames, DWORD dwNewNameCount ) { HRESULT hr = S_OK; PCLASSINFO pNewClassArray = NULL; DWORD dwAdditions = 0; DWORD dwCurCount = dwCount; int nCount; // // The first element is already in the array. // dwAdditions = --dwNewNameCount; if (!dwNewNameCount) { RRETURN(hr); } // // We need to realloc the new array and copy over the new elements. // pNewClassArray = (CLASSINFO *) ReallocADsMem( *ppClassArray, (dwCurCount) * sizeof(CLASSINFO), (dwCurCount + dwAdditions) * sizeof(CLASSINFO) ); if (!pNewClassArray) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } // // If the alloc succeeded we must return the new array. // *ppClassArray = pNewClassArray; for (DWORD dwCtr = 0; dwCtr < dwAdditions; dwCtr++ ) { CLASSINFO classOriginal = pNewClassArray[dwCurPos]; CLASSINFO *pClassNew = pNewClassArray + (dwCurPos + dwCtr + 1); // // Copy over the property. First all data that is not ptrs. // pClassNew->dwType = classOriginal.dwType; pClassNew->lHelpFileContext = classOriginal.lHelpFileContext; pClassNew->fObsolete = classOriginal.fObsolete; pClassNew->fProcessedSuperiorClasses = classOriginal.fProcessedSuperiorClasses; pClassNew->IsContainer = classOriginal.IsContainer; // // Now the strings and other pointers. // pClassNew->pszOID = AllocADsStr(classOriginal.pszOID); if (classOriginal.pszOID && !pClassNew->pszOID) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } // // The GUIDs are not copied over as they are not used or freed. // pClassNew->pszHelpFileName = AllocADsStr(classOriginal.pszHelpFileName); if (classOriginal.pszHelpFileName && !pClassNew->pszHelpFileName) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } pClassNew->pszDescription = AllocADsStr(classOriginal.pszDescription); if (classOriginal.pszDescription && !pClassNew->pszDescription) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } // // pOIDsSuperiorClasses and pOIDsAuxClasses are arrays. // if (classOriginal.pOIDsSuperiorClasses) { pClassNew->pOIDsSuperiorClasses = CopyStringArray(classOriginal.pOIDsSuperiorClasses); if (!pClassNew->pOIDsSuperiorClasses) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } } if (classOriginal.pOIDsAuxClasses) { pClassNew->pOIDsAuxClasses = CopyStringArray(classOriginal.pOIDsAuxClasses); if (!pClassNew->pOIDsAuxClasses) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } } // // Now the int arrays. Note that all of these will tag on the // the last element (-1), -1 is not included in the count. // if (classOriginal.pOIDsMustContain) { nCount = classOriginal.nNumOfMustContain + 1; pClassNew->pOIDsMustContain = (int *) AllocADsMem(sizeof(int) * nCount); if (!pClassNew->pOIDsMustContain) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } memcpy( pClassNew->pOIDsMustContain, classOriginal.pOIDsMustContain, sizeof(int) * nCount ); pClassNew->nNumOfMustContain = --nCount; } if (classOriginal.pOIDsMayContain) { nCount = classOriginal.nNumOfMayContain + 1; pClassNew->pOIDsMayContain = (int *) AllocADsMem(sizeof(int) * nCount); if (!pClassNew->pOIDsMayContain) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } memcpy( pClassNew->pOIDsMayContain, classOriginal.pOIDsMayContain, sizeof(int) * nCount ); pClassNew->nNumOfMayContain = --nCount; } if (classOriginal.pOIDsNotContain) { nCount = classOriginal.nNumOfNotContain + 1; pClassNew->pOIDsNotContain = (int *) AllocADsMem(sizeof(int) * nCount); if (!pClassNew->pOIDsNotContain) { BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); } memcpy( pClassNew->pOIDsNotContain, classOriginal.pOIDsNotContain, sizeof(int) * nCount ); pClassNew->nNumOfNotContain = --nCount; } // // This is just a copy from the array. // pClassNew->pszName = ppszNewNames[dwCtr + 1]; } // // Success case. // RRETURN(hr); error: // // Something failed, try and cleanup some pieces // if (pNewClassArray && (dwCtr != (DWORD)-1) ) { // // Cleanup the new elements added. // for (DWORD i = 0; i <= dwCtr; i++) { CLASSINFO *pClassFree = pNewClassArray + (dwCurPos + i + 1); // // Free all the strings in this element except name. // if (pClassFree->pszOID) { FreeADsStr(pClassFree->pszOID); pClassFree->pszOID = NULL; } if (pClassFree->pszHelpFileName) { FreeADsStr(pClassFree->pszHelpFileName); pClassFree->pszHelpFileName = NULL; } if (pClassFree->pszDescription) { FreeADsStr(pClassFree->pszDescription); pClassFree->pszDescription = NULL; } // // Now the string arrays. // if (pClassFree->pOIDsSuperiorClasses) { nCount = 0; LPTSTR pszTemp; while (pszTemp = (pClassFree->pOIDsSuperiorClasses)[nCount]) { FreeADsStr(pszTemp); nCount++; } FreeADsMem(pClassFree->pOIDsSuperiorClasses); pClassFree->pOIDsSuperiorClasses = NULL; } if (pClassFree->pOIDsAuxClasses) { nCount = 0; LPTSTR pszTemp; while (pszTemp = (pClassFree->pOIDsAuxClasses)[nCount]) { FreeADsStr(pszTemp); nCount++; } FreeADsMem(pClassFree->pOIDsAuxClasses); pClassFree->pOIDsAuxClasses = NULL; } if (pClassFree->pOIDsMustContain) { FreeADsMem(pClassFree->pOIDsMustContain); pClassFree->pOIDsMustContain = NULL; pClassFree->nNumOfMustContain = 0; } if (pClassFree->pOIDsMayContain) { FreeADsMem(pClassFree->pOIDsMayContain); pClassFree->pOIDsMayContain = NULL; pClassFree->nNumOfMayContain = 0; } if (pClassFree->pOIDsNotContain) { FreeADsMem(pClassFree->pOIDsNotContain); pClassFree->pOIDsNotContain = NULL; pClassFree->nNumOfNotContain = 0; } } // for } // if we have elements to free RRETURN(hr); } //+--------------------------------------------------------------------------- // Function: // // Synopsis: // // Arguments: // // Returns: // // Modifies: // // History: 11-3-95 krishnag Created. // //---------------------------------------------------------------------------- CSchemaLexer::CSchemaLexer(LPTSTR szBuffer): _ptr(NULL), _Buffer(NULL), _dwLastTokenLength(0), _dwLastToken(0), _dwEndofString(0), _fInQuotes(FALSE) { if (!szBuffer || !*szBuffer) { return; } _Buffer = AllocADsStr(szBuffer); _ptr = _Buffer; } //+--------------------------------------------------------------------------- // Function: // // Synopsis: // // Arguments: // // Returns: // // Modifies: // // History: 08-12-96 t-danal Created. // //---------------------------------------------------------------------------- CSchemaLexer::~CSchemaLexer() { FreeADsStr(_Buffer); } //+--------------------------------------------------------------------------- // Function: // // Synopsis: // // Arguments: // // Returns: // // Modifies: // // History: 11-3-95 krishnag Created. // //---------------------------------------------------------------------------- HRESULT CSchemaLexer::GetNextToken(LPTSTR szToken, LPDWORD pdwToken) { TCHAR c; DWORD state = 0; LPTSTR pch = szToken; memset(szToken, 0, sizeof(TCHAR) * MAX_TOKEN_LENGTH); _dwLastTokenLength = 0; while (1) { c = NextChar(); switch (state) { case 0: *pch++ = c; _dwLastTokenLength++; switch (c) { case TEXT('(') : *pdwToken = TOKEN_OPENBRACKET; _dwLastToken = *pdwToken; RRETURN(S_OK); break; case TEXT(')') : *pdwToken = TOKEN_CLOSEBRACKET; _dwLastToken = *pdwToken; RRETURN(S_OK); break; case TEXT('\'') : *pdwToken = TOKEN_QUOTE; _dwLastToken = *pdwToken; _fInQuotes = !_fInQuotes; RRETURN(S_OK); break; case TEXT('$') : *pdwToken = TOKEN_DOLLAR; _dwLastToken = *pdwToken; RRETURN(S_OK); break; case TEXT(' ') : pch--; _dwLastTokenLength--; break; case TEXT('\0') : *pdwToken = TOKEN_END; _dwLastToken = *pdwToken; RRETURN(S_OK); break; case TEXT('{') : *pdwToken = TOKEN_OPEN_CURLY; _dwLastToken = *pdwToken; RRETURN(S_OK); break; case TEXT('}') : *pdwToken = TOKEN_CLOSE_CURLY; _dwLastToken = *pdwToken; RRETURN(S_OK); break; default: state = 1; break; } // end of switch c break; case 1: switch (c) { case TEXT('(') : case TEXT(')') : case TEXT('\'') : case TEXT('$') : case TEXT(' ') : case TEXT('\0') : case TEXT('{') : case TEXT('}') : if ( _fInQuotes && c != TEXT('\'')) { if ( c == TEXT('\0')) RRETURN(E_FAIL); *pch++ = c; _dwLastTokenLength++; state = 1; break; } else // Not in quotes or in quotes and reach the matching quote { PushbackChar(); *pdwToken = TOKEN_IDENTIFIER; _dwLastToken = *pdwToken; RRETURN (S_OK); } break; default : *pch++ = c; _dwLastTokenLength++; state = 1; break; } // switch c break; default: RRETURN(E_FAIL); } // switch state } } HRESULT CSchemaLexer::GetNextToken2(LPTSTR szToken, LPDWORD pdwToken) { TCHAR c; DWORD state = 0; LPTSTR pch = szToken; memset(szToken, 0, sizeof(TCHAR) * MAX_TOKEN_LENGTH); _dwLastTokenLength = 0; while (1) { c = NextChar(); switch (state) { case 0: *pch++ = c; _dwLastTokenLength++; switch (c) { case TEXT('(') : *pdwToken = TOKEN_OPENBRACKET; _dwLastToken = *pdwToken; RRETURN(S_OK); break; case TEXT(')') : *pdwToken = TOKEN_CLOSEBRACKET; _dwLastToken = *pdwToken; RRETURN(S_OK); break; case TEXT('\'') : *pdwToken = TOKEN_QUOTE; _dwLastToken = *pdwToken; _fInQuotes = !_fInQuotes; RRETURN(S_OK); break; case TEXT('$') : *pdwToken = TOKEN_DOLLAR; _dwLastToken = *pdwToken; RRETURN(S_OK); break; case TEXT('\0') : *pdwToken = TOKEN_END; _dwLastToken = *pdwToken; RRETURN(S_OK); break; case TEXT('{') : *pdwToken = TOKEN_OPEN_CURLY; _dwLastToken = *pdwToken; RRETURN(S_OK); break; case TEXT('}') : *pdwToken = TOKEN_CLOSE_CURLY; _dwLastToken = *pdwToken; RRETURN(S_OK); break; default: state = 1; break; } // end of switch c break; case 1: switch (c) { case TEXT('(') : case TEXT(')') : case TEXT('\'') : case TEXT('$') : case TEXT(' ') : case TEXT('\0') : case TEXT('{') : case TEXT('}') : if ( _fInQuotes && c != TEXT('\'')) { if ( c == TEXT('\0')) RRETURN(E_FAIL); *pch++ = c; _dwLastTokenLength++; state = 1; break; } else // Not in quotes or in quotes and reach the matching quote { PushbackChar(); *pdwToken = TOKEN_IDENTIFIER; _dwLastToken = *pdwToken; RRETURN (S_OK); } break; default : *pch++ = c; _dwLastTokenLength++; state = 1; break; } // switch c break; default: RRETURN(E_FAIL); } // switch state } } //+--------------------------------------------------------------------------- // Function: // // Synopsis: // // Arguments: // // Returns: // // Modifies: // // History: 11-3-95 krishnag Created. // //---------------------------------------------------------------------------- TCHAR CSchemaLexer::NextChar() { if (_ptr == NULL || *_ptr == TEXT('\0')) { _dwEndofString = TRUE; return(TEXT('\0')); } return(*_ptr++); } //+--------------------------------------------------------------------------- // Function: // // Synopsis: ONLY ONE TOKEN CAN BE PUSHED BACK. // // Arguments: // // Returns: // // Modifies: // // History: 11-3-95 krishnag Created. // //---------------------------------------------------------------------------- HRESULT CSchemaLexer::PushBackToken() { DWORD i = 0; if (_dwLastToken == TOKEN_END) { RRETURN(S_OK); } for (i=0; i < _dwLastTokenLength; i++) { if (*(--_ptr) == TEXT('\'') ) { _fInQuotes = !_fInQuotes; } } RRETURN(S_OK); } //+--------------------------------------------------------------------------- // Function: // // Synopsis: // // Arguments: // // Returns: // // Modifies: // // History: 11-3-95 krishnag Created. // //---------------------------------------------------------------------------- void CSchemaLexer::PushbackChar() { if (_dwEndofString) { return; } _ptr--; } //+--------------------------------------------------------------------------- // Function: // // Synopsis: // // Arguments: // // Returns: // // Modifies: // // History: 11-3-95 krishnag Created. // //---------------------------------------------------------------------------- BOOL CSchemaLexer::IsKeyword(LPTSTR szToken, LPDWORD pdwToken) { DWORD i = 0; for (i = 0; i < g_dwSchemaKeywordListSize; i++) { if (!_tcsicmp(szToken, g_aSchemaKeywordList[i].Keyword)) { *pdwToken = g_aSchemaKeywordList[i].dwTokenId; return(TRUE); } else if (!_wcsnicmp(szToken, L"X-", 2)) { // // Terms begining with X- are special tokens for schema. // *pdwToken = TOKEN_X; return(TRUE); } } *pdwToken = 0; return(FALSE); } int _cdecl searchentrycmp( const void *s1, const void *s2 ) { SEARCHENTRY *srch1 = (SEARCHENTRY *) s1; SEARCHENTRY *srch2 = (SEARCHENTRY *) s2; return ( _tcsicmp( srch1->pszName, srch2->pszName )); } int _cdecl intcmp( const void *s1, const void *s2 ) { int n1 = *((int *) s1); int n2 = *((int *) s2); int retval; if ( n1 == n2 ) retval = 0; else if ( n1 < n2 ) retval = -1; else retval = 1; return retval; } long CompareUTCTime( LPTSTR pszTime1, LPTSTR pszTime2 ) { SYSTEMTIME sysTime1; SYSTEMTIME sysTime2; FILETIME fTime1; FILETIME fTime2; memset( &sysTime1, 0, sizeof(sysTime1)); memset( &sysTime2, 0, sizeof(sysTime2)); // // We are ignoring the last part which might be a float. // The time window is sufficiently small for us not to // worry about this value. // _stscanf( pszTime1, TEXT("%4d%2d%2d%2d%2d%2d"), &sysTime1.wYear, &sysTime1.wMonth, &sysTime1.wDay, &sysTime1.wHour, &sysTime1.wMinute, &sysTime1.wSecond ); _stscanf( pszTime2, TEXT("%4d%2d%2d%2d%2d%2d"), &sysTime2.wYear, &sysTime2.wMonth, &sysTime2.wDay, &sysTime2.wHour, &sysTime2.wMinute, &sysTime2.wSecond ); if ( SystemTimeToFileTime( &sysTime1, &fTime1 ) && SystemTimeToFileTime( &sysTime2, &fTime2 ) ) { return CompareFileTime( &fTime1, &fTime2 ); } // If SystemTimeToFileTime failed, then assume that pszTime1 is in cache, // pszTime2 is on the server and if we cannot get the correct time, we // should always read from the server again. Hence, return -1; return -1; } int FindEntryInSearchTable( LPTSTR pszName, SEARCHENTRY *aSearchTable, DWORD nSearchTableSize) { SEARCHENTRY searchEntry; SEARCHENTRY *matchedEntry = NULL; searchEntry.pszName = pszName; matchedEntry = (SEARCHENTRY *) bsearch( (SEARCHENTRY *) &searchEntry, aSearchTable, nSearchTableSize, sizeof(SEARCHENTRY), searchentrycmp ); if ( matchedEntry ) { return matchedEntry->nIndex; } return -1; } int FindSearchTableIndex( LPTSTR pszName, SEARCHENTRY *aSearchTable, DWORD nSearchTableSize ) { SEARCHENTRY searchEntry; SEARCHENTRY *matchedEntry = NULL; searchEntry.pszName = pszName; matchedEntry = (SEARCHENTRY *) bsearch( (SEARCHENTRY *) &searchEntry, aSearchTable, nSearchTableSize, sizeof(SEARCHENTRY), searchentrycmp ); if ( matchedEntry ) { return (int)( matchedEntry - aSearchTable ); // return index of search table } return -1; } HRESULT ReadSubSchemaSubEntry( LPWSTR pszLDAPServer, LPWSTR * ppszSubSchemaEntry, OUT BOOL *pfBoundOk, // have we at least once bound to domain // successfully, OPTIONAL (can be NULL) CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; ROOTDSENODE rootDSE = {0}; ADsAssert(ppszSubSchemaEntry); *ppszSubSchemaEntry = NULL; // // Call the generic function // hr = ReadRootDSENode( pszLDAPServer, &rootDSE, pfBoundOk, Credentials, dwPort ); BAIL_ON_FAILURE(hr); if ( !rootDSE.pszSubSchemaEntry ) { // // SubschemaEntry must be found // BAIL_ON_FAILURE(hr = E_FAIL); } else { *ppszSubSchemaEntry = rootDSE.pszSubSchemaEntry; } error: RRETURN(hr); } HRESULT ReadPagingSupportedAttr( LPWSTR pszLDAPServer, BOOL * pfPagingSupported, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; ROOTDSENODE rootDSE = {0}; ADsAssert(pfPagingSupported); *pfPagingSupported = FALSE; // // Call the generic function // hr = ReadRootDSENode( pszLDAPServer, &rootDSE, NULL, Credentials, dwPort ); BAIL_ON_FAILURE(hr); if ( rootDSE.pszSubSchemaEntry) { FreeADsStr (rootDSE.pszSubSchemaEntry); } *pfPagingSupported = rootDSE.fPagingSupported; error: RRETURN(hr); } HRESULT ReadSortingSupportedAttr( LPWSTR pszLDAPServer, BOOL * pfSortingSupported, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; ROOTDSENODE rootDSE = {0}; ADsAssert(pfSortingSupported); *pfSortingSupported = FALSE; // // Call the generic function // hr = ReadRootDSENode( pszLDAPServer, &rootDSE, NULL, Credentials, dwPort ); BAIL_ON_FAILURE(hr); if ( rootDSE.pszSubSchemaEntry) { FreeADsStr (rootDSE.pszSubSchemaEntry); } *pfSortingSupported = rootDSE.fSortingSupported; error: RRETURN(hr); } HRESULT ReadAttribScopedSupportedAttr( LPWSTR pszLDAPServer, BOOL * pfAttribScopedSupported, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; ROOTDSENODE rootDSE = {0}; ADsAssert(pfAttribScopedSupported); *pfAttribScopedSupported = FALSE; // // Call the generic function // hr = ReadRootDSENode( pszLDAPServer, &rootDSE, NULL, Credentials, dwPort ); BAIL_ON_FAILURE(hr); if ( rootDSE.pszSubSchemaEntry) { FreeADsStr (rootDSE.pszSubSchemaEntry); } *pfAttribScopedSupported = rootDSE.fAttribScopedSupported; error: RRETURN(hr); } HRESULT ReadVLVSupportedAttr( LPWSTR pszLDAPServer, BOOL * pfVLVSupported, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; ROOTDSENODE rootDSE = {0}; ADsAssert(pfVLVSupported); *pfVLVSupported = FALSE; // // Call the generic function // hr = ReadRootDSENode( pszLDAPServer, &rootDSE, NULL, Credentials, dwPort ); BAIL_ON_FAILURE(hr); if ( rootDSE.pszSubSchemaEntry) { FreeADsStr (rootDSE.pszSubSchemaEntry); } *pfVLVSupported = rootDSE.fVLVSupported; error: RRETURN(hr); } // // Returns the info about SecDesc Control if appropriate // HRESULT ReadSecurityDescriptorControlType( LPWSTR pszLDAPServer, DWORD * pdwSecDescType, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; ROOTDSENODE rootDSE = {0}; ADsAssert(pdwSecDescType); *pdwSecDescType = ADSI_LDAPC_SECDESC_NONE; // // Call the generic function // hr = ReadRootDSENode( pszLDAPServer, &rootDSE, NULL, Credentials, dwPort ); BAIL_ON_FAILURE(hr); if ( rootDSE.pszSubSchemaEntry) { FreeADsStr (rootDSE.pszSubSchemaEntry); } *pdwSecDescType = rootDSE.dwSecDescType; error: // // Since the error case is uninteresting, if there was an // error, we will continue with no sec desc // if (hr == HRESULT_FROM_WIN32(ERROR_DS_NO_ATTRIBUTE_OR_VALUE)) RRETURN (S_OK); else RRETURN(hr); } // // This is to see if we support the domain scope control. // If we do we can set it to reduce server load. // HRESULT ReadDomScopeSupportedAttr( LPWSTR pszLDAPServer, BOOL * pfDomScopeSupported, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; ROOTDSENODE rootDSE = {0}; ADsAssert(pfDomScopeSupported); *pfDomScopeSupported = FALSE; // // Call the generic function // hr = ReadRootDSENode( pszLDAPServer, &rootDSE, NULL, Credentials, dwPort ); BAIL_ON_FAILURE(hr); if ( rootDSE.pszSubSchemaEntry) { FreeADsStr (rootDSE.pszSubSchemaEntry); } *pfDomScopeSupported = rootDSE.fDomScopeSupported; error: RRETURN(hr); } // // This is to see if we support the domain scope control. // If we do we can set it to reduce server load. // HRESULT ReadServerSupportsIsADControl( LPWSTR pszLDAPServer, BOOL * pfServerIsAD, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; ROOTDSENODE rootDSE = {0}; ADsAssert(pfServerIsAD); *pfServerIsAD = FALSE; // // Call the generic function // hr = ReadRootDSENode( pszLDAPServer, &rootDSE, NULL, Credentials, dwPort ); BAIL_ON_FAILURE(hr); if ( rootDSE.pszSubSchemaEntry) { FreeADsStr (rootDSE.pszSubSchemaEntry); } *pfServerIsAD = rootDSE.fTalkingToAD; error: RRETURN(hr); } // // This is to see if we are talking to enhacned AD servers so we // can process the aux classes correctly. // HRESULT ReadServerSupportsIsEnhancedAD( LPWSTR pszLDAPServer, BOOL * pfServerIsEnhancedAD, BOOL * pfServerIsADControl, CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; ROOTDSENODE rootDSE = {0}; ADsAssert(pfServerIsEnhancedAD); ADsAssert(pfServerIsADControl); *pfServerIsEnhancedAD = FALSE; *pfServerIsADControl = FALSE; // // Call the generic function // hr = ReadRootDSENode( pszLDAPServer, &rootDSE, NULL, Credentials, dwPort ); BAIL_ON_FAILURE(hr); if ( rootDSE.pszSubSchemaEntry) { FreeADsStr (rootDSE.pszSubSchemaEntry); } *pfServerIsEnhancedAD = rootDSE.fTalkingToEnhancedAD; *pfServerIsADControl = rootDSE.fTalkingToAD; error: RRETURN(hr); } BOOL EquivalentServers( LPWSTR pszTargetServer, LPWSTR pszSourceServer ) { if (!pszTargetServer && !pszSourceServer) { return(TRUE); } if (pszTargetServer && pszSourceServer) { #ifdef WIN95 if (!_wcsicmp(pszTargetServer, pszSourceServer)) { #else if (CompareStringW( LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, pszTargetServer, -1, pszSourceServer, -1 ) == CSTR_EQUAL ) { #endif return(TRUE); } } return(FALSE); } BOOL EquivalentUsers( LPWSTR pszUser1, LPWSTR pszUser2 ) { if (!pszUser1 && !pszUser2) { return(TRUE); } if (pszUser1 && pszUser2) { #ifdef WIN95 if (!_wcsicmp(pszUser1, pszUser2)) { #else if (CompareStringW( LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, pszUser1, -1, pszUser2, -1 ) == CSTR_EQUAL ) { #endif return(TRUE); } } return(FALSE); } HRESULT ReadRootDSENode( LPWSTR pszLDAPServer, PROOTDSENODE pRootDSE, OUT BOOL * pfBoundOk, // have we at least once bound to domain // successfully, OPTIONAL (can be NULL) CCredentials& Credentials, DWORD dwPort ) { HRESULT hr = S_OK; PSCHEMALIST pTemp = NULL; PSCHEMALIST pNewNode = NULL; ADS_LDP * ld = NULL; int nCount1 = 0, nCount2 = 0, nCount3 = 0; LPWSTR *aValues1 = NULL, *aValues2 = NULL, *aValues3 = NULL; LDAPMessage * res = NULL; LDAPMessage *e = NULL; LPWSTR aStrings[4]; // Attributes to fetch. BOOL fBoundOk = FALSE; // have we at least once bound to // domain successfully? BOOL fNoData = FALSE; ADsAssert(pRootDSE); memset (pRootDSE, 0x00, sizeof(ROOTDSENODE)); ENTER_SUBSCHEMA_CRITSECT(); pTemp = gpSubSchemaList; while (pTemp) { if (EquivalentServers(pszLDAPServer, pTemp->pszLDAPServer)){ if (pTemp->fNoDataGot) { // // This is necessary for V2 server // If BoundOk is not set we may end up not loading // the default schema // fBoundOk = TRUE; LEAVE_SUBSCHEMA_CRITSECT(); BAIL_ON_FAILURE( hr = HRESULT_FROM_WIN32(ERROR_DS_NO_ATTRIBUTE_OR_VALUE) ); } pRootDSE->fPagingSupported = pTemp->fPagingSupported; pRootDSE->fSortingSupported = pTemp->fSortingSupported; pRootDSE->fVLVSupported = pTemp->fVLVSupported; pRootDSE->fAttribScopedSupported = pTemp->fAttribScopedSupported; pRootDSE->dwSecDescType = pTemp->dwSecDescType; pRootDSE->fDomScopeSupported = pTemp->fDomScopeSupported; pRootDSE->fTalkingToAD = pTemp->fTalkingToAD; pRootDSE->fTalkingToEnhancedAD = pTemp->fTalkingToEnhancedAD; pRootDSE->fNoDataGot = pTemp->fNoDataGot; pRootDSE->pszSubSchemaEntry = AllocADsStr(pTemp->pszSubSchemaEntry); if (!pRootDSE->pszSubSchemaEntry) { hr = E_OUTOFMEMORY; } LEAVE_SUBSCHEMA_CRITSECT(); // // we have at least once bound successfully to the domain // fBoundOk = TRUE; goto error; // can't return direct, need clean up } pTemp = pTemp->pNext; } LEAVE_SUBSCHEMA_CRITSECT(); hr = LdapOpenObject( pszLDAPServer, NULL, &ld, Credentials, dwPort ); BAIL_ON_FAILURE(hr); // // we have once bound to the node successfully - just now // fBoundOk=TRUE; // // Ask only for the attributes we are intersted in. // aStrings[0] = LDAP_OPATT_SUBSCHEMA_SUBENTRY_W; aStrings[1] = LDAP_OPATT_SUPPORTED_CONTROL_W; aStrings[2] = LDAP_OPATT_SUPPORTED_CAPABILITIES_W; aStrings[3] = NULL; hr = LdapSearchS( ld, NULL, LDAP_SCOPE_BASE, L"(objectClass=*)", aStrings, 0, &res ); // Only one entry should be returned if ( FAILED(hr) || FAILED(hr = LdapFirstEntry( ld, res, &e )) ) { goto error; } hr = LdapGetValues( ld, e, LDAP_OPATT_SUBSCHEMA_SUBENTRY_W, &aValues1, &nCount1 ); if (SUCCEEDED(hr) && nCount1==0) { // // No data flag indicates that we read nothing but the // search was a success. // fNoData = TRUE; } BAIL_ON_FAILURE(hr); hr = LdapGetValues( ld, e, LDAP_OPATT_SUPPORTED_CONTROL_W, &aValues2, &nCount2 ); // // okay to have no values for supportedControl // if (FAILED(hr)) { // // Reset the error because we were really succesful // in reading critical information. // hr = S_OK; } hr = LdapGetValues( ld, e, LDAP_OPATT_SUPPORTED_CAPABILITIES_W, &aValues3, &nCount3 ); // // okay to have no values for supportedControl // if (FAILED(hr)) { // // Reset the error because we were really succesful // in reading critical information. // hr = S_OK; } ENTER_SUBSCHEMA_CRITSECT(); pTemp = gpSubSchemaList; while (pTemp) { if (EquivalentServers(pszLDAPServer, pTemp->pszLDAPServer)) { // // Found a match -looks like someone has come in before us // if (pTemp->fNoDataGot) { // // This is necessary for V2 server // LEAVE_SUBSCHEMA_CRITSECT(); BAIL_ON_FAILURE( hr = HRESULT_FROM_WIN32(ERROR_DS_NO_ATTRIBUTE_OR_VALUE) ); } pRootDSE->fPagingSupported = pTemp->fPagingSupported; pRootDSE->fSortingSupported = pTemp->fSortingSupported; pRootDSE->fVLVSupported = pTemp->fVLVSupported; pRootDSE->fAttribScopedSupported = pTemp->fAttribScopedSupported; pRootDSE->dwSecDescType = pTemp->dwSecDescType; pRootDSE->fDomScopeSupported = pTemp->fDomScopeSupported; pRootDSE->fTalkingToAD = pTemp->fTalkingToAD; pRootDSE->fTalkingToEnhancedAD = pTemp->fTalkingToEnhancedAD; pRootDSE->fNoDataGot = pTemp->fNoDataGot; pRootDSE->pszSubSchemaEntry = AllocADsStr(pTemp->pszSubSchemaEntry); if (!pRootDSE->pszSubSchemaEntry) { hr = E_OUTOFMEMORY; } LEAVE_SUBSCHEMA_CRITSECT(); goto error; // clean up first before return } pTemp = pTemp->pNext; } pNewNode = (PSCHEMALIST)AllocADsMem(sizeof(SCHEMALIST)); if (!pNewNode) { hr = E_OUTOFMEMORY; LEAVE_SUBSCHEMA_CRITSECT(); goto error; // clean up first before return } pNewNode->pNext = gpSubSchemaList; pNewNode->pszLDAPServer = AllocADsStr(pszLDAPServer); if (aValues1 && aValues1[0]) { pNewNode->pszSubSchemaEntry = AllocADsStr(aValues1[0]); pNewNode->fNoDataGot = FALSE; } else { pNewNode->pszSubSchemaEntry = NULL; pNewNode->fNoDataGot = TRUE; } // // Default to this value // pNewNode->dwSecDescType = ADSI_LDAPC_SECDESC_NONE; if (aValues2) { for (int j=0; jfPagingSupported = TRUE; } else if (_wcsicmp(aValues2[j], LDAP_SERVER_SORT_OID_W) == 0) { pNewNode->fSortingSupported = TRUE; } else if (_wcsicmp(aValues2[j], LDAP_SERVER_SD_FLAGS_OID_W) == 0) { pNewNode->dwSecDescType = ADSI_LDAPC_SECDESC_NT; } else if (_wcsicmp(aValues2[j], ADSI_LDAP_OID_SECDESC_OLD) == 0) { pNewNode->dwSecDescType = ADSI_LDAPC_SECDESC_OTHER; } else if (_wcsicmp(aValues2[j], LDAP_SERVER_DOMAIN_SCOPE_OID_W) == 0) { pNewNode->fDomScopeSupported = TRUE; } else if (_wcsicmp(aValues2[j], LDAP_CONTROL_VLVREQUEST_W) == 0) { pNewNode->fVLVSupported = TRUE; } else if (_wcsicmp(aValues2[j], LDAP_SERVER_ASQ_OID_W) == 0) { pNewNode->fAttribScopedSupported = TRUE; } } } else { pNewNode->fPagingSupported = FALSE; pNewNode->fSortingSupported = FALSE; pNewNode->fDomScopeSupported = FALSE; pNewNode->fVLVSupported = FALSE; pNewNode->fAttribScopedSupported = FALSE; } if (aValues3) { for (int j=0; jfTalkingToAD = TRUE; } else if (_wcsicmp(aValues3[j], LDAP_CAP_ACTIVE_DIRECTORY_V51_OID_W) == 0) { // // Replace with correct OID from ntldap.h. // pNewNode->fTalkingToEnhancedAD = TRUE; } } } else { // // Should already be false but just in case. // pNewNode->fTalkingToAD = FALSE; pNewNode->fTalkingToEnhancedAD = FALSE; } gpSubSchemaList = pNewNode; if (fNoData == FALSE) { pRootDSE->fPagingSupported = pNewNode->fPagingSupported; pRootDSE->fSortingSupported = pNewNode->fSortingSupported; pRootDSE->fVLVSupported = pNewNode->fVLVSupported; pRootDSE->fAttribScopedSupported = pNewNode->fAttribScopedSupported; pRootDSE->fNoDataGot = pNewNode->fNoDataGot; pRootDSE->dwSecDescType = pNewNode->dwSecDescType; pRootDSE->fDomScopeSupported = pNewNode->fDomScopeSupported; pRootDSE->fTalkingToAD = pNewNode->fTalkingToAD; pRootDSE->fTalkingToEnhancedAD = pNewNode->fTalkingToEnhancedAD; pRootDSE->pszSubSchemaEntry = AllocADsStr(pNewNode->pszSubSchemaEntry); if (!pRootDSE->pszSubSchemaEntry) { hr = E_OUTOFMEMORY; } } LEAVE_SUBSCHEMA_CRITSECT(); error: if (aValues1) { LdapValueFree(aValues1); } if (aValues2) { LdapValueFree(aValues2); } if (aValues3) { LdapValueFree(aValues3); } if (res) { LdapMsgFree(res); } if (ld) { LdapCloseObject(ld); } // // return to caller if we have at least once bound succsufully // to the node // if (pfBoundOk) *pfBoundOk = fBoundOk; // // Need to special case fNoData to ensure that the other code // that relies on this eCode from this routine continues to // work properly // if (fNoData) { RRETURN(HRESULT_FROM_WIN32(ERROR_DS_NO_ATTRIBUTE_OR_VALUE)); } else RRETURN(hr); }