//Copyright (c) 1998 - 1999 Microsoft Corporation
/*******************************************************************************
*
* domain.cpp
*
* implementation of the CDomain class
*
*
*******************************************************************************/

#include "stdafx.h"
#include "winadmin.h"
#include "admindoc.h"
#include "dialogs.h"
#include <malloc.h>                     // for alloca used by Unicode conversion macros
#include <mfc42\afxconv.h>           // for Unicode conversion macros
static int _convert;

#include <winsta.h>
#include <regapi.h>
#include "..\..\inc\utilsub.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define MIN_MAJOR_VERSION 4
#define MIN_MINOR_VERSION 0



//////////////////////////////////////////////////////////////////////////////////////////
//
//      CDomain Member Functions
//
//////////////////////////////////////////////////////////////////////////////////////////

CDomain::CDomain(TCHAR *name)
{
    m_Flags = 0;
    m_PreviousState = DS_NONE;
    m_State = DS_NONE;
    m_hTreeItem = NULL;
    wcscpy(m_Name, name);
    m_pBackgroundThread = NULL;    
}


CDomain::~CDomain()
{
        if(m_State == DS_ENUMERATING) StopEnumerating();

}


void CDomain::SetState(DOMAIN_STATE state)
{
        // remember the previous state
        m_PreviousState = m_State;

        m_State = state;

        CWinAdminDoc *pDoc = (CWinAdminDoc*)((CWinAdminApp*)AfxGetApp())->GetDocument();

        CFrameWnd *p = (CFrameWnd*)pDoc->GetMainWnd();
        if(p && ::IsWindow(p->GetSafeHwnd())) {
                p->SendMessage(WM_ADMIN_UPDATE_DOMAIN, 0, (LPARAM)this);
        }
}


BOOL CDomain::StartEnumerating()
{
    BOOL bResult = FALSE;
    
    LockBackgroundThread();
    
    if( m_State == DS_ENUMERATING || m_State == DS_STOPPED_ENUMERATING )
    {
        UnlockBackgroundThread( );

        return FALSE;
    }

        // Fire off the background thread for this domain
    
    if( m_pBackgroundThread == NULL )
    {
        DomainProcInfo *pProcInfo = new DomainProcInfo;
        
        if( pProcInfo != NULL )
        {
            pProcInfo->pDomain = this;
            pProcInfo->pDoc = (CWinAdminDoc*)((CWinAdminApp*)AfxGetApp())->GetDocument();
            m_BackgroundContinue = TRUE;
            m_pBackgroundThread = AfxBeginThread((AFX_THREADPROC)CDomain::BackgroundThreadProc,
                                                 pProcInfo,
                                                 0,
                                                 CREATE_SUSPENDED,
                                                 NULL );

            if( m_pBackgroundThread == NULL )
            {
                ODS( L"CDomain!StartEnumerating AfxBeginThread failed running low on resources\n" );

                delete pProcInfo;

                return FALSE;
            }

            m_pBackgroundThread->m_bAutoDelete = FALSE;

            if (m_pBackgroundThread->ResumeThread() <= 1)
            {
                bResult = TRUE;
            }
        }
    }
    
    UnlockBackgroundThread();
    
    return TRUE;
}


void CDomain::StopEnumerating()
{
    // Tell the background thread to terminate and
    // wait for it to do so.
    LockBackgroundThread();

    if(m_pBackgroundThread)
    {
        CWinThread *pBackgroundThread = m_pBackgroundThread;
        HANDLE hThread = m_pBackgroundThread->m_hThread;
        
        // Clear the pointer before releasing the lock
        m_pBackgroundThread = NULL;
        
        ClearBackgroundContinue( );

        UnlockBackgroundThread();
        
        // Wait for the thread's death
        if(WaitForSingleObject(hThread, 1000) == WAIT_TIMEOUT)
        {
            TerminateThread(hThread, 0);
        }

        WaitForSingleObject(hThread, INFINITE);
        
        // delete the CWinThread object
        delete pBackgroundThread;
    }
    else
    {
        UnlockBackgroundThread();
    }
    
    
    SetState(DS_STOPPED_ENUMERATING);

    DBGMSG( L"%s stopped enumerating\n" , GetName( ) );
}


