Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

748 lines
24 KiB

  1. //Copyright (c) 1998 - 1999 Microsoft Corporation
  2. /*******************************************************************************
  3. *
  4. * domain.cpp
  5. *
  6. * implementation of the CDomain class
  7. *
  8. *
  9. *******************************************************************************/
  10. #include "stdafx.h"
  11. #include "winadmin.h"
  12. #include "admindoc.h"
  13. #include "dialogs.h"
  14. #include <malloc.h> // for alloca used by Unicode conversion macros
  15. #include <mfc42\afxconv.h> // for Unicode conversion macros
  16. static int _convert;
  17. #include <winsta.h>
  18. #include <regapi.h>
  19. #include "..\..\inc\utilsub.h"
  20. #ifdef _DEBUG
  21. #define new DEBUG_NEW
  22. #undef THIS_FILE
  23. static char THIS_FILE[] = __FILE__;
  24. #endif
  25. #define MIN_MAJOR_VERSION 4
  26. #define MIN_MINOR_VERSION 0
  27. //////////////////////////////////////////////////////////////////////////////////////////
  28. //
  29. // CDomain Member Functions
  30. //
  31. //////////////////////////////////////////////////////////////////////////////////////////
  32. CDomain::CDomain(TCHAR *name)
  33. {
  34. m_Flags = 0;
  35. m_PreviousState = DS_NONE;
  36. m_State = DS_NONE;
  37. m_hTreeItem = NULL;
  38. wcscpy(m_Name, name);
  39. m_pBackgroundThread = NULL;
  40. }
  41. CDomain::~CDomain()
  42. {
  43. if(m_State == DS_ENUMERATING) StopEnumerating();
  44. }
  45. void CDomain::SetState(DOMAIN_STATE state)
  46. {
  47. // remember the previous state
  48. m_PreviousState = m_State;
  49. m_State = state;
  50. CWinAdminDoc *pDoc = (CWinAdminDoc*)((CWinAdminApp*)AfxGetApp())->GetDocument();
  51. CFrameWnd *p = (CFrameWnd*)pDoc->GetMainWnd();
  52. if(p && ::IsWindow(p->GetSafeHwnd())) {
  53. p->SendMessage(WM_ADMIN_UPDATE_DOMAIN, 0, (LPARAM)this);
  54. }
  55. }
  56. BOOL CDomain::StartEnumerating()
  57. {
  58. BOOL bResult = FALSE;
  59. LockBackgroundThread();
  60. if( m_State == DS_ENUMERATING || m_State == DS_STOPPED_ENUMERATING )
  61. {
  62. UnlockBackgroundThread( );
  63. return FALSE;
  64. }
  65. // Fire off the background thread for this domain
  66. if( m_pBackgroundThread == NULL )
  67. {
  68. DomainProcInfo *pProcInfo = new DomainProcInfo;
  69. if( pProcInfo != NULL )
  70. {
  71. pProcInfo->pDomain = this;
  72. pProcInfo->pDoc = (CWinAdminDoc*)((CWinAdminApp*)AfxGetApp())->GetDocument();
  73. m_BackgroundContinue = TRUE;
  74. m_pBackgroundThread = AfxBeginThread((AFX_THREADPROC)CDomain::BackgroundThreadProc,
  75. pProcInfo,
  76. 0,
  77. CREATE_SUSPENDED,
  78. NULL );
  79. if( m_pBackgroundThread == NULL )
  80. {
  81. ODS( L"CDomain!StartEnumerating AfxBeginThread failed running low on resources\n" );
  82. delete pProcInfo;
  83. return FALSE;
  84. }
  85. m_pBackgroundThread->m_bAutoDelete = FALSE;
  86. if (m_pBackgroundThread->ResumeThread() <= 1)
  87. {
  88. bResult = TRUE;
  89. }
  90. }
  91. }
  92. UnlockBackgroundThread();
  93. return TRUE;
  94. }
  95. void CDomain::StopEnumerating()
  96. {
  97. // Tell the background thread to terminate and
  98. // wait for it to do so.
  99. LockBackgroundThread();
  100. if(m_pBackgroundThread)
  101. {
  102. CWinThread *pBackgroundThread = m_pBackgroundThread;
  103. HANDLE hThread = m_pBackgroundThread->m_hThread;
  104. // Clear the pointer before releasing the lock
  105. m_pBackgroundThread = NULL;
  106. ClearBackgroundContinue( );
  107. UnlockBackgroundThread();
  108. // Wait for the thread's death
  109. if(WaitForSingleObject(hThread, 1000) == WAIT_TIMEOUT)
  110. {
  111. TerminateThread(hThread, 0);
  112. }
  113. WaitForSingleObject(hThread, INFINITE);
  114. // delete the CWinThread object
  115. delete pBackgroundThread;
  116. }
  117. else
  118. {
  119. UnlockBackgroundThread();
  120. }
  121. SetState(DS_STOPPED_ENUMERATING);
  122. DBGMSG( L"%s stopped enumerating\n" , GetName( ) );
  123. }
  124. USHORT Buflength(LPWSTR buf)
  125. {
  126. LPWSTR p = buf;
  127. USHORT length = 0;
  128. while(*p) {
  129. USHORT plength = wcslen(p) + 1;
  130. length += plength;
  131. p += plength;
  132. }
  133. return length;
  134. } // end Buflength
  135. LPWSTR ConcatenateBuffers(LPWSTR buf1, LPWSTR buf2)
  136. {
  137. // Make sure both buffer pointers are valid
  138. if(!buf1 && !buf2) return NULL;
  139. if(buf1 && !buf2) return buf1;
  140. if(!buf1 && buf2) return buf2;
  141. // figure out how big a buffer we'll need
  142. USHORT buf1Length = Buflength(buf1);
  143. USHORT buf2Length = Buflength(buf2);
  144. USHORT bufsize = buf1Length + buf2Length + 1;
  145. // allocate a buffer
  146. LPWSTR pBuffer = (LPWSTR)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, bufsize * sizeof(WCHAR));
  147. // If we can't allocate a buffer, free the second buffer and
  148. // return the pointer to the first of the two buffers
  149. if(!pBuffer) {
  150. LocalFree(buf2);
  151. return(buf1);
  152. }
  153. LPWSTR p = pBuffer;
  154. // copy the contents of the first buffer into the new buffer
  155. memcpy((char*)p, (char*)buf1, buf1Length * sizeof(WCHAR));
  156. p += buf1Length;
  157. // copy the contents of the second buffer into the new buffer
  158. memcpy((char*)p, (char*)buf2, buf2Length * sizeof(WCHAR));
  159. LocalFree(buf1);
  160. LocalFree(buf2);
  161. return pBuffer;
  162. } // end ConcatenateBuffers
  163. void CDomain::CreateServers(LPWSTR pBuffer, LPVOID _pDoc)
  164. {
  165. CWinAdminDoc *pDoc = (CWinAdminDoc*)_pDoc;
  166. CWinAdminApp *pApp = (CWinAdminApp*)AfxGetApp();
  167. LPWSTR pTemp = pBuffer;
  168. // Loop through all the WinFrame servers that we found
  169. while(*pTemp)
  170. {
  171. // The server's name is in pTemp
  172. // Find the server in our list
  173. CServer *pServer = pDoc->FindServerByName(pTemp);
  174. // If the server is in our list, set the flag to say we found it
  175. if(pServer)
  176. {
  177. pServer->SetBackgroundFound();
  178. if( pServer->GetTreeItem( ) == NULL )
  179. {
  180. CFrameWnd *p = (CFrameWnd*)pDoc->GetMainWnd();
  181. p->SendMessage(WM_ADMIN_ADD_SERVER, ( WPARAM )TVI_SORT, (LPARAM)pServer);
  182. }
  183. }
  184. else
  185. {
  186. // We don't want to add the current Server again
  187. if( lstrcmpi( pTemp , pApp->GetCurrentServerName() ) )
  188. {
  189. // Create a new server object
  190. CServer *pNewServer = new CServer(this, pTemp, FALSE, pDoc->ShouldConnect(pTemp));
  191. if(pNewServer != NULL )
  192. {
  193. // Add the server object to our linked list
  194. pDoc->AddServer(pNewServer);
  195. // Set the flag to say we found it
  196. pNewServer->SetBackgroundFound();
  197. CFrameWnd *p = (CFrameWnd*)pDoc->GetMainWnd();
  198. if(p && ::IsWindow(p->GetSafeHwnd()))
  199. {
  200. p->SendMessage(WM_ADMIN_ADD_SERVER, ( WPARAM )TVI_SORT, (LPARAM)pNewServer);
  201. }
  202. }
  203. }
  204. }
  205. // Go to the next server in the buffer
  206. pTemp += (wcslen(pTemp) + 1);
  207. } // end while (*pTemp)
  208. }
  209. /////////////////////////////////////////////////////////////////////////////
  210. // CDomain::BackgroundThreadProc
  211. //
  212. // Static member function for background thread
  213. // Looks for servers appearing and disappearing
  214. // Called with AfxBeginThread
  215. // Thread terminates when function returns
  216. //
  217. UINT CDomain::BackgroundThreadProc(LPVOID bg)
  218. {
  219. // We need a pointer to the document so we can make
  220. // calls to member functions
  221. CWinAdminDoc *pDoc = (CWinAdminDoc*)((DomainProcInfo*)bg)->pDoc;
  222. CDomain *pDomain = ((DomainProcInfo*)bg)->pDomain;
  223. delete bg;
  224. CWinAdminApp *pApp = (CWinAdminApp*)AfxGetApp();
  225. // We want to keep track of whether or not we've enumerated - so
  226. // that we can update the tree when we're done
  227. BOOL bNotified = FALSE;
  228. // We can't send messages to the view until they're ready
  229. // v-nicbd RESOLVED In case we are exiting tsadmin, we are waiting uselessly here
  230. // - 500ms lapsed is negligible in UI
  231. while( !pDoc->AreAllViewsReady() )
  232. {
  233. Sleep(500);
  234. }
  235. // Don't do this until the views are ready!
  236. pDomain->SetState(DS_INITIAL_ENUMERATION);
  237. // If there is an extension DLL loaded, we will allow it to enumerate
  238. // additional servers
  239. LPFNEXENUMERATEPROC EnumerateProc = pApp->GetExtEnumerationProc();
  240. // The first time we enumerate servers, we want the CServer object
  241. // to put the server in the views when it has enough info.
  242. // On subsequent enumerations, we will add the server to the views
  243. // here.
  244. BOOL bSubsequent = FALSE;
  245. while(pDomain->ShouldBackgroundContinue())
  246. {
  247. BOOL Enumerated = FALSE;
  248. CObList TempServerList;
  249. DBGMSGx( L"CDomain!BackgroundThreadProc %s still going thread %d\n" , pDomain->GetName( ) , GetCurrentThreadId( ) );
  250. // Loop through all the servers and turn off the flag
  251. // that tells this thread that he found it on this pass
  252. pDoc->LockServerList();
  253. CObList *pServerList = pDoc->GetServerList();
  254. POSITION pos = pServerList->GetHeadPosition();
  255. while(pos)
  256. {
  257. POSITION pos2 = pos;
  258. CServer *pServer = (CServer*)pServerList->GetNext(pos);
  259. if(pServer->GetDomain() == pDomain)
  260. {
  261. pServer->ClearBackgroundFound();
  262. // We want to remove a server if we could see it
  263. // the last time we enumerated servers
  264. // NOTE: This should not cause any problems
  265. // The views should no longer have items pointing
  266. // to this server at this point
  267. //
  268. // Move the server object to a temporary list.
  269. // This is so that we can unlock the server list before
  270. // we call the destructor on a CServer object since the
  271. // destructor will end up calling SetState() which does
  272. // a SendMessage. This is not good to do with the list
  273. // locked.
  274. if(pServer->IsServerInactive() && !pServer->IsCurrentServer())
  275. {
  276. pServer = (CServer*)pServerList->GetAt(pos2);
  277. // Remove it from the server list
  278. DBGMSG( L"Adding %s to temp list to destroy\n" , pServer->GetName( ) );
  279. pServerList->RemoveAt(pos2);
  280. // Add it to our temporary list
  281. TempServerList.AddTail(pServer);
  282. }
  283. }
  284. }
  285. pDoc->UnlockServerList();
  286. // do a first loop to signal the servers' background threads that they must stop
  287. pos = TempServerList.GetHeadPosition();
  288. while(pos)
  289. {
  290. CServer *pServer = (CServer*)TempServerList.GetNext(pos);
  291. DBGMSG( L"Clearing %s backgrnd cont\n", pServer->GetName() );
  292. pServer->ClearBackgroundContinue();
  293. }
  294. // do a second loop to disconnect and delete the servers
  295. pos = TempServerList.GetHeadPosition();
  296. while(pos)
  297. {
  298. CServer *pServer = (CServer*)TempServerList.GetNext(pos);
  299. DBGMSG( L"Disconnecting and deleteing %s now!!!\n", pServer->GetName( ) );
  300. pServer->Disconnect( );
  301. delete pServer;
  302. ODS( L"gone.\n" );
  303. }
  304. TempServerList.RemoveAll();
  305. // Make sure we don't have to quit
  306. if(!pDomain->ShouldBackgroundContinue())
  307. {
  308. return 0;
  309. }
  310. // Get all the Servers now (we already got the current server)
  311. LPWSTR pBuffer = NULL;
  312. // Find all WinFrame servers in the domain
  313. pBuffer = pDomain->EnumHydraServers(/*pDomain->GetName(),*/ MIN_MAJOR_VERSION, MIN_MINOR_VERSION);
  314. // Make sure we don't have to quit
  315. if(!pDomain->ShouldBackgroundContinue())
  316. {
  317. if(pBuffer) LocalFree(pBuffer);
  318. return 0;
  319. }
  320. // Make sure we don't have to quit
  321. if(!pDomain->ShouldBackgroundContinue())
  322. {
  323. if(pBuffer) LocalFree(pBuffer);
  324. return 0;
  325. }
  326. if(pBuffer) {
  327. Enumerated = TRUE;
  328. pDomain->CreateServers(pBuffer, (LPVOID)pDoc);
  329. LocalFree(pBuffer);
  330. } // end if(pBuffer)
  331. // Make sure we don't have to quit
  332. if(!pDomain->ShouldBackgroundContinue()) return 0;
  333. if(!bNotified) {
  334. pDomain->SetState(DS_ENUMERATING);
  335. bNotified = TRUE;
  336. }
  337. // If there is an extension DLL loaded, allow it to enumerate additional servers
  338. LPWSTR pExtBuffer = NULL;
  339. if(EnumerateProc) {
  340. pExtBuffer = (*EnumerateProc)(pDomain->GetName());
  341. }
  342. // If the extension DLL found servers, concatenate the two buffers
  343. // The ConcatenateBuffers function will delete both buffers and return a
  344. // pointer to the new buffer
  345. if(pExtBuffer) {
  346. Enumerated = TRUE;
  347. pDomain->CreateServers(pExtBuffer, (LPVOID)pDoc);
  348. LocalFree(pExtBuffer);
  349. }
  350. // Make sure we don't have to quit
  351. if(!pDomain->ShouldBackgroundContinue())
  352. {
  353. return 0;
  354. }
  355. if(Enumerated)
  356. {
  357. // Mark the current server as found
  358. CServer *pCurrentServer = pDoc->GetCurrentServer();
  359. if(pCurrentServer) pCurrentServer->SetBackgroundFound();
  360. // Go through the list of servers and see which ones don't have
  361. // the flag set saying that we found it
  362. CObList TempList;
  363. pDoc->LockServerList();
  364. pServerList = pDoc->GetServerList();
  365. POSITION pos = pServerList->GetHeadPosition();
  366. while(pos)
  367. {
  368. CServer *pServer = (CServer*)pServerList->GetNext(pos);
  369. if(pServer->GetDomain() == pDomain)
  370. {
  371. // we check to see if this server has been initially inserted to our server list
  372. // manually. If so we don't want it inserted to our templist for deletion.
  373. if( !pServer->IsManualFind() &&
  374. ( !pServer->IsBackgroundFound() ||
  375. pServer->HasLostConnection() ||
  376. !pServer->IsServerSane() ) )
  377. {
  378. DBGMSG( L"Removing %s background not found or lost connection\n" , pServer->GetName( ) );
  379. // Set the flag to say that this server is inactive
  380. pServer->SetServerInactive();
  381. // Add it to our temporary list
  382. TempList.AddTail(pServer);
  383. }
  384. }
  385. }
  386. pDoc->UnlockServerList();
  387. pos = TempList.GetHeadPosition();
  388. CFrameWnd *p = (CFrameWnd*)pDoc->GetMainWnd();
  389. while(pos)
  390. {
  391. CServer *pServer = (CServer*)TempList.GetNext(pos);
  392. // Send a message to the mainframe to remove the server
  393. if(p && ::IsWindow(p->GetSafeHwnd()))
  394. {
  395. DBGMSG( L"CDomain!Bkthrd removing %s temped threads from treeview & view\n" , pServer->GetName( ) );
  396. // clean up old node
  397. if( pServer->GetTreeItemFromFav( ) != NULL )
  398. {
  399. // we cannot keep a favnode around if a server node is being deleted
  400. // massive AV's will occurr. So a quick fix is to remove the favnode
  401. // if it exists and create a new server node and mark it as manually
  402. // found. This will prevent this server node from being removed in
  403. // case NetEnumServer fails to pick up this server
  404. p->SendMessage( WM_ADMIN_REMOVESERVERFROMFAV , TRUE , ( LPARAM )pServer );
  405. CServer *ptServer = new CServer( pDomain , pServer->GetName( ) , FALSE , FALSE );
  406. if( ptServer != NULL )
  407. {
  408. ptServer->SetManualFind( );
  409. pDoc->AddServer(ptServer);
  410. p->SendMessage(WM_ADMIN_ADDSERVERTOFAV , 0 , (LPARAM)ptServer);
  411. }
  412. }
  413. p->SendMessage(WM_ADMIN_REMOVE_SERVER, TRUE, (LPARAM)pServer);
  414. }
  415. }
  416. TempList.RemoveAll();
  417. } // end if(Enumerated)
  418. // We don't want to do this constantly, it eats up processor cycles to enumerate servers
  419. // so we'll now let the user refresh these servers manually
  420. // Document destructor will signal the event to wake us up if he
  421. // wants us to quit
  422. pDomain->m_WakeUpEvent.Lock( INFINITE );
  423. bSubsequent = TRUE;
  424. } // end while(1)
  425. return 0;
  426. } // end CDomain::BackgroundThreadProc
  427. /*******************************************************************************
  428. *
  429. * EnumHydraServers - Hydra helper function (taken from utildll and modified
  430. * to be used along with a version check.
  431. *
  432. * Enumerate the Hydra servers on the network by Domain
  433. * Returns all the servers whose version is >= the version passed.
  434. *
  435. * ENTRY:
  436. * pDomain (input)
  437. * Specifies the domain to enumerate; NULL for current domain.
  438. * verMajor (input)
  439. * specifies the Major version to check for.
  440. * verMinor (input)
  441. * specifies the minor version to check for.
  442. *
  443. * EXIT:
  444. * (LPTSTR) Points to LocalAlloced buffer containing results of the
  445. * enumeration, in multi-string format, if sucessful; NULL if
  446. * error. The caller must perform a LocalFree of this buffer
  447. * when done. If error (NULL), the error code is set for
  448. * retrieval by GetLastError();
  449. *
  450. ******************************************************************************/
  451. LPWSTR CDomain::EnumHydraServers( /*LPWSTR pDomain,*/ DWORD verMajor, DWORD verMinor )
  452. {
  453. PSERVER_INFO_101 pInfo = NULL;
  454. DWORD dwByteCount, dwIndex, TotalEntries;
  455. DWORD AvailCount = 0;
  456. LPWSTR pTemp, pBuffer = NULL;
  457. /*
  458. * Enumerate all WF servers on the specified domain.
  459. */
  460. if ( NetServerEnum ( NULL,
  461. 101,
  462. (LPBYTE *)&pInfo,
  463. (DWORD) -1,
  464. &AvailCount,
  465. &TotalEntries,
  466. SV_TYPE_TERMINALSERVER,
  467. m_Name, /*pDomain,*/
  468. NULL ) ||
  469. !AvailCount )
  470. goto done;
  471. //
  472. // Traverse list of the servers that match the major and minor versions'criteria
  473. // and calculate the total byte count for list of
  474. // servers that will be returned.
  475. //
  476. for( dwByteCount = dwIndex = 0; dwIndex < AvailCount; dwIndex++ )
  477. {
  478. if( ((pInfo[dwIndex].sv101_version_major & MAJOR_VERSION_MASK) >=
  479. verMajor) && (pInfo[dwIndex].sv101_version_minor >= verMinor) )
  480. {
  481. dwByteCount += (wcslen(pInfo[dwIndex].sv101_name) + 1) * 2;
  482. }
  483. }
  484. dwByteCount += 2; // for ending null
  485. /*
  486. * Allocate memory.
  487. */
  488. if( (pBuffer = (LPWSTR)LocalAlloc(LPTR, dwByteCount)) == NULL )
  489. {
  490. SetLastError(ERROR_NOT_ENOUGH_MEMORY);
  491. goto done;
  492. }
  493. /*
  494. * Traverse list again and copy servers to buffer.
  495. */
  496. for( pTemp = pBuffer, dwIndex = 0; dwIndex < AvailCount; dwIndex++ )
  497. {
  498. if( ((pInfo[dwIndex].sv101_version_major & MAJOR_VERSION_MASK) >=
  499. verMajor) && (pInfo[dwIndex].sv101_version_minor >= verMinor) )
  500. {
  501. // MS Bug 1821
  502. if ( wcslen(pInfo[dwIndex].sv101_name) != 0 )
  503. {
  504. wcscpy(pTemp, pInfo[dwIndex].sv101_name);
  505. pTemp += (wcslen(pInfo[dwIndex].sv101_name) + 1);
  506. }
  507. }
  508. }
  509. *pTemp = L'\0'; // ending null
  510. done:
  511. if( AvailCount && pInfo )
  512. {
  513. NetApiBufferFree( pInfo );
  514. }
  515. return(pBuffer);
  516. } // end CDomain::EnumHydraServers
  517. /////////////////////////////////////////////////////////////////////////////
  518. // CDomain::DisconnectAllServers
  519. //
  520. // Disconnect from all servers in this Domain
  521. //
  522. void CDomain::DisconnectAllServers()
  523. {
  524. CWinAdminDoc *pDoc = (CWinAdminDoc*)((CWinAdminApp*)AfxGetApp())->GetDocument();
  525. CObList *pServerList = pDoc->GetServerList();
  526. CString AString;
  527. CDialog dlgWait;
  528. dlgWait.Create(IDD_SHUTDOWN, NULL);
  529. pDoc->LockServerList();
  530. ODS( L"TSADMIN:CDomain::DisconnectAllServers about to disconnect all connected servers\n" );
  531. // Do a first loop to signal the server background threads that they must stop
  532. POSITION pos = pServerList->GetHeadPosition();
  533. while(pos) {
  534. // Get a pointer to the server
  535. CServer *pServer = (CServer*)pServerList->GetNext(pos);
  536. // If this Server is in the domain and connected, tell the server background thread to stop
  537. if(pServer->GetDomain() == this
  538. && pServer->GetState() != SS_NOT_CONNECTED) {
  539. pServer->ClearBackgroundContinue();
  540. }
  541. }
  542. // do a second loop to disconnect the servers
  543. pos = pServerList->GetHeadPosition();
  544. while(pos) {
  545. // Get a pointer to the server
  546. CServer *pServer = (CServer*)pServerList->GetNext(pos);
  547. // If this Server is in the domain and connected, disconnect from it
  548. if ((pServer->GetDomain() == this) && (pServer->GetState() != SS_NOT_CONNECTED)) {
  549. AString.Format(IDS_DISCONNECTING, pServer->GetName());
  550. dlgWait.SetDlgItemText(IDC_SHUTDOWN_MSG, AString);
  551. // Tell the server to connect
  552. pServer->Disconnect();
  553. }
  554. }
  555. //
  556. // tell domain not to connect to any more servers
  557. //
  558. pDoc->UnlockServerList();
  559. dlgWait.PostMessage(WM_CLOSE);
  560. } // end CDomain::DisconnectAllServers
  561. /////////////////////////////////////////////////////////////////////////////
  562. // CDomain::ConnectAllServers
  563. //
  564. // Connect to all servers in this Domain
  565. //
  566. void CDomain::ConnectAllServers()
  567. {
  568. CWinAdminDoc *pDoc = (CWinAdminDoc*)((CWinAdminApp*)AfxGetApp())->GetDocument();
  569. CObList *pServerList = pDoc->GetServerList();
  570. pDoc->LockServerList();
  571. POSITION pos = pServerList->GetHeadPosition();
  572. while(pos) {
  573. // Get a pointer to the server
  574. CServer *pServer = (CServer*)pServerList->GetNext(pos);
  575. // If this Server is int the domain and not connected, connect to it
  576. if(pServer->GetDomain() == this
  577. && pServer->IsState(SS_NOT_CONNECTED)) {
  578. // Tell the server to connect
  579. pServer->Connect();
  580. }
  581. }
  582. pDoc->UnlockServerList();
  583. } // end CDomain::ConnectAllServers