/*************************************************************************
*
* dirwalk.c
*
* Walk a tree setting ACL's on an NT system.
*
* Copyright Microsoft, 1998
*
*
*
*************************************************************************/


#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <stdio.h>
#include <process.h>

#include <winsta.h>
#include <syslib.h>

#include "security.h"

#if DBG
ULONG
DbgPrint(
    PCH Format,
    ...
    );
#define DBGPRINT(x) DbgPrint x
#if DBGTRACE
#define TRACE0(x)   DbgPrint x
#define TRACE1(x)   DbgPrint x
#else
#define TRACE0(x)
#define TRACE1(x)
#endif
#else
#define DBGPRINT(x)
#define TRACE0(x)
#define TRACE1(x)
#endif


// Global variables
PWCHAR gpAvoidDir = NULL;

CRITICAL_SECTION SyslibCritSect;


typedef BOOLEAN (CALLBACK* NODEPROC)( PWCHAR, PWIN32_FIND_DATAW, DWORD, DWORD );

BOOLEAN
EnumerateDirectory(
    PWCHAR   pRoot,
    DWORD    Level,
    NODEPROC pProc
    );

BOOLEAN
NodeEnumProc(
    PWCHAR pParent,
    PWIN32_FIND_DATA p,
    DWORD  Level,
    DWORD  Index
    );

PWCHAR
AddWildCard(
    PWCHAR pRoot
    );

PWCHAR
AddBackSlash(
    PWCHAR pRoot
    );

FILE_RESULT
xxxProcessFile(
    PWCHAR pParent,
    PWIN32_FIND_DATAW p,
    DWORD  Level,
    DWORD  Index
    );

/*****************************************************************************
 *
 *  CtxGetSyslibCritSect
 *
 *   Returns the library global critical section pointer.
 *   Critical section will be initialised if necessary
 *
 * ENTRY:
 *    VOID - Caller must ensure that only one threads a times calls this
 *           function. The function will not itself guarantie mutual exclusion.
 * EXIT:
 *   Pointer to critical section. NULL if failure.
 *
 ****************************************************************************/


PCRITICAL_SECTION
CtxGetSyslibCritSect(void)
{
    static BOOLEAN fInitialized = FALSE;
    NTSTATUS Status;

    if( !fInitialized ){

            Status = RtlInitializeCriticalSection(&SyslibCritSect);
            if (NT_SUCCESS(Status)) {
                fInitialized = TRUE;
            }else{
                return NULL;
            }

    }
    return(&SyslibCritSect);
}


/*****************************************************************************
 *
 *  SetFileTree
 *
 *   Walk the given tree calling the processing function for each node.
 *
 * ENTRY:
 *   pRoot (input)
 *     Full WIN32 path to directory to walk
 *
 *   pVoidDir (input)
 *
 * EXIT:
 *   STATUS_SUCCESS - no error
 *
 ****************************************************************************/

BOOLEAN
SetFileTree(
    PWCHAR   pRoot,
    PWCHAR   pAvoidDir
    )
{
    BOOLEAN rc;
    PWCHAR pRootNew;
    static BOOLEAN fInitialized = FALSE;
    PRTL_CRITICAL_SECTION pLock = CtxGetSyslibCritSect(); 

    // if critical section could not be created, do nothing.

    if (pLock == NULL) {
        return FALSE;
    }
    DBGPRINT(( "entering SetFileTree(pRoot=%ws,pAvoidDir=%ws)\n", pRoot, pAvoidDir ));

    EnterCriticalSection(pLock);
    // If this is the console make sure the user hasn't changed

    if ((NtCurrentPeb()->SessionId == 0) && fInitialized) {
        CheckUserSid();

    } else if ( !fInitialized ) {
       fInitialized = TRUE;
       if ( !InitSecurity() ) {
          DBGPRINT(( "problem initializing security; we're outta here.\n" ));
          LeaveCriticalSection(pLock);
          return( 1 ); // (non-zero for error...)// Should be return FALSE!?
       }
    }
    LeaveCriticalSection(pLock);

    gpAvoidDir = pAvoidDir;

    // be sure to apply security to root directory
    pRootNew = AddBackSlash(pRoot);
    if(pRootNew) {
        DBGPRINT(( "processing file %ws\n", pRootNew ));
        xxxProcessFile(pRootNew, NULL, 0, 0);
        LocalFree(pRootNew);
    }

    rc = EnumerateDirectory( pRoot, 0, NodeEnumProc );
    DBGPRINT(( "leaving SetFileTree()\n" ));
    return( rc );
}

/*****************************************************************************
 *
 *  EnumerateDirectory
 *
 *   Walk the given directory calling the processing function for each file.
 *
 * ENTRY:
 *   pRoot (input)
 *     Full WIN32 path to directory to walk
 *
 *   Level (input)
 *     Level we are in a given tree. Useful for formating output
 *
 *   pProc (input)
 *     Procedure to call for each non-directory file
 *
 * EXIT:
 *   STATUS_SUCCESS - no error
 *
 ****************************************************************************/