USHORT Buflength(LPWSTR buf)
{
        LPWSTR p = buf;
        USHORT length = 0;

        while(*p) {
                USHORT plength = wcslen(p) + 1;
                length += plength;
                p += plength;
        }

        return length;

}       // end Buflength


LPWSTR ConcatenateBuffers(LPWSTR buf1, LPWSTR buf2)
{
        // Make sure both buffer pointers are valid
        if(!buf1 && !buf2) return NULL;
        if(buf1 && !buf2) return buf1;
        if(!buf1 && buf2) return buf2;

        // figure out how big a buffer we'll need
        USHORT buf1Length = Buflength(buf1);
        USHORT buf2Length = Buflength(buf2);
        USHORT bufsize = buf1Length + buf2Length + 1;
        // allocate a buffer
        LPWSTR pBuffer = (LPWSTR)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, bufsize * sizeof(WCHAR));
    // If we can't allocate a buffer, free the second buffer and
    // return the pointer to the first of the two buffers
    if(!pBuffer) {
        LocalFree(buf2);
        return(buf1);
    }

        LPWSTR p = pBuffer;
        // copy the contents of the first buffer into the new buffer
        memcpy((char*)p, (char*)buf1, buf1Length * sizeof(WCHAR));
        p += buf1Length;
        // copy the contents of the second buffer into the new buffer
        memcpy((char*)p, (char*)buf2, buf2Length * sizeof(WCHAR));

        LocalFree(buf1);
        LocalFree(buf2);

        return pBuffer;

}       // end ConcatenateBuffers

void CDomain::CreateServers(LPWSTR pBuffer, LPVOID _pDoc)
{    
    CWinAdminDoc *pDoc = (CWinAdminDoc*)_pDoc;
    CWinAdminApp *pApp = (CWinAdminApp*)AfxGetApp();
    
    LPWSTR pTemp = pBuffer;
    
    // Loop through all the WinFrame servers that we found
    while(*pTemp)
    {
        // The server's name is in pTemp
        // Find the server in our list
        CServer *pServer = pDoc->FindServerByName(pTemp);
        // If the server is in our list, set the flag to say we found it
        if(pServer)
        {
            pServer->SetBackgroundFound();

            if( pServer->GetTreeItem( ) == NULL )
            {
                CFrameWnd *p = (CFrameWnd*)pDoc->GetMainWnd();
                
                p->SendMessage(WM_ADMIN_ADD_SERVER, ( WPARAM )TVI_SORT, (LPARAM)pServer);
            }

        }
        else
        {
            // We don't want to add the current Server again
            if( lstrcmpi( pTemp , pApp->GetCurrentServerName() ) )
            {
                // Create a new server object
                CServer *pNewServer = new CServer(this, pTemp, FALSE, pDoc->ShouldConnect(pTemp));
                
                if(pNewServer != NULL )
                {
                    // Add the server object to our linked list
                    pDoc->AddServer(pNewServer);
                    
                    // Set the flag to say we found it
                    pNewServer->SetBackgroundFound();
                    
                    CFrameWnd *p = (CFrameWnd*)pDoc->GetMainWnd();
                    
                    if(p && ::IsWindow(p->GetSafeHwnd()))
                    {
                        p->SendMessage(WM_ADMIN_ADD_SERVER, ( WPARAM )TVI_SORT, (LPARAM)pNewServer);                        
                        
                    }
                }
            }
        }
        // Go to the next server in the buffer
        pTemp += (wcslen(pTemp) + 1);
    } // end while (*pTemp)
    
}

