/*++ Microsoft Windows NT RPC Name Service Copyright (c) 1995 Microsoft Corporation Module Name: brodcast.cxx Abstract: This file contains the code for all mailslot activity relating to binding handle queries-- both outgoing and incoming ones. Things to improve in future revisions: 1. CBindingVector::msriList 2. formQueryPacket Author: Satish Thatte (SatishT) 08/15/95 Created all the code below except where otherwise indicated. --*/ #include #include #include /* NOTE: Compatibility Issue: see below for meaning of END_FLAG_SIZE */ const int END_FLAG_SIZE = 4; /**************** Utilities for Broadcast **********************/ /* a little macro exclusively for use in UnmarshallBroadcastReplyItem */ #define unBufferIt(source) \ lBufferSize -= advance; \ if (lBufferSize < 0) { \ StatusCode = NSI_S_UNMARSHALL_UNSUCCESSFUL; \ return; \ } \ if (source) memcpy((char*) source, pcBuffer, advance); \ pcBuffer += advance; void UnmarshallBroadcastReplyItem( TSSLEntryList *psslCache, STRING_T szEntryDomain, char * &pcBuffer, long& lBufferSize, ULONG& StatusCode ) /*++ Routine Description: Unmarshall an item from the broadcast reply buffer. If a well-formed item is found, the locator's cache and the temporary given cache (first argument) is updated and the buffer size is decreased accordingly. A reply packet contains the name of the replying locator's domain, which is used as the domain of the entry unless otherwise specified in the name itself. If the unmarshalling is unsuccessful, this is indicated in the status code returned. Arguments: psslCache - temporary cache for remote handle szEntryDomain - domain of replying locator pcBuffer - reply buffer lBufferSize - reduced buffer size returned here StatusCode - status of unmarshalling attempt returned here Returned Status: NSI_S_OK, NSI_OUT_OF_MEMORY, NSI_S_UNMARSHALL_UNSUCCESSFUL, NSI_S_UNSUPPORTED_BUFFER_TYPE Remarks: The following function has "return"s inside a __try block. Since this whole code is slated for removal after a couple of updates, I have left them in. They could be easily replaced with "goto"s if necessary. --*/ { StatusCode = NSI_S_OK; long advance; RPC_SYNTAX_IDENTIFIER Interface; RPC_SYNTAX_IDENTIFIER XferSyntax; STRING_T binding; STRING_T entryName = NULL; advance = sizeof(fixed_part_of_reply); fixed_part_of_reply fpr; unBufferIt(&fpr); if (fpr.type != MailslotServerEntryType) { StatusCode = NSI_S_UNSUPPORTED_BUFFER_TYPE; return; } Interface = fpr.Interface; XferSyntax = fpr.XferSyntax; advance = fpr.EntryNameLength * sizeof(WCHAR); entryName = (WCHAR*) pcBuffer; if (fpr.EntryNameLength != (wcslen(entryName) + 1)) { StatusCode = NSI_S_UNMARSHALL_UNSUCCESSFUL; return; } unBufferIt(NULL); advance = sizeof(UNSIGNED32); UNSIGNED32 objVectorSize; unBufferIt(&objVectorSize); GUID * guidVector = new GUID[objVectorSize]; NSI_UUID_VECTOR_T * pUuidVector = (NSI_UUID_VECTOR_T *) new char [ sizeof(UNSIGNED32) + sizeof(NSI_UUID_P_T) * objVectorSize ]; pUuidVector->count = objVectorSize; advance = sizeof(void*); // this is a useless pointer part lBufferSize -= advance; // therefore we don't unmarshall it pcBuffer += advance; // we just account for its size CStringW *pswEntry = NULL, *pswDomain = NULL; CEntryName * penTempName = NULL; __try{ advance = sizeof(GUID); for (ULONG i = 0; i < objVectorSize; i++) { pUuidVector->uuid[i] = &(guidVector[i]); unBufferIt(&(guidVector[i])); } advance = fpr.BindingLength * sizeof(WCHAR); binding = (WCHAR*) pcBuffer; if (fpr.BindingLength != (wcslen(binding) + 1)) { StatusCode = NSI_S_UNMARSHALL_UNSUCCESSFUL; return; } unBufferIt(NULL); /* first update the central cache and, if there is anything new, also update the temporary cache in psslCache */ parseEntryName(entryName,pswDomain,pswEntry); if (!pswDomain && szEntryDomain) // if name has no domain then use the // domain of origin in forming name penTempName = new CEntryName(szEntryDomain,*pswEntry); else penTempName = new CEntryName(entryName); myRpcLocator->UpdateCache( *penTempName, RPC_C_NS_SYNTAX_DCE, Interface, XferSyntax, binding, pUuidVector, psslCache ); } __finally { delete [] pUuidVector; delete [] guidVector; delete pswDomain; delete pswEntry; delete penTempName; } } BOOL CBroadcastQueryPacket::subsumes(QueryPacket& NewQuery, ULONG tolerance) { // too stale? if (!isCurrent(tolerance)) return FALSE; CStringW OtherEntry(NewQuery.EntryName); // incompatible entry name involved? if (!(swEntryName == OtherEntry) && (swEntryName.length() > 0)) return FALSE; CGUID OtherObject(NewQuery.Object); // incompatible object involved? if (!(OtherObject == Object) && !Object.IsNil()) return FALSE; CGUIDVersion OtherInterface(NewQuery.Interface); CGUID myGUID(Interface.myIdAndVersion().SyntaxGUID); // incompatible interface involved? if ( !OtherInterface.isMatching(Interface,RPC_C_VERS_COMPATIBLE) && !myGUID.IsNil() ) return FALSE; // we ran the gauntlet successfully -- stop the broadcast! return TRUE; } void formQueryPacket( CEntryName * penEntryName, CGUIDVersion * pGVInterface, CGUID * pIDobject, QueryPacket & NetRequest ) /*++ Routine Description: Forms a query packet given the items in it. Arguments: penEntryName - (wrapped) entry name pGVInterface - (wrapped) interface UUID and version pIDobject - (wrapped) object UUID NetRequest - The request packet is returned here Remarks: BUGBUG: We used to perform some shenanigans with interface and object settings in the QueryPacket to allow us to correctly report NSI_S_ENTRY_NOT_FOUND when appropriate. Specifically, using wildcards for interface and object if the entry name was non null so as to gather all information about the specific entry from the net. In order to interoperate with the old locator, these shenanigans have been suspended, and we will currently return NSI_S_ENTRY_NOT_FOUND spuriously when no information is received from the net in response to a specific rather than a wildcard query, just like the old locator (so that the old tests pass when they expect NSI_S_ENTRY_NOT_FOUND !!). The problem with the old locator is that it carefully avoids sending duplicate binding handles, so if a null interface is given in the broadcast query, it naturally assumes that we don't care about the interface, and may omit info about some interfaces unless they have distinct binding handles associated with them. --*/ { memset(&(NetRequest.Object),0,sizeof(NetRequest.Object)); memset(&(NetRequest.Interface),0,sizeof(NetRequest.Interface)); if (penEntryName) // specific entry - we used to leave wildcards for interface and object wcscpy(NetRequest.EntryName,*penEntryName); else // otherwise, use real interface and object if given memset(&(NetRequest.EntryName),0,sizeof(WCHAR)*MAX_ENTRY_NAME_LENGTH); // NOTE: compatibility removal: the following two lines were conditional // on the else before the "shenanigans" were eliminated if (pIDobject) NetRequest.Object = pIDobject->myGUID(); if (pGVInterface) NetRequest.Interface = *pGVInterface; } TSSLEntryList * getBroadcastResults( ULONG cacheTolerance, CEntryName * penEntryName, CGUIDVersion * pGVInterface, CGUID * pIDobject, ULONG & StatusCode ) /*++ Routine Description: Broadcasts for the requested binding handles. Arguments: cacheTolerance - the staleness tolerance associated with this request, used in deciding whether to broadcast at all penEntryName - (wrapped) entry name pGVInterface - (wrapped) interface UUID and version pIDobject - (wrapped) object UUID StatusCode - The status of the broadcast attempt is returned here Returns: A temporary cache of entries containing the new information. Remarks: A broadcast is made only if a subsuming broadcast has not been made within the staleness limit specified by the parameter cacheTolerance. Only one broadcast is allowed at a time, since there is only one mailslot for replies. --*/ { char * pcBuffer; ULONG waitCur = INITIAL_MAILSLOT_READ_WAIT; // current wait time for replies STRING_T szEntryDomain; READ_MAIL_SLOT *hMailslotForReplies = myRpcLocator->hMailslotForReplies; long cbRead; QueryReply NetReply; if (!hMailslotForReplies) { StatusCode = NSI_S_ENTRY_NOT_FOUND; return NULL; } QueryPacket NetRequest; wcscpy(NetRequest.WkstaName, TEXT("\\\\")); wcscpy(NetRequest.WkstaName + 2, *myRpcLocator->getComputerName()); formQueryPacket(penEntryName,pGVInterface,pIDobject,NetRequest); // now get this broadcast cleared -- make sure it is not redundant if (!myRpcLocator->broadcastCleared(NetRequest,cacheTolerance)) { if (penEntryName) DBGOUT(BROADCAST, "\nBroadcast request denied for " << *penEntryName << "\n" << "Interface = " << &NetRequest.Interface.SyntaxGUID << "\n" << "Object = " << &NetRequest.Object << "\n\n"); else DBGOUT(BROADCAST, "\nBroadcast request denied for NULL entry\n" << "Interface = " << &NetRequest.Interface.SyntaxGUID << "\n" << "Object = " << &NetRequest.Object << "\n\n"); return NULL; } else { if (penEntryName) DBGOUT(BROADCAST, "\nI am broadcasting for " << *penEntryName << "\n" << "Interface = " << &NetRequest.Interface.SyntaxGUID << "\n" << "Object = " << &NetRequest.Object << "\n\n"); else DBGOUT(BROADCAST, "\nI am broadcasting for NULL entry\n" << "Interface = " << &NetRequest.Interface.SyntaxGUID << "\n" << "Object = " << &NetRequest.Object << "\n\n"); myRpcLocator->markBroadcast(NetRequest); } // szDomain should be either NULL or a plain Domain name STRING_T szDomainParam; CStringW *pswDomain = penEntryName ? penEntryName->getDomainName() : NULL; if (pswDomain) szDomainParam = catenate(TEXT("\\\\"),*pswDomain); else szDomainParam = TEXT("\\\\*"); TSSLEntryList *psslCache = new TSSLEntryList; StatusCode = NSI_S_OK; __try { csBindingBroadcastGuard.Enter(); // only one broadcast at a time WRITE_MAIL_SLOT MSquery(szDomainParam, PMAILNAME_S); MSquery.Write((char *) &NetRequest, sizeof(NetRequest)); // now loop waiting for responses from other RPC servers while (cbRead = hMailslotForReplies->Read((char *) &NetReply, sizeof(NetReply), waitCur ) ) { pcBuffer = NetReply.Buffer; szEntryDomain = (_wcsicmp( *myRpcLocator->getDomainName(), NetReply.Domain ) == 0 )? NULL: NetReply.Domain; while (!StatusCode) { UnmarshallBroadcastReplyItem( psslCache, szEntryDomain, pcBuffer, cbRead, StatusCode ); } StatusCode = NSI_S_OK; // halve the wait period everytime you get a response from the net waitCur >>= 1; } csBindingBroadcastGuard.Leave(); } __except(EXCEPTION_EXECUTE_HANDLER) { csBindingBroadcastGuard.Leave(); StatusCode = GetExceptionCode(); psslCache->wipeOut(); delete psslCache; psslCache = NULL; if (pswDomain) delete [] szDomainParam; return NULL; } if (pswDomain) delete [] szDomainParam; if (psslCache->size() == 0) { delete psslCache; psslCache = NULL; } return psslCache; } /**************** Methods related to MailSlotReplyItem ********************/ /* a little macro exclusively for use in CMailSlotReplyItem::Marshall */ #define bufferIt(source) \ lBufferSize -= advance; \ if (lBufferSize < 0) return 0; \ memcpy(pcBuffer, (char*) source, advance); \ pcBuffer += advance; DWORD CMailSlotReplyItem::Marshall( char * pcBuffer, long lBufferSize ) /*++ Method Description: --*/ { DWORD dwOriginalBufferSize = lBufferSize; fixed_part_of_reply fpr; fpr.type = MailslotServerEntryType; fpr.Interface = Interface; fpr.XferSyntax = XferSyntax; fpr.BindingLength = wcslen(binding)+ 1; fpr.EntryNameLength = wcslen(entryName) + 1; long advance; advance = sizeof(fixed_part_of_reply); bufferIt(&fpr); advance = fpr.EntryNameLength * sizeof(WCHAR); bufferIt(entryName); advance = sizeof(long); long objListSize = pObjectList->size(); bufferIt(&objListSize); advance = sizeof(void*); // this is a useless pointer part lBufferSize -= advance; // therefore we don't marshall it pcBuffer += advance; // we just account for its size TCSafeSkipListIterator objIter(*pObjectList); advance = sizeof(GUID); for (CGUID *obj =objIter.next(); obj; obj =objIter.next()) { GUID rep = obj->myGUID(); bufferIt(&rep); } advance = fpr.BindingLength * sizeof(WCHAR); bufferIt(binding); return dwOriginalBufferSize - lBufferSize; } TMSRILinkList * CBindingVector::msriList( CInterface *pIf, TCSafeSkipList* psslObjList ) /*++ Method Description: We combine every relevant object in the server entry with every basic binding string although that seems ridiculous. This is the way the old locator expects us to behave as far as I can tell. --*/ { CMailSlotReplyItem *pmsrl; TMSRILinkList *pmsrill = new TMSRILinkList; TCSafeSkipListIterator bindingIter(*this); for (CStringW * psw = bindingIter.next(); psw; psw = bindingIter.next()) { pmsrl = new CMailSlotReplyItem; pmsrl->binding = *psw; pmsrl->pObjectList = psslObjList; pmsrl->Interface = pIf->myIdAndVersion(); pmsrl->XferSyntax = pIf->xferSyntaxIdAndVersion(); pmsrl->entryName = pMyEntry->getCurrentName(); pmsrill->insert(pmsrl); } return pmsrill; } /********** CBroadcastLookupHandle Methods ****************/ CBroadcastLookupHandle::CBroadcastLookupHandle( UNSIGNED32 EntryNameSyntax, STRING_T EntryName, CGUIDVersion * pGVInterface, CGUIDVersion * pGVTransferSyntax, CGUID * pIDobject, unsigned long ulVectorSize, unsigned long ulCacheAge ) : CRemoteLookupHandle( EntryNameSyntax, EntryName, pGVInterface, pGVTransferSyntax, pIDobject, ulVectorSize, ulCacheAge ) { } void CBroadcastLookupHandle::initialize() { StatusCode = NSI_S_OK; psslNewCache = getBroadcastResults( ulCacheMax, penEntryName, pgvInterface, pidObject, StatusCode ); if (!psslNewCache) plhFetched = NULL; else { TSSLEntryListIterator *pCacheIter = new TSSLEntryListIterator(*psslNewCache); plhFetched = new CGroupLookupHandle( pCacheIter, pgvInterface, pgvTransferSyntax, pidObject, ulVS, ulCacheMax ); } if (penEntryName && !plhFetched) // we looked for a specific entry StatusCode = NSI_S_ENTRY_NO_NEW_INFO; // by name but found nothing new, // possibly because the broadcast // was redundant and was not made ulCreationTime = CurrentTime(); fNotInitialized = FALSE; } /****************** CBroadcastObjectInqHandle Methods *******************/ CBroadcastObjectInqHandle::CBroadcastObjectInqHandle( STRING_T szEntryName, ULONG ulCacheAge ) : CRemoteObjectInqHandle(szEntryName,ulCacheAge) { } void CBroadcastObjectInqHandle::initialize() { StatusCode = NSI_S_OK; TSSLEntryList *psslNewCache = getBroadcastResults( ulCacheMax, penEntryName, NULL, NULL, StatusCode ); if (!psslNewCache || (psslNewCache->size() == 0)) { pUuidVector = NULL; return; } CEntry *pEntry = psslNewCache->pop(); psslNewCache->wipeOut(); delete psslNewCache; pUuidVector = getVector(pEntry->objectInquiry(ulCacheMax)); delete pEntry; ulCreationTime = CurrentTime(); fNotInitialized = FALSE; } /************ Methods for CServerMailSlotReplyHandle *************/ CServerMailSlotReplyHandle::CServerMailSlotReplyHandle( TMSRILinkList * pmsrill ) { pmsriIterator = new TMSRILinkListIterator(*pmsrill); delete pmsrill; // see CServerLookupHandle::CServerLookupHandle above } CServerMailSlotReplyHandle::~CServerMailSlotReplyHandle() { for ( // run down the remaining items in the handle CMailSlotReplyItem* pmsri = pmsriIterator->next(); pmsri; pmsri = pmsriIterator->next() ) { delete pmsri; } delete pmsriIterator; } /************ Methods for CIndexMailSlotReplyHandle *************/ void CIndexMailSlotReplyHandle::advanceCurrentHandle() { delete pCurrentHandle; pCurrentHandle = NULL; CEntry *pCurEntry; while (!pEIterator->finished()) { pCurEntry = pEIterator->next(); if (pCurEntry->isCacheType()) continue; pCurrentHandle = pCurEntry->MailSlotLookup( pGVInterface, pIDobject ); if (pCurrentHandle && !pCurrentHandle->finished()) break; } } CIndexMailSlotReplyHandle::CIndexMailSlotReplyHandle( CGUIDVersion * pGVInf, CGUID * pIDobj, TEntryIterator * pEI ) { pEIterator = pEI; pGVInterface = pGVInf; pIDobject = pIDobj; pCurrentHandle = NULL; advanceCurrentHandle(); } CMailSlotReplyItem * CIndexMailSlotReplyHandle::next() { if (pCurrentHandle && pCurrentHandle->finished()) advanceCurrentHandle(); if (!pCurrentHandle) return NULL; // no more entries else return pCurrentHandle->next(); } int CIndexMailSlotReplyHandle::finished() { if (pCurrentHandle && pCurrentHandle->finished()) advanceCurrentHandle(); if (!pCurrentHandle) return TRUE; // no more entries else return FALSE; } /************ MailSlotLookup Methods *************/ CMailSlotReplyHandle * CServerEntry::MailSlotLookup( CGUIDVersion * pGVInterface, CGUID * pIDobject ) /*++ Method Description: --*/ { TCSafeSkipListIterator IfIter(InterfaceList); TMSRILinkList *pmsrill = new TMSRILinkList; for (CInterface *pIf = IfIter.next(); pIf != NULL; pIf = IfIter.next()) if (pIf->isCompatibleWith(pGVInterface,NULL) ) { TMSRILinkList *pmsrillTemp = pIf->pBVhandles->msriList(pIf,&ObjectList); pmsrill->catenate(*pmsrillTemp); delete pmsrillTemp; } return new CServerMailSlotReplyHandle( pmsrill ); } CMailSlotReplyHandle * CFullServerEntry::MailSlotLookup( CGUIDVersion * pGVInterface, CGUID * pIDobject ) { if (pLocalEntry) return pLocalEntry->MailSlotLookup( pGVInterface, pIDobject ); else return NULL; } /************** Thread definition for replying to broadcasts ***************/ #define cleanup() \ delete plhQuery; \ delete pGVinterface; \ delete pIDobject; \ delete pEntryName; void QueryProcess(void*) /*++ Routine Description: This thread creates a mailslot which listens for requests for RPC servers of a given interface GID. It then uses LookUp to build a response list. It then replies via a mailslot to the requesting machine. --*/ { QueryReply NetReply; QueryPacket NetQuery; DWORD dwMailSize, dwBufferUsed; // create both a server (s) side mailslot READ_MAIL_SLOT *hMailslotForQueries; __try{ hMailslotForQueries = new READ_MAIL_SLOT(PMAILNAME_S, sizeof(QueryPacket)); } __except(EXCEPTION_EXECUTE_HANDLER) { ExitThread(NSI_S_MAILSLOT_ERROR); } wcscpy(NetReply.Domain, *myRpcLocator->getDomainName()); while (1) { CMailSlotReplyHandle * plhQuery = NULL; CGUIDVersion * pGVinterface = NULL; CGUID * pIDobject = NULL; CEntry * pEntry = NULL; RPC_STATUS status = NSI_S_OK; dwMailSize = hMailslotForQueries->Read( (char *) &NetQuery, sizeof(QueryPacket), MAILSLOT_WAIT_FOREVER ); if (dwMailSize != sizeof(QueryPacket)) continue; // strange query, ignore it // ignore messages to self, sending a request on a mailslot // by a NetLookUp request will be delivered to the local slot too. // The pointer arithmetic skips over the initial "\\" in the name if (myRpcLocator->IsSelf(NetQuery.WkstaName+2)) continue; DBGOUT(BROADCAST, "\nReceived broadcast request from " << NetQuery.WkstaName << "\n\n"); // OK looks like a genuine query -- if we are in a workgroup, rememeber // the broadcaster as a potential master if (myRpcLocator->IsInWorkgroup()) myRpcLocator->addMaster((NetQuery.WkstaName)+2); pGVinterface = UuidIsNil(&NetQuery.Interface.SyntaxGUID,&status) ? NULL : new CGUIDVersion(NetQuery.Interface); pIDobject = UuidIsNil(&NetQuery.Object,&status) ? NULL : new CGUID(NetQuery.Object); // Variable used to make sure the entry name is well formed and local CEntryName *pEntryName = NULL; // This global reader block is needed because a lot of the info in the // MSRIs is "borrowed" from regular entries, i.e., not copied. Thus, an // unexport in a separate thread can have unfortunate consequences. CriticalReader me(rwEntryGuard); if (NetQuery.EntryName[0] == 0) { // use default entry plhQuery = new CIndexMailSlotReplyHandle( pGVinterface, pIDobject, myRpcLocator->IndexLookup(pGVinterface) ); if (!plhQuery) { cleanup(); // ignore the query continue; // if nothing to report } } else { __try { pEntryName = new CEntryName(NetQuery.EntryName); } __except (EXCEPTION_EXECUTE_HANDLER) { cleanup(); // ignore the query continue; // if there is any problem } if (!pEntryName->isLocal()) { cleanup(); // ignore the query continue; // if entry not in our domain } pEntry = myRpcLocator->findEntry(pEntryName); if (!pEntry) { cleanup(); // ignore the query continue; // if entry not found } else plhQuery = pEntry->MailSlotLookup( pGVinterface, pIDobject ); if (!plhQuery) { cleanup(); // ignore the query continue; // if nothing to report } } WRITE_MAIL_SLOT MSReply(NetQuery.WkstaName, PMAILNAME_C); char *pcBuffer = NetReply.Buffer; /* we must take care of the possibility that an item is fetched but cannot be marshalled because the buffer is full -- we have to retry marshalling it in the next cycle */ CMailSlotReplyItem * pmsriNext = NULL; pmsriNext = plhQuery->next(); while (pmsriNext) { int BufferNotFull = TRUE; dwBufferUsed = 0; while (BufferNotFull && pmsriNext) { /* NOTE: Compatibility Issue: The +END_FLAG_SIZE below reserves space for the 4-byte 0 flag required by the old locator at the end of the buffer. */ DWORD dwBytesWritten = pmsriNext->Marshall( pcBuffer+dwBufferUsed, NET_REPLY_BUFFER_SIZE-(dwBufferUsed+END_FLAG_SIZE) ); if (dwBytesWritten) { dwBufferUsed += dwBytesWritten; delete pmsriNext; pmsriNext = plhQuery->next(); // only if marshalling was successful } else BufferNotFull = FALSE; } /* NOTE: Compatibility Issue: The old locator does not use the count of bytes read to decide when to stop unmarshalling a mailslot reply -- instead it uses the 0 flag after the last marshalled entry for that purpose, so for compatibility reasons we must do the needful. */ memset(pcBuffer+dwBufferUsed,0,END_FLAG_SIZE); MSReply.Write( (char*) &NetReply, dwBufferUsed + MAX_DOMAIN_NAME_LENGTH * sizeof(WCHAR) + END_FLAG_SIZE ); } cleanup(); } }