BOOLEAN
EnumerateDirectory(
    PWCHAR   pRoot,
    DWORD    Level,
    NODEPROC pProc
    )
{
    BOOL rc;
    DWORD Result;
    HANDLE hf;
    DWORD Index;
    WIN32_FIND_DATA Data;
    PWCHAR pRootNew;

    DBGPRINT(( "entering EnumerateDirectory(pRoot=%ws,Level=%ld,pProc=NodeEnumProc)\n",pRoot,Level ));

    if( pRoot == NULL ) {
        DBGPRINT(( "leaving EnumerateDirectory(), return=FALSE\n" ));
        return( FALSE );
    }

    // Make sure it is not one we want to avoid
    if( gpAvoidDir ) {
        DWORD Len = wcslen( gpAvoidDir );

        if( _wcsnicmp( pRoot, gpAvoidDir, Len ) == 0 ) {
            DBGPRINT(( "leaving EnumerateDirectory(), return=FALSE\n" ));
            return( FALSE );
        }
    }

    pRootNew = AddWildCard( pRoot );
    if( pRootNew == NULL ) {
        DBGPRINT(( "leaving EnumerateDirectory(), return=FALSE\n" ));
        return( FALSE );
    }

    Index = 0;

    DBGPRINT(("FindFirstFileW: %ws\n",pRootNew));

    hf = FindFirstFileW(
             pRootNew,
             &Data
             );

    if( hf == INVALID_HANDLE_VALUE ) {
        DBGPRINT(("EnumerateDirectory: Error %d opening root %ws\n",GetLastError(),pRootNew));
        LocalFree( pRootNew );
        DBGPRINT(( "leaving EnumerateDirectory(), return=FALSE\n" ));
        return(FALSE);
    }

    while( 1 ) {

        // pass the parent without the wildcard added
        pProc( pRoot, &Data, Level, Index );

        rc = FindNextFile(
                 hf,
                 &Data
                 );

        if( !rc ) {
            Result = GetLastError();
            if( Result == ERROR_NO_MORE_FILES ) {
                FindClose( hf );
                LocalFree( pRootNew );
                DBGPRINT(( "leaving EnumerateDirectory(), return=TRUE\n" ));
                return( TRUE );
            }
            else {
                DBGPRINT(("EnumerateDirectory: Error %d, Index 0x%x\n",Result,Index));
                FindClose( hf );
                LocalFree( pRootNew );
                DBGPRINT(( "leaving EnumerateDirectory(), return=FALSE\n" ));
                return( FALSE );
            }
        }

        Index++;
    }

// UNREACHABLE.

}

/*****************************************************************************
 *
 *  NodeEnumProc
 *
 *   Process the enumerated file
 *
 * ENTRY:
 *   Param1 (input/output)
 *     Comments
 *
 * EXIT:
 *   STATUS_SUCCESS - no error
 *
 ****************************************************************************/

BOOLEAN
NodeEnumProc(
    PWCHAR pParent,
    PWIN32_FIND_DATAW p,
    DWORD  Level,
    DWORD  Index
    )
{
    BOOLEAN rc;
    PWCHAR  pParentNew;
    DWORD   ParentCount, ChildCount;

    //
    // We must append the directory to our parent path to get the
    // new full path.
    //

    ParentCount = wcslen( pParent );
    ChildCount  = wcslen( p->cFileName );

    pParentNew = LocalAlloc( LMEM_FIXED, (ParentCount + ChildCount + 2)*sizeof(WCHAR) );

    if( pParentNew == NULL ) return( FALSE );

    wcscpy( pParentNew, pParent );
    wcscat( pParentNew, L"\\" );
    wcscat( pParentNew, p->cFileName );

    if( p->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {

        // Skip "." and ".."
        if( wcscmp( L".", p->cFileName ) == 0 ) {
            LocalFree( pParentNew );
            return( TRUE );
        }

        if( wcscmp( L"..", p->cFileName ) == 0 ) {
            LocalFree( pParentNew );
            return( TRUE );
        }

        TRACE0(("%ws:\n",pParentNew));

        xxxProcessFile( pParentNew, p, Level, Index );

        // For directories, we recurse
        rc = EnumerateDirectory( pParentNew, Level+1, NodeEnumProc );

        LocalFree( pParentNew );

        return( rc );
    }

    TRACE0(("%ws\n",pParentNew));

    xxxProcessFile( pParentNew, p, Level, Index );

    LocalFree( pParentNew );

    return( TRUE );
}

/*****************************************************************************
 *
 *  AddWildCard
 *
 *   Add the wild card search specifier
 *
 * ENTRY:
 *   Param1 (input/output)
 *     Comments
 *
 * EXIT:
 *   STATUS_SUCCESS - no error
 *
 ****************************************************************************/

PWCHAR
AddWildCard(
    PWCHAR pRoot
    )
{
    DWORD  Count;
    PWCHAR pNew;
    PWCHAR WildCard = L"\\*";

    Count = wcslen( pRoot );
    pNew = LocalAlloc( LMEM_FIXED, (Count + wcslen(WildCard) + 1)*sizeof(WCHAR) );

    if( pNew == NULL ) {
        return( NULL );
    }

    wcscpy( pNew, pRoot );
    wcscat( pNew, WildCard );

    return( pNew );
}

/*****************************************************************************
 *
 *  AddBackSlash
 *
 *   Add the backslash character to path
 *
 * ENTRY:
 *   Param1 (input/output)
 *     Comments
 *
 * EXIT:
 *   STATUS_SUCCESS - no error
 *
 ****************************************************************************/

PWCHAR
AddBackSlash(
    PWCHAR pRoot
    )
{
    DWORD  Count;
    PWCHAR pNew;
    PWCHAR BackSlash = L"\\";

    Count = wcslen( pRoot );
    pNew = LocalAlloc( LMEM_FIXED, (Count + wcslen(BackSlash) + 1)*sizeof(WCHAR) );

    if( pNew == NULL ) {
        return( NULL );
    }

    wcscpy( pNew, pRoot );

    // only add backslash if string doesn't already have it
    if(*(pRoot+Count-1) != L'\\')
        wcscat( pNew, BackSlash );

    return( pNew );
}