/////////////////////////////////////////////////////////////////////////////
// CDomain::BackgroundThreadProc
//
// Static member function for background thread
// Looks for servers appearing and disappearing
// Called with AfxBeginThread
// Thread terminates when function returns
//
UINT CDomain::BackgroundThreadProc(LPVOID bg)
{
    // We need a pointer to the document so we can make
    // calls to member functions
    CWinAdminDoc *pDoc = (CWinAdminDoc*)((DomainProcInfo*)bg)->pDoc;
    CDomain *pDomain = ((DomainProcInfo*)bg)->pDomain;
    delete bg;
    
    CWinAdminApp *pApp = (CWinAdminApp*)AfxGetApp();
    
    // We want to keep track of whether or not we've enumerated - so
    // that we can update the tree when we're done
    BOOL bNotified = FALSE;
    
    // We can't send messages to the view until they're ready
    // v-nicbd  RESOLVED  In case we are exiting tsadmin, we are waiting uselessly here
    //          - 500ms lapsed is negligible in UI
    while( !pDoc->AreAllViewsReady() )
    {
        Sleep(500);
    }
    
    // Don't do this until the views are ready!
    pDomain->SetState(DS_INITIAL_ENUMERATION);
    
    // If there is an extension DLL loaded, we will allow it to enumerate
    // additional servers
    LPFNEXENUMERATEPROC EnumerateProc = pApp->GetExtEnumerationProc();
    
    // The first time we enumerate servers, we want the CServer object
    // to put the server in the views when it has enough info.
    // On subsequent enumerations, we will add the server to the views
    // here.
    BOOL bSubsequent = FALSE;
    
    while(pDomain->ShouldBackgroundContinue())
    {
        
        BOOL Enumerated = FALSE;
        
        CObList TempServerList;
        
        DBGMSGx( L"CDomain!BackgroundThreadProc %s still going thread %d\n" , pDomain->GetName( ) , GetCurrentThreadId( ) );        
        
        // Loop through all the servers and turn off the flag
        // that tells this thread that he found it on this pass
        pDoc->LockServerList();
        CObList *pServerList = pDoc->GetServerList();
        
        POSITION pos = pServerList->GetHeadPosition();
        
        while(pos)
        {
            POSITION pos2 = pos;
            
            CServer *pServer = (CServer*)pServerList->GetNext(pos);
            
            if(pServer->GetDomain() == pDomain)
            {
                pServer->ClearBackgroundFound();
                
                // We want to remove a server if we could see it
                // the last time we enumerated servers
                // NOTE: This should not cause any problems
                //       The views should no longer have items pointing
                //       to this server at this point
                //
                // Move the server object to a temporary list.
                // This is so that we can unlock the server list before
                // we call the destructor on a CServer object since the
                // destructor will end up calling SetState() which does
                // a SendMessage.  This is not good to do with the list
                // locked.
                
                if(pServer->IsServerInactive() && !pServer->IsCurrentServer())
                {                    
                    pServer = (CServer*)pServerList->GetAt(pos2);
                    // Remove it from the server list
                    DBGMSG( L"Adding %s to temp list to destroy\n" , pServer->GetName( ) );
                    
                    pServerList->RemoveAt(pos2);
                    // Add it to our temporary list
                    TempServerList.AddTail(pServer);
                }
            }
        }
        
        pDoc->UnlockServerList();
        
        // do a first loop to signal the servers' background threads that they must stop
        pos = TempServerList.GetHeadPosition();
        while(pos)
        {
            CServer *pServer = (CServer*)TempServerList.GetNext(pos);
            
            DBGMSG( L"Clearing %s backgrnd cont\n", pServer->GetName() );
            
            pServer->ClearBackgroundContinue();
        }
        // do a second loop to disconnect and delete the servers
        pos = TempServerList.GetHeadPosition();
        
        while(pos)
        {
            CServer *pServer = (CServer*)TempServerList.GetNext(pos);
            
            DBGMSG( L"Disconnecting and deleteing %s now!!!\n", pServer->GetName( ) );
            
            pServer->Disconnect( );
            
            delete pServer;
            
            ODS( L"gone.\n" );
        }
        
        TempServerList.RemoveAll();
        
        // Make sure we don't have to quit
        if(!pDomain->ShouldBackgroundContinue())
        {
            return 0;
        }
        
        // Get all the Servers now (we already got the current server)
        LPWSTR pBuffer = NULL;
        
        // Find all WinFrame servers in the domain
        pBuffer = pDomain->EnumHydraServers(/*pDomain->GetName(),*/ MIN_MAJOR_VERSION, MIN_MINOR_VERSION);
        
        // Make sure we don't have to quit
        if(!pDomain->ShouldBackgroundContinue())
        {
            if(pBuffer) LocalFree(pBuffer);
            return 0;
        }
        
        // Make sure we don't have to quit
        if(!pDomain->ShouldBackgroundContinue())
        {
            if(pBuffer) LocalFree(pBuffer);
            return 0;
        }
        
        if(pBuffer) {
            Enumerated = TRUE;
            
            pDomain->CreateServers(pBuffer, (LPVOID)pDoc);
            
            LocalFree(pBuffer);
        }       // end if(pBuffer)
        
        // Make sure we don't have to quit
        if(!pDomain->ShouldBackgroundContinue()) return 0;
        
        if(!bNotified) {
            pDomain->SetState(DS_ENUMERATING);
            bNotified = TRUE;
        }
        
        // If there is an extension DLL loaded, allow it to enumerate additional servers
        LPWSTR pExtBuffer = NULL;
        
        if(EnumerateProc) {
            pExtBuffer = (*EnumerateProc)(pDomain->GetName());
        }
        
        // If the extension DLL found servers, concatenate the two buffers
        // The ConcatenateBuffers function will delete both buffers and return a
        // pointer to the new buffer
        if(pExtBuffer) {
            Enumerated = TRUE;
            pDomain->CreateServers(pExtBuffer, (LPVOID)pDoc);
            LocalFree(pExtBuffer);
        }
        
        // Make sure we don't have to quit
        if(!pDomain->ShouldBackgroundContinue())
        {
            return 0;
        }
        
        if(Enumerated)
        {
            // Mark the current server as found
            CServer *pCurrentServer = pDoc->GetCurrentServer();
            if(pCurrentServer) pCurrentServer->SetBackgroundFound();
            // Go through the list of servers and see which ones don't have
            // the flag set saying that we found it
            CObList TempList;
            
            pDoc->LockServerList();
            pServerList = pDoc->GetServerList();
            
            POSITION pos = pServerList->GetHeadPosition();
            
            while(pos)
            {
                CServer *pServer = (CServer*)pServerList->GetNext(pos);
                
                if(pServer->GetDomain() == pDomain)
                {
                    // we check to see if this server has been initially inserted to our server list
                    // manually. If so we don't want it inserted to our templist for deletion.
                    if( !pServer->IsManualFind() &&
                        ( !pServer->IsBackgroundFound() || 
                        pServer->HasLostConnection() ||
                        !pServer->IsServerSane() ) )
                    {
                        DBGMSG( L"Removing %s background not found or lost connection\n" , pServer->GetName( ) );
                        // Set the flag to say that this server is inactive
                        pServer->SetServerInactive();
                        // Add it to our temporary list
                        TempList.AddTail(pServer);
                    }
                }
            }
            
            pDoc->UnlockServerList();
            
            pos = TempList.GetHeadPosition();
            
            CFrameWnd *p = (CFrameWnd*)pDoc->GetMainWnd();
            
            while(pos)
            {
                CServer *pServer = (CServer*)TempList.GetNext(pos);
                // Send a message to the mainframe to remove the server
                if(p && ::IsWindow(p->GetSafeHwnd()))
                {
                    DBGMSG( L"CDomain!Bkthrd removing %s temped threads from treeview & view\n" , pServer->GetName( ) );
                    
                    // clean up old node
                    if( pServer->GetTreeItemFromFav( ) != NULL )
                    {
                        // we cannot keep a favnode around if a server node is being deleted
                        // massive AV's will occurr.  So a quick fix is to remove the favnode 
                        // if it exists and create a new server node and mark it as manually
                        // found.  This will prevent this server node from being removed in 
                        // case NetEnumServer fails to pick up this server
                        p->SendMessage( WM_ADMIN_REMOVESERVERFROMFAV , TRUE , ( LPARAM )pServer );                   
                        
                        CServer *ptServer = new CServer( pDomain , pServer->GetName( ) , FALSE , FALSE );
                        
                        if( ptServer != NULL )
                        {
                            ptServer->SetManualFind( );
                            
                            pDoc->AddServer(ptServer);
                            
                            p->SendMessage(WM_ADMIN_ADDSERVERTOFAV , 0 , (LPARAM)ptServer);
                        }
                    }
                    
                    p->SendMessage(WM_ADMIN_REMOVE_SERVER, TRUE, (LPARAM)pServer);       
                }
            }
            
            TempList.RemoveAll();
            
        } // end if(Enumerated)
        
        // We don't want to do this constantly, it eats up processor cycles to enumerate servers
        // so we'll now let the user refresh these servers manually
        // Document destructor will signal the event to wake us up if he
        // wants us to quit
        pDomain->m_WakeUpEvent.Lock( INFINITE );
        
        bSubsequent = TRUE;
    }   // end while(1)
    
    return 0;
    
}       // end CDomain::BackgroundThreadProc



/*******************************************************************************
 *
 *      EnumHydraServers - Hydra helper function (taken from utildll and modified
 *      to be used along with a version check.
 *
 *      Enumerate the Hydra servers on the network by Domain
 *      Returns all the servers whose version is >= the version passed.
 *
 *  ENTRY:
 *      pDomain (input)
 *          Specifies the domain to enumerate; NULL for current domain.
 *      verMajor (input)
 *          specifies the Major version to check for.
 *      verMinor (input)
 *          specifies the minor version to check for.
 *
 *  EXIT:
 *      (LPTSTR) Points to LocalAlloced buffer containing results of the
 *               enumeration, in multi-string format, if sucessful; NULL if
 *               error.  The caller must perform a LocalFree of this buffer
 *               when done.  If error (NULL), the error code is set for
 *               retrieval by GetLastError();
 *
 ******************************************************************************/
LPWSTR CDomain::EnumHydraServers( /*LPWSTR pDomain,*/ DWORD verMajor, DWORD verMinor )

{
    PSERVER_INFO_101 pInfo = NULL;
    DWORD dwByteCount, dwIndex, TotalEntries;
    DWORD AvailCount = 0;
    LPWSTR pTemp, pBuffer = NULL;

    /*
     * Enumerate all WF servers on the specified domain.
     */
    if ( NetServerEnum ( NULL,
                         101,
                         (LPBYTE *)&pInfo,
                         (DWORD) -1,
                         &AvailCount,
                         &TotalEntries,
                         SV_TYPE_TERMINALSERVER,
                         m_Name, /*pDomain,*/
                         NULL ) ||
         !AvailCount )
        goto done;

    //
    // Traverse list of the servers that match the major and minor versions'criteria
    // and calculate the total byte count for list of
    // servers that will be returned.
    //
    for( dwByteCount = dwIndex = 0; dwIndex < AvailCount; dwIndex++ )
    {
        if( ((pInfo[dwIndex].sv101_version_major & MAJOR_VERSION_MASK) >=
            verMajor) && (pInfo[dwIndex].sv101_version_minor >= verMinor) )
        {
            dwByteCount += (wcslen(pInfo[dwIndex].sv101_name) + 1) * 2;
        }
    }

    dwByteCount += 2;   // for ending null

    /*
     * Allocate memory.
     */
    if( (pBuffer = (LPWSTR)LocalAlloc(LPTR, dwByteCount)) == NULL )
    {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        goto done;
    }

    /*
     * Traverse list again and copy servers to buffer.
     */
    for( pTemp = pBuffer, dwIndex = 0; dwIndex < AvailCount; dwIndex++ )
    {
        if( ((pInfo[dwIndex].sv101_version_major & MAJOR_VERSION_MASK) >=
            verMajor) && (pInfo[dwIndex].sv101_version_minor >= verMinor) )
        {
            // MS Bug 1821
            if ( wcslen(pInfo[dwIndex].sv101_name) != 0 )
            {
                wcscpy(pTemp, pInfo[dwIndex].sv101_name);

                pTemp += (wcslen(pInfo[dwIndex].sv101_name) + 1);
            }
        }
    }
    *pTemp = L'\0';     // ending null
    
done:
    if( AvailCount && pInfo )
    {
        NetApiBufferFree( pInfo );
    }
    
    return(pBuffer);
    
}  // end CDomain::EnumHydraServers


/////////////////////////////////////////////////////////////////////////////
// CDomain::DisconnectAllServers
//
// Disconnect from all servers in this Domain
//
void CDomain::DisconnectAllServers()
{
        CWinAdminDoc *pDoc = (CWinAdminDoc*)((CWinAdminApp*)AfxGetApp())->GetDocument();

        CObList *pServerList = pDoc->GetServerList();

    CString AString;
        CDialog dlgWait;
        dlgWait.Create(IDD_SHUTDOWN, NULL);


        pDoc->LockServerList();

    ODS( L"TSADMIN:CDomain::DisconnectAllServers about to disconnect all connected servers\n" );

    // Do a first loop to signal the server background threads that they must stop
        POSITION pos = pServerList->GetHeadPosition();
        while(pos) {
                // Get a pointer to the server
                CServer *pServer = (CServer*)pServerList->GetNext(pos);
                // If this Server is in the domain and connected, tell the server background thread to stop
                if(pServer->GetDomain() == this
                        && pServer->GetState() != SS_NOT_CONNECTED) {
            pServer->ClearBackgroundContinue();
        }
        }
    // do a second loop to disconnect the servers
        pos = pServerList->GetHeadPosition();
        while(pos) {
                // Get a pointer to the server
                CServer *pServer = (CServer*)pServerList->GetNext(pos);
                // If this Server is in the domain and connected, disconnect from it
                if ((pServer->GetDomain() == this) && (pServer->GetState() != SS_NOT_CONNECTED)) {
                        AString.Format(IDS_DISCONNECTING, pServer->GetName());
                        dlgWait.SetDlgItemText(IDC_SHUTDOWN_MSG, AString);

                        // Tell the server to connect
                pServer->Disconnect();
                }
        }

    //
    // tell domain not to connect to any more servers
    //


        pDoc->UnlockServerList();

        dlgWait.PostMessage(WM_CLOSE);

}       // end CDomain::DisconnectAllServers


/////////////////////////////////////////////////////////////////////////////
// CDomain::ConnectAllServers
//
// Connect to all servers in this Domain
//
void CDomain::ConnectAllServers()
{
        CWinAdminDoc *pDoc = (CWinAdminDoc*)((CWinAdminApp*)AfxGetApp())->GetDocument();

        CObList *pServerList = pDoc->GetServerList();

        pDoc->LockServerList();

        POSITION pos = pServerList->GetHeadPosition();
        while(pos) {
                // Get a pointer to the server
                CServer *pServer = (CServer*)pServerList->GetNext(pos);
                // If this Server is int the domain and not connected, connect to it
                if(pServer->GetDomain() == this
                        && pServer->IsState(SS_NOT_CONNECTED)) {
                // Tell the server to connect
                    pServer->Connect();
                }
        }

        pDoc->UnlockServerList();

}       // end CDomain::ConnectAllServers