/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    nwspl.c

Abstract:

    This module contains the Netware print provider.

Author:

    Yi-Hsin Sung    (yihsins)   15-Apr-1993

Revision History:
    Yi-Hsin Sung    (yihsins)   15-May-1993
        Moved most of the functionality to the server side

    Ram Viswanathan (ramv)      09-Aug-1995
        Added functionality to Add and Delete Printer.


--*/

#include <stdio.h>

#include <nwclient.h>
#include <winspool.h>
#include <winsplp.h>
#include <ntlsa.h>

#include <nwpkstr.h>
#include <splutil.h>
#include <nwreg.h>
#include <nwspl.h>
#include <nwmisc.h>
#include <winsta.h>
#include  <nds.h>
#include <ndsapi32.h>
#include "nwutil.h"

#include <overflow.h>

//------------------------------------------------------------------
//
// Local Functions
//
//------------------------------------------------------------------
// now all SKUs have TerminalServer flag.  If App Server is enabled, SingleUserTS flag is cleared
#define IsTerminalServer() (BOOLEAN)(!(USER_SHARED_DATA->SuiteMask & (1 << SingleUserTS))) //user mode
DWORD
InitializePortNames(
    VOID
);

VOID
NwpGetUserInfo(
    LPWSTR *ppszUser
);

DWORD
NwpGetThreadUserInfo(
    LPWSTR  *ppszUser,
    LPWSTR  *ppszUserSid
);

DWORD
NwpGetUserNameFromSid(
    PSID pUserSid,
    LPWSTR *ppszUserName
);



DWORD
NwpGetLogonUserInfo(
    LPWSTR  *ppszUserSid
);


DWORD
ThreadIsInteractive(
    VOID
);

VOID
pFreeAllContexts();

//------------------------------------------------------------------
//
// Global Variables
//
//------------------------------------------------------------------

HMODULE hmodNW = NULL;
BOOL    fIsWinnt = FALSE ;

BOOL  bEnableAddPrinterConnection = FALSE;
BOOL  bRestrictToInboxDrivers = FALSE;
BOOL  bNeedAddPrinterConnCleanup = FALSE;

WCHAR *pszRegistryPath = NULL;
WCHAR *pszRegistryPortNames=L"PortNames";
WCHAR szMachineName[MAX_COMPUTERNAME_LENGTH + 3];
PNWPORT pNwFirstPort = NULL;
CRITICAL_SECTION NwSplSem;
CRITICAL_SECTION NwServiceListCriticalSection; // Used to protect linked
                                               // list of registered services
HANDLE           NwServiceListDoneEvent = NULL;// Used to stop local advertise
                                               // threads.
STATIC HANDLE handleDummy;  // This is a dummy handle used to
                            // return to the clients if we have previously
                            // opened the given printer successfully
                            // and the netware workstation service is not
                            // currently available.

STATIC
PRINTPROVIDOR PrintProvidor = { OpenPrinter,
                                SetJob,
                                GetJob,
                                EnumJobs,
                                AddPrinter,                 // NOT SUPPORTED
                                DeletePrinter,              // NOT SUPPORTED
                                SetPrinter,
                                GetPrinter,
                                EnumPrinters,
                                AddPrinterDriver,           // NOT SUPPORTED
                                EnumPrinterDrivers,         // NOT SUPPORTED
                                GetPrinterDriverW,          // NOT SUPPORTED
                                GetPrinterDriverDirectory,  // NOT SUPPORTED
                                DeletePrinterDriver,        // NOT SUPPORTED
                                AddPrintProcessor,          // NOT SUPPORTED
                                EnumPrintProcessors,        // NOT SUPPORTED
                                GetPrintProcessorDirectory, // NOT SUPPORTED
                                DeletePrintProcessor,       // NOT SUPPORTED
                                EnumPrintProcessorDatatypes,// NOT SUPPORTED
                                StartDocPrinter,
                                StartPagePrinter,           // NOT SUPPORTED
                                WritePrinter,
                                EndPagePrinter,             // NOT SUPPORTED
                                AbortPrinter,
                                ReadPrinter,                // NOT SUPPORTED
                                EndDocPrinter,
                                AddJob,
                                ScheduleJob,
                                GetPrinterData,             // NOT SUPPORTED
                                SetPrinterData,             // NOT SUPPORTED
                                WaitForPrinterChange,
                                ClosePrinter,
                                AddForm,                    // NOT SUPPORTED
                                DeleteForm,                 // NOT SUPPORTED
                                GetForm,                    // NOT SUPPORTED
                                SetForm,                    // NOT SUPPORTED
                                EnumForms,                  // NOT SUPPORTED
                                EnumMonitors,               // NOT SUPPORTED
                                EnumPorts,
                                AddPort,                    // NOT SUPPORTED
                                ConfigurePort,
                                DeletePort,
                                CreatePrinterIC,            // NOT SUPPORTED
                                PlayGdiScriptOnPrinterIC,   // NOT SUPPORTED
                                DeletePrinterIC,            // NOT SUPPORTED
                                AddPrinterConnection,
                                DeletePrinterConnection,    // NOT SUPPORTED
                                PrinterMessageBox,          // NOT SUPPORTED
                                AddMonitor,                 // NOT SUPPORTED
                                DeleteMonitor               // NOT SUPPORTED
};


//------------------------------------------------------------------
//
//  Initialization Functions
//
//------------------------------------------------------------------


BOOL InitializeDll(
    HINSTANCE hdll,
    DWORD     dwReason,
    LPVOID    lpReserved
)
{
    NT_PRODUCT_TYPE ProductType ;

    UNREFERENCED_PARAMETER( lpReserved );

    if ( dwReason == DLL_PROCESS_ATTACH )
    {
        DisableThreadLibraryCalls( hdll );

        hmodNW = hdll;

        //
        // are we a winnt machine?
        //
        fIsWinnt = RtlGetNtProductType(&ProductType) ? 
                       (ProductType == NtProductWinNt) :
                       FALSE ;

        //
        // Initialize the critical section for maintaining the registered
        // service list
        //
        InitializeCriticalSection( &NwServiceListCriticalSection );
        NwServiceListDoneEvent = CreateEventA( NULL, TRUE, FALSE, NULL );
    }
    else if ( dwReason == DLL_PROCESS_DETACH )
    {
        //
        // Free up memories used by the port link list
        //
        DeleteAllPortEntries();

        //
        // Get rid of Service List and Shutdown SAP library
        //
        NwTerminateServiceProvider();

#ifndef NT1057
        //
        // Clean up shell extensions
        //
        NwCleanupShellExtensions();
#endif
        pFreeAllContexts();          // clean up RNR stuff
        DeleteCriticalSection( &NwServiceListCriticalSection );
        if ( NwServiceListDoneEvent )
        {
            CloseHandle( NwServiceListDoneEvent );
            NwServiceListDoneEvent = NULL;
        }
    }

    return TRUE;
}



DWORD
InitializePortNames(
    VOID
)
/*++

Routine Description:

    This is called by the InitializePrintProvidor to initialize the ports
    names used in this providor.

Arguments:

    None.

Return Value:

    Returns NO_ERROR or the error that occurred.

--*/
{
    DWORD err;
    HKEY  hkeyPath;
    HKEY  hkeyPortNames;

    err = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                        pszRegistryPath,
                        0,
                        KEY_READ,
                        &hkeyPath );

    if ( !err )
    {
        DWORD BytesNeeded = sizeof( BOOL );

        err = RegQueryValueExW( hkeyPath,
                                L"EnableUserAddPrinter",
                                NULL,
                                NULL,
                                (LPBYTE) &bEnableAddPrinterConnection,
                                &BytesNeeded );
        if ( err ) // just default to not allowed
            bEnableAddPrinterConnection = FALSE;

        err = RegQueryValueExW( hkeyPath,
                                L"RestrictToInboxDrivers",
                                NULL,
                                NULL,
                                (LPBYTE) &bRestrictToInboxDrivers,
                                &BytesNeeded );
        if ( err ) // just default to not restricted
            bRestrictToInboxDrivers = FALSE;

        err = RegOpenKeyEx( hkeyPath,
                            pszRegistryPortNames,
                            0,
                            KEY_READ,
                            &hkeyPortNames );

        if ( !err )
        {
            DWORD i = 0;
            WCHAR Buffer[MAX_PATH];
            DWORD BufferSize;

            while ( !err )
            {
                BufferSize = sizeof(Buffer) / sizeof(WCHAR);

                err = RegEnumValue( hkeyPortNames,
                                    i,
                                    Buffer,
                                    &BufferSize,
                                    NULL,
                                    NULL,
                                    NULL,
                                    NULL );

                if ( !err )
                    CreatePortEntry( Buffer );

                i++;
            }

            /* We expect RegEnumKeyEx to return ERROR_NO_MORE_ITEMS
             * when it gets to the end of the keys, so reset the status:
             */
            if( err == ERROR_NO_MORE_ITEMS )
                err = NO_ERROR;

            RegCloseKey( hkeyPortNames );
        }
#if DBG
        else
        {
            IF_DEBUG(PRINT)
                KdPrint(("NWSPL [RegOpenKeyEx] (%ws) failed: Error = %d\n",
                         pszRegistryPortNames, err ));
        }
#endif

        RegCloseKey( hkeyPath );
    }
#if DBG
    else
    {
        IF_DEBUG(PRINT)
            KdPrint(("NWSPL [RegOpenKeyEx] (%ws) failed: Error = %d\n",
                      pszRegistryPath, err ));
    }
#endif

    return err;
}

//------------------------------------------------------------------
//
// Print Provider Functions supported by NetWare provider
//
//------------------------------------------------------------------


BOOL
InitializePrintProvidor(
    LPPRINTPROVIDOR pPrintProvidor,
    DWORD           cbPrintProvidor,
    LPWSTR          pszFullRegistryPath
)
/*++

Routine Description:

    This is called by the spooler subsystem to initialize the print
    providor.

Arguments:

    pPrintProvidor      -  Pointer to the print providor structure to be
                           filled in by this function
    cbPrintProvidor     -  Count of bytes of the print providor structure
    pszFullRegistryPath -  Full path to the registry key of this print providor

Return Value:

    Always TRUE.

--*/
{
    //
    //  dfergus 20 Apr 2001 #323700
    //  Prevent Multiple CS Initialization
    //
    static int iCSInit = 0;

    DWORD dwLen;

    if ( !pPrintProvidor || !pszFullRegistryPath || !*pszFullRegistryPath )
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    memcpy( pPrintProvidor,
            &PrintProvidor,
            min( sizeof(PRINTPROVIDOR), cbPrintProvidor) );

    //
    // Store the registry path for this print providor
    //
    if ( !(pszRegistryPath = AllocNwSplStr(pszFullRegistryPath)) )
        return FALSE;

    //
    // Store the local machine name
    //
    szMachineName[0] = szMachineName[1] = L'\\';
    dwLen = MAX_COMPUTERNAME_LENGTH;
    GetComputerName( szMachineName + 2, &dwLen );

#if DBG
    IF_DEBUG(PRINT)
    {
        KdPrint(("NWSPL [InitializePrintProvidor] "));
        KdPrint(("RegistryPath = %ws, ComputerName = %ws\n",
                 pszRegistryPath, szMachineName ));
    }
#endif

    //
    //  dfergus 20 Apr 2001 #323700
    //  Prevent Multiple CS Initialization
    //
    if( !iCSInit )
    {
        InitializeCriticalSection( &NwSplSem );
        iCSInit = 1;
    }
    //
    // Ignore the error returned from InitializePortNames.
    // The provider can still function if we cannot get all the port
    // names.
    //
    InitializePortNames();

    return TRUE;
}



BOOL
OpenPrinterW(
    LPWSTR             pszPrinterName,
    LPHANDLE           phPrinter,
    LPPRINTER_DEFAULTS pDefault
)
/*++

Routine Description:

    This routine retrieves a handle identifying the specified printer.

Arguments:

    pszPrinterName -  Name of the printer
    phPrinter      -  Receives the handle that identifies the given printer
    pDefault       -  Points to a PRINTER_DEFAULTS structure. Can be NULL.

Return Value:

    TRUE if the function succeeds, FALSE otherwise. Use GetLastError() for
    extended error information.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(( "NWSPL [OpenPrinter] Name = %ws\n", pszPrinterName ));
#endif

    UNREFERENCED_PARAMETER( pDefault );

    if ( !pszPrinterName )
    {
        SetLastError( ERROR_INVALID_NAME );
        return FALSE;
    }

    RpcTryExcept
    {
        err = NwrOpenPrinter( NULL,
                              pszPrinterName,
                              PortKnown( pszPrinterName ),
                              (LPNWWKSTA_PRINTER_CONTEXT) phPrinter );

        //
        // Make sure there is a port of this name so that
        // EnumPorts will return it.
        //

        if ( !err )
        {

            if ( !PortExists( pszPrinterName, &err ) && !err )
            {
                //
                // We will ignore the errors since it is
                // still OK if we can't add the port.
                // Cannot delete once created, don't create
                // We should not create port entry and registry entry
               
                if ( CreatePortEntry( pszPrinterName ) )
                    CreateRegistryEntry( pszPrinterName );

            }

        }
        
    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
        {
            if ( PortKnown( pszPrinterName ))
            {
                *phPrinter = &handleDummy;
                err = NO_ERROR;
            }
            else
            {
                err = ERROR_INVALID_NAME;
            }
        }
        else
        {
            err = NwpMapRpcError( code );
        }
    }
    RpcEndExcept

    if ( err )
    {
        SetLastError( err );

#if DBG
        IF_DEBUG(PRINT)
            KdPrint(("NWSPL [OpenPrinter] err = %d\n", err));
#endif
    }

    return ( err == NO_ERROR );

}



BOOL
ClosePrinter(
    HANDLE  hPrinter
)
/*++

Routine Description:

    This routine closes the given printer object.

Arguments:

    hPrinter -  Handle of the printer object

Return Value:

    TRUE if the function succeeds, FALSE otherwise. Use GetLastError() for
    extended error information.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(( "NWSPL [ClosePrinter]\n"));
#endif

    //
    // Just return success if the handle is a dummy one
    //
    if ( hPrinter == &handleDummy )
        return TRUE;

    // Clean up any registry stuff left behind by AddPrinterConnection
    if ( bEnableAddPrinterConnection 
        && bNeedAddPrinterConnCleanup )
    {
        DWORD err;
        HKEY  hkey;
        HKEY  hSubKey;
        WCHAR * szConnKey = L"Printers\\Connections";
#if DBG
    IF_DEBUG(PRINT)
    {
        KdPrint(("Cleaning up registry key %ws ...\n", szConnKey ));
    }
#endif
        err = RegOpenCurrentUser( KEY_ALL_ACCESS, &hkey );

        if ( err == ERROR_SUCCESS )
        {
            err = RegOpenKeyExW( hkey, szConnKey, 0, KEY_ALL_ACCESS, &hSubKey );
            if ( err == ERROR_SUCCESS )
            {
                WCHAR szKeyName[256];
                DWORD dwIndex = 0;
                DWORD dwSize;
                FILETIME keyModTime;
                HKEY hConnKey;
                WCHAR szProvider[64];

                dwSize = 256;
                err = RegEnumKeyEx(hSubKey, dwIndex,
                    szKeyName, &dwSize, NULL, NULL, NULL, &keyModTime);
                while (!err)
                {
                    dwIndex++;
                    err = RegOpenKeyExW( hSubKey, szKeyName, 
                        0, KEY_ALL_ACCESS, &hConnKey);
                    if (err)
                        break;
                    dwSize = 64 * sizeof(WCHAR);
                    err = RegQueryValueExW(hConnKey, L"Provider", NULL,
                        NULL, (LPBYTE) szProvider, &dwSize);
                    if (err)
                        szProvider[0] = 0;

                    if (lstrcmpi(szProvider, L"nwprovau.dll") == 0)
                    {
                        RegDeleteValueW( hConnKey, L"Provider" );
                        RegDeleteValueW( hConnKey, L"Server" );
                        RegCloseKey(hConnKey);
                        err = RegDeleteKeyW(hSubKey, szKeyName);
                        if (err == ERROR_SUCCESS)
                            bNeedAddPrinterConnCleanup = FALSE;
#if DBG
                        IF_DEBUG(PRINT)
                        {
                            if (err == ERROR_SUCCESS)
                                KdPrint(("    Deleted %ws\n", szKeyName));
                        }
#endif
                    }
                    else
                        RegCloseKey(hConnKey);
                    dwSize = 256;
                    err = RegEnumKeyEx(hSubKey, dwIndex,
                        szKeyName, &dwSize, NULL, NULL, NULL,  &keyModTime);
                }
                RegCloseKey( hSubKey );
            }
            RegCloseKey( hkey );
        }
    }

    RpcTryExcept
    {
        err = NwrClosePrinter( (LPNWWKSTA_PRINTER_CONTEXT) &hPrinter );
    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;

}



BOOL
GetPrinter(
    HANDLE  hPrinter,
    DWORD   dwLevel,
    LPBYTE  pbPrinter,
    DWORD   cbBuf,
    LPDWORD pcbNeeded
)
/*++

Routine Description:

    The routine retrieves information about the given printer.

Arguments:

    hPrinter  -  Handle of the printer
    dwLevel   -  Specifies the level of the structure to which pbPrinter points.
    pbPrinter -  Points to a buffer that receives the PRINTER_INFO object.
    cbBuf     -  Size, in bytes of the array pbPrinter points to.
    pcbNeeded -  Points to a value which specifies the number of bytes copied
                 if the function succeeds or the number of bytes required if
                 cbBuf was too small.

Return Value:

    TRUE if the function succeeds and FALSE otherwise.  GetLastError() can be
    used to retrieve extended error information.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(( "NWSPL [GetPrinter] Level = %d\n", dwLevel ));
#endif

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }
    else if ( ( dwLevel != 1 ) && ( dwLevel != 2 ) && (dwLevel != 3 ))
    {
        SetLastError( ERROR_INVALID_LEVEL );
        return FALSE;
    }

    RpcTryExcept
    {
        err = NwrGetPrinter( (NWWKSTA_PRINTER_CONTEXT) hPrinter,
                             dwLevel,
                             pbPrinter,
                             cbBuf,
                             pcbNeeded );

        if ( !err )
        {
            if ( dwLevel == 1 )
                MarshallUpStructure( pbPrinter, PrinterInfo1Offsets, pbPrinter);
            else
                MarshallUpStructure( pbPrinter, PrinterInfo2Offsets, pbPrinter);
        }

    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;
}



BOOL
SetPrinter(
    HANDLE  hPrinter,
    DWORD   dwLevel,
    LPBYTE  pbPrinter,
    DWORD   dwCommand
)
/*++

Routine Description:

    The routine sets the specified by pausing printing, resuming printing, or
    clearing all print jobs.

Arguments:

    hPrinter  -  Handle of the printer
    dwLevel   -  Specifies the level of the structure to which pbPrinter points.
    pbPrinter -  Points to a buffer that supplies the PRINTER_INFO object.
    dwCommand -  Specifies the new printer state.

Return Value:

    TRUE if the function succeeds and FALSE otherwise.  GetLastError() can be
    used to retrieve extended error information.

--*/
{
    DWORD err = NO_ERROR;

    UNREFERENCED_PARAMETER( pbPrinter );

#if DBG
    IF_DEBUG(PRINT)
    {
        KdPrint(( "NWSPL [SetPrinter] Level = %d Command = %d\n",
                  dwLevel, dwCommand ));
    }
#endif

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }

    switch ( dwLevel )
    {
        case 0:
        case 1:
        case 2:
        case 3:
            break;

        default:
            SetLastError( ERROR_INVALID_LEVEL );
            return FALSE;
    }

    RpcTryExcept
    {
        err = NwrSetPrinter( (NWWKSTA_PRINTER_CONTEXT) hPrinter,
                             dwCommand );

    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;
}



BOOL
EnumPrintersW(
    DWORD   dwFlags,
    LPWSTR  pszName,
    DWORD   dwLevel,
    LPBYTE  pbPrinter,
    DWORD   cbBuf,
    LPDWORD pcbNeeded,
    LPDWORD pcReturned
)
/*++

Routine Description:

    This routine enumerates the available providers, servers, printers
    depending on the given pszName.

Arguments:

    dwFlags    -  Printer type requested
    pszName    -  The name of the container object
    dwLevel    -  The structure level requested
    pbPrinter  -  Points to the array to receive the PRINTER_INFO objects
    cbBuf      -  Size, in bytes of pbPrinter
    pcbNeeded  -  Count of bytes needed
    pcReturned -  Count of PRINTER_INFO objects

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD  err = NO_ERROR;

#if DBG
    IF_DEBUG(PRINT)
    {
        KdPrint(("NWSPL [EnumPrinters] Flags = %d Level = %d",dwFlags,dwLevel));
        if ( pszName )
            KdPrint((" PrinterName = %ws\n", pszName ));
        else
            KdPrint(("\n"));
    }
#endif

    if ( (dwLevel != 1) && (dwLevel != 2) )
    {
        SetLastError( ERROR_INVALID_NAME );  // should be level, but winspool
                                             // is silly.
        return FALSE;
    }
    else if ( !pcbNeeded || !pcReturned )
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    RpcTryExcept
    {
        *pcReturned = 0;
        *pcbNeeded = 0;

        if (  ( dwFlags & PRINTER_ENUM_NAME )
           && ( dwLevel == 1 )
           )
        {
            err = NwrEnumPrinters( NULL,
                                   pszName,
                                   pbPrinter,
                                   cbBuf,
                                   pcbNeeded,
                                   pcReturned );

            if ( !err )
            {
                DWORD i;
                for ( i = 0; i < *pcReturned; i++ )
                     MarshallUpStructure( pbPrinter + i*sizeof(PRINTER_INFO_1W),
                                          PrinterInfo1Offsets,
                                          pbPrinter );
            }
        }
        else
        {
            err = ERROR_INVALID_NAME;
        }
    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_NAME;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;
}


//
//  Handle structure
//  This structure was copied from \nw\svcdlls\nwwks\server\spool.c
//           to fix NT bug # 366632.
//
typedef struct _NWSPOOL {
    DWORD      nSignature;             // Signature
    DWORD      errOpenPrinter;         // OpenPrinter API will always return
                                       // success on known printers. This will
                                       // contain the error that we get
                                       // if something went wrong in the API.
    PVOID      pPrinter;               // Points to the corresponding printer
    HANDLE     hServer;                // Opened handle to the server
    struct _NWSPOOL  *pNextSpool;      // Points to the next handle
    DWORD      nStatus;                // Status
    DWORD      nJobNumber;             // StartDocPrinter/AddJob: Job Number
    HANDLE     hChangeEvent;           // WaitForPrinterChange: event to wait on
    DWORD      nWaitFlags;             // WaitForPrinterChange: flags to wait on
    DWORD      nChangeFlags;           // Changes that occurred to the printer
} NWSPOOL, *PNWSPOOL;



DWORD
StartDocPrinter(
    HANDLE  hPrinter,
    DWORD   dwLevel,
    LPBYTE  lpbDocInfo
)
/*++

Routine Description:

    This routine informs the print spooler that a document is to be spooled
    for printing.

Arguments:

    hPrinter   -  Handle of the printer
    dwLevel    -  Level of the structure pointed to by lpbDocInfo. Must be 1.
    lpbDocInfo -  Points to the DOC_INFO_1 object

Return Value:

    TRUE if the function succeeds, FALSE otherwise. The extended error
    can be retrieved through GetLastError().

--*/
{
    DWORD err;
    DOC_INFO_1 *pDocInfo1 = (DOC_INFO_1 *) lpbDocInfo;
    LPWSTR pszUser = NULL;

    DWORD PrintOption = NW_PRINT_OPTION_DEFAULT;
    LPWSTR pszPreferredSrv = NULL;  

#if DBG
    IF_DEBUG(PRINT)
    {
        KdPrint(( "NWSPL [StartDocPrinter] " ));
        if ( pDocInfo1 )
        {
            if ( pDocInfo1->pDocName )
                KdPrint(("Document %ws", pDocInfo1->pDocName ));
            if ( pDocInfo1->pOutputFile )
                KdPrint(("OutputFile %ws", pDocInfo1->pOutputFile ));
            if ( pDocInfo1->pDatatype )
                KdPrint(("Datatype %ws", pDocInfo1->pDatatype ));
        }
        KdPrint(("\n"));
    }
#endif

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }
    else if ( dwLevel != 1 )
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    // ignore the error, just use default value
    NwpGetUserInfo( &pszUser );
    NwQueryInfo( &PrintOption, &pszPreferredSrv );  
    if (pszPreferredSrv) {
        LocalFree( pszPreferredSrv );
    }
    RpcTryExcept
    {
        err = NwrStartDocPrinter( (NWWKSTA_PRINTER_CONTEXT) hPrinter,
                                  pDocInfo1? pDocInfo1->pDocName : NULL,
                                  pszUser, PrintOption);

    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    LocalFree( pszUser );

    if ( err )
        SetLastError( err );
    //
    // Can't do this, seems to break GSWN printing on multi-homed machines
    // Commenting out code change that tries to return the job id.
    //
    // else
    //    return ((PNWSPOOL) hPrinter)->nJobNumber;

    return err == NO_ERROR;
}



BOOL
WritePrinter(
    HANDLE  hPrinter,
    LPVOID  pBuf,
    DWORD   cbBuf,
    LPDWORD pcbWritten
)
/*++

Routine Description:

    This routine informs the print spooler that the specified data should be
    written to the given printer.

Arguments:

    hPrinter   -  Handle of the printer object
    pBuf       -  Address of array that contains printer data
    cbBuf      -  Size, in bytes of pBuf
    pcbWritten -  Receives the number of bytes actually written to the printer

Return Value:

    TRUE if it succeeds, FALSE otherwise. Use GetLastError() to get extended
    error.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(( "NWSPL [WritePrinter]\n"));

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }
#endif

    RpcTryExcept
    {
        err = NwrWritePrinter( (NWWKSTA_PRINTER_CONTEXT) hPrinter,
                               pBuf,
                               cbBuf,
                               pcbWritten );
    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;
}



BOOL
AbortPrinter(
    HANDLE  hPrinter
)
/*++

Routine Description:

    This routine deletes a printer's spool file if the printer is configured
    for spooling.

Arguments:

    hPrinter - Handle of the printer object

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(( "NWSPL [AbortPrinter]\n"));

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }
#endif

    RpcTryExcept
    {
        err = NwrAbortPrinter( (NWWKSTA_PRINTER_CONTEXT) hPrinter );
    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;

}



BOOL
EndDocPrinter(
    HANDLE   hPrinter
)
/*++

Routine Description:

    This routine ends the print job for the given printer.

Arguments:

    hPrinter -  Handle of the printer object

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(( "NWSPL [EndDocPrinter]\n"));
#endif

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }

    RpcTryExcept
    {
        err = NwrEndDocPrinter( (NWWKSTA_PRINTER_CONTEXT) hPrinter );
    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;
}



BOOL
GetJob(
    HANDLE   hPrinter,
    DWORD    dwJobId,
    DWORD    dwLevel,
    LPBYTE   pbJob,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded
)
/*++

Routine Description:

    This routine retrieves print-job data for the given printer.

Arguments:

    hPrinter  -  Handle of the printer
    dwJobId   -  Job identifition number
    dwLevel   -  Data structure level of pbJob
    pbJob     -  Address of data-structure array
    cbBuf     -  Count of bytes in array
    pcbNeeded -  Count of bytes retrieved or required

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [GetJob] JobId = %d Level = %d\n", dwJobId, dwLevel));
#endif

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }
    else if (( dwLevel != 1 ) && ( dwLevel != 2 ))
    {
        SetLastError( ERROR_INVALID_LEVEL );
        return FALSE;
    }

    RpcTryExcept
    {
        err = NwrGetJob( (NWWKSTA_PRINTER_CONTEXT) hPrinter,
                         dwJobId,
                         dwLevel,
                         pbJob,
                         cbBuf,
                         pcbNeeded );

        if ( !err )
        {
            if ( dwLevel == 1 )
                MarshallUpStructure( pbJob, JobInfo1Offsets, pbJob );
            else
                MarshallUpStructure( pbJob, JobInfo2Offsets, pbJob );
        }
    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;

}



BOOL
EnumJobs(
    HANDLE  hPrinter,
    DWORD   dwFirstJob,
    DWORD   dwNoJobs,
    DWORD   dwLevel,
    LPBYTE  pbJob,
    DWORD   cbBuf,
    LPDWORD pcbNeeded,
    LPDWORD pcReturned
)
/*++

Routine Description:

    This routine initializes the array of JOB_INFO_1 or JOB_INFO_2 structures
    with data describing the specified print jobs for the given printer.

Arguments:

    hPrinter    -  Handle of the printer
    dwFirstJob  -  Location of first job in the printer
    dwNoJobs    -  Number of jobs to enumerate
    dwLevel     -  Data structure level
    pbJob       -  Address of structure array
    cbBuf       -  Size of pbJob, in bytes
    pcbNeeded   -  Receives the number of bytes copied or required
    pcReturned  -  Receives the number of jobs copied

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [EnumJobs] Level = %d FirstJob = %d NoJobs = %d\n",
                 dwLevel, dwFirstJob, dwNoJobs));
#endif

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }
    else if ( ( dwLevel != 1 ) && ( dwLevel != 2 ) )
    {
        SetLastError( ERROR_INVALID_LEVEL );
        return FALSE;
    }

    RpcTryExcept
    {
        *pcReturned = 0;
        *pcbNeeded = 0;

        err = NwrEnumJobs( (NWWKSTA_PRINTER_CONTEXT) hPrinter,
                           dwFirstJob,
                           dwNoJobs,
                           dwLevel,
                           pbJob,
                           cbBuf,
                           pcbNeeded,
                           pcReturned );

        if ( !err )
        {
            DWORD i;
            DWORD cbStruct;
            DWORD_PTR *pOffsets;

            if ( dwLevel == 1 )
            {
                cbStruct = sizeof( JOB_INFO_1W );
                pOffsets = JobInfo1Offsets;
            }
            else  // dwLevel == 2
            {
                cbStruct = sizeof( JOB_INFO_2W );
                pOffsets = JobInfo2Offsets;
            }

            for ( i = 0; i < *pcReturned; i++ )
                 MarshallUpStructure( pbJob + i * cbStruct, pOffsets, pbJob );
        }

    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;

}



BOOL
SetJob(
    HANDLE  hPrinter,
    DWORD   dwJobId,
    DWORD   dwLevel,
    LPBYTE  pbJob,
    DWORD   dwCommand
)
/*++

Routine Description:

    This routine pauses, cancels, resumes, restarts the specified print job
    in the given printer. The function can also be used to set print job
    parameters such as job position, and so on.

Arguments:

    hPrinter  -  Handle of the printer
    dwJobId   -  Job indentification number
    dwLevel   -  Data structure level
    pbJob     -  Address of data structure
    dwCommand -  Specify the operation to be performed

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
    {
        KdPrint(("NWSPL [SetJob] Level = %d JobId = %d Command = %d\n",
                 dwLevel, dwJobId, dwCommand));
    }
#endif

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }
    else if ( ( dwLevel != 0 ) && ( dwLevel != 1 ) && ( dwLevel != 2 ) )
    {
        SetLastError( ERROR_INVALID_LEVEL );
        return FALSE;
    }
    else if ( ( dwLevel == 0 ) && ( pbJob != NULL ) )
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    RpcTryExcept
    {
        NW_JOB_INFO NwJobInfo;

        if ( dwLevel == 1 )
        {
            NwJobInfo.nPosition = ((LPJOB_INFO_1W) pbJob)->Position;
            NwJobInfo.pUserName = ((LPJOB_INFO_1W) pbJob)->pUserName;
            NwJobInfo.pDocument = ((LPJOB_INFO_1W) pbJob)->pDocument;
        }
        else if ( dwLevel == 2 )
        {
            NwJobInfo.nPosition = ((LPJOB_INFO_2W) pbJob)->Position;
            NwJobInfo.pUserName = ((LPJOB_INFO_2W) pbJob)->pUserName;
            NwJobInfo.pDocument = ((LPJOB_INFO_2W) pbJob)->pDocument;
        }

        err = NwrSetJob( (NWWKSTA_PRINTER_CONTEXT) hPrinter,
                         dwJobId,
                         dwLevel,
                         dwLevel == 0 ? NULL : &NwJobInfo,
                         dwCommand );

    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;

}



BOOL
AddJob(
    HANDLE  hPrinter,
    DWORD   dwLevel,
    LPBYTE  pbAddJob,
    DWORD   cbBuf,
    LPDWORD pcbNeeded
)
/*++

Routine Description:

    This routine returns a full path and filename of a file that can be used
    to store a print job.

Arguments:

    hPrinter  -  Handle of the printer
    dwLevel   -  Data structure level
    pbAddJob  -  Points to a ADD_INFO_1 structure
    cbBuf     -  Size of pbAddJob, in bytes
    pcbNeeded -  Receives the bytes copied or required

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD err;

    ADDJOB_INFO_1W TempBuffer;
    PADDJOB_INFO_1W OutputBuffer;
    DWORD OutputBufferSize;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(( "NWSPL [AddJob]\n"));
#endif

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }
    else if ( dwLevel != 1 )
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    //
    // The output buffer size must be at least the size of the fixed
    // portion of the structure marshalled by RPC or RPC will not
    // call the server-side to get the pcbNeeded.  Use our own temporary
    // buffer to force RPC to call the server-side if output buffer
    // specified by the caller is too small.
    //
    if (cbBuf < sizeof(ADDJOB_INFO_1W)) {
        OutputBuffer = &TempBuffer;
        OutputBufferSize = sizeof(ADDJOB_INFO_1W);
    }
    else {
        OutputBuffer = (LPADDJOB_INFO_1W) pbAddJob;
        OutputBufferSize = cbBuf;
    }

    RpcTryExcept
    {
        err = NwrAddJob( (NWWKSTA_PRINTER_CONTEXT) hPrinter,
                         OutputBuffer,
                         OutputBufferSize,
                         pcbNeeded );

    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;
}



BOOL
ScheduleJob(
    HANDLE  hPrinter,
    DWORD   dwJobId
)
/*++

Routine Description:

    This routine informs the print spooler that the specified job can be
    scheduled for spooling.

Arguments:

    hPrinter -  Handle of the printer
    dwJobId  -  Job number that can be scheduled

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(( "NWSPL [ScheduleJob] JobId = %d\n", dwJobId ));
#endif

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return FALSE;
    }

    RpcTryExcept
    {
        err = NwrScheduleJob( (NWWKSTA_PRINTER_CONTEXT) hPrinter,
                              dwJobId );

    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;
}



DWORD
WaitForPrinterChange(
    HANDLE  hPrinter,
    DWORD   dwFlags
)
/*++

Routine Description:

    This function returns when one or more requested changes occur on a
    print server or if the function times out.

Arguments:

    hPrinter -  Handle of the printer to wait on
    dwFlags  -  A bitmask that specifies the changes that the application
                wishes to be notified of.

Return Value:

    Return a bitmask that indicates the changes that occurred.

--*/
{
    DWORD err;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [WaitForPrinterChange] Flags = %d\n", dwFlags));
#endif

    if ( hPrinter == &handleDummy )
    {
        SetLastError( ERROR_NO_NETWORK );
        return 0;
    }

    RpcTryExcept
    {
        err = NwrWaitForPrinterChange( (NWWKSTA_PRINTER_CONTEXT) hPrinter,
                                       &dwFlags );

    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept

    if ( err )
    {
        SetLastError( err );
        return 0;
    }

    return dwFlags;
}



BOOL
EnumPortsW(
    LPWSTR   pszName,
    DWORD    dwLevel,
    LPBYTE   pbPort,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded,
    LPDWORD  pcReturned
)
/*++

Routine Description:

    This function enumerates the ports available for printing on a
    specified server.

Arguments:

    pszName - Name of the server to enumerate on
    dwLevel - Structure level
    pbPort  - Address of array to receive the port information
    cbBuf   - Size, in bytes, of pbPort
    pcbNeeded  - Address to store the number of bytes needed or copied
    pcReturned - Address to store the number of entries copied

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD err = NO_ERROR;
    DWORD cb = 0;
    PNWPORT pNwPort;
    LPPORT_INFO_1W pPortInfo1;
    LPBYTE pEnd = pbPort + cbBuf;
    LPBYTE pFixedDataEnd = pbPort;
    BOOL FitInBuffer;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [EnumPorts]\n"));
#endif

    if ( dwLevel != 1 )
    {
        SetLastError( ERROR_INVALID_NAME );
        return FALSE;
    }
    else if ( !IsLocalMachine( pszName ) )
    {
        SetLastError( ERROR_INVALID_NAME );
        return FALSE;
    }

    EnterCriticalSection( &NwSplSem );

    pNwPort = pNwFirstPort;
    while ( pNwPort )
    {
        cb += sizeof(PORT_INFO_1W) + ( wcslen( pNwPort->pName)+1)*sizeof(WCHAR);
        pNwPort = pNwPort->pNext;
    }

    *pcbNeeded = cb;
    *pcReturned = 0;

    if ( cb <= cbBuf )
    {
        pEnd = pbPort + cbBuf;

        pNwPort = pNwFirstPort;
        while ( pNwPort )
        {
            pPortInfo1 = (LPPORT_INFO_1W) pFixedDataEnd;
            pFixedDataEnd += sizeof( PORT_INFO_1W );

            FitInBuffer = NwlibCopyStringToBuffer( pNwPort->pName,
                                                   wcslen( pNwPort->pName),
                                                   (LPCWSTR) pFixedDataEnd,
                                                   (LPWSTR *) &pEnd,
                                                   &pPortInfo1->pName );
            ASSERT( FitInBuffer );

            pNwPort = pNwPort->pNext;
            (*pcReturned)++;
        }
    }
    else
    {
        err = ERROR_INSUFFICIENT_BUFFER;
    }

    LeaveCriticalSection( &NwSplSem );

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;
}



BOOL
DeletePortW(
    LPWSTR  pszName,
    HWND    hWnd,
    LPWSTR  pszPortName
)
/*++

Routine Description:

    This routine deletes the port given on the server. A dialog can
    be displayed if needed.

Arguments:

    pszName - Name of the server for which the port should be deleted
    hWnd    - Parent window
    pszPortName - The name of the port to delete

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD err;
    BOOL fPortDeleted;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [DeletePort]\n"));
#endif

    if ( !IsLocalMachine( pszName ) )
    {
        SetLastError( ERROR_NOT_SUPPORTED );
        return FALSE;
    }

    fPortDeleted = DeletePortEntry( pszPortName );

    if ( fPortDeleted )
    {
        err = DeleteRegistryEntry( pszPortName );
    }
    else
    {
        err = ERROR_UNKNOWN_PORT;
    }

    if ( err )
        SetLastError( err );

    return err == NO_ERROR;

}



BOOL
ConfigurePortW(
    LPWSTR  pszName,
    HWND    hWnd,
    LPWSTR  pszPortName
)
/*++

Routine Description:

    This routine displays the port configuration dialog box
    for the given port on the given server.

Arguments:

    pszName - Name of the server on which the given port exist
    hWnd    - Parent window
    pszPortName - The name of the port to be configured

Return Value:

    TRUE if the function succeeds, FALSE otherwise.

--*/
{
    DWORD nCurrentThreadId;
    DWORD nWindowThreadId;
    WCHAR szCaption[MAX_PATH];
    WCHAR szMessage[MAX_PATH];

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [ConfigurePort] PortName = %ws\n", pszPortName));
#endif

    if ( !IsLocalMachine( pszName ) )
    {
        SetLastError( ERROR_NOT_SUPPORTED );
        return FALSE;
    }
    else if ( !PortKnown( pszPortName ) )
    {
        SetLastError( ERROR_UNKNOWN_PORT );
        return FALSE;
    }

    nCurrentThreadId = GetCurrentThreadId();
    nWindowThreadId  = GetWindowThreadProcessId( hWnd, NULL );

    if ( !AttachThreadInput( nCurrentThreadId, nWindowThreadId, TRUE ))
        KdPrint(("[NWSPL] AttachThreadInput failed with %d.\n",GetLastError()));

    if ( LoadStringW( hmodNW,
                      IDS_NETWARE_PRINT_CAPTION,
                      szCaption,
                      sizeof( szCaption ) / sizeof( WCHAR )))
    {
        if ( LoadStringW( hmodNW,
                          IDS_NOTHING_TO_CONFIGURE,
                          szMessage,
                          sizeof( szMessage ) / sizeof( WCHAR )))
        {
            MessageBox( hWnd, szMessage, szCaption,
                        MB_OK | MB_ICONINFORMATION );
        }
        else
        {
            KdPrint(("[NWSPL] LoadString failed with %d.\n",GetLastError()));
        }
    }
    else
    {
        KdPrint(("[NWSPL] LoadString failed with %d.\n",GetLastError()));
    }

    if ( !AttachThreadInput( nCurrentThreadId, nWindowThreadId, FALSE ))
        KdPrint(("[NWSPL] DetachThreadInput failed with %d.\n",GetLastError()));

    return TRUE;
}

BOOL
AddPrinterConnectionW(
    LPWSTR  pszPrinterName
)
{
    DWORD err=0;
    LPHANDLE           phPrinter = NULL;
    LPPRINTER_DEFAULTS pDefault = NULL;
    LPTSTR pszDriverPrefix = L"PnP Driver:";
    HANDLE             hTreeConn = NULL;
    BYTE                RawResponse[4096];
    DWORD               RawResponseSize = sizeof(RawResponse);
    LPWSTR pszDescription = NULL;
    LPWSTR pszCurrPtr = NULL;
    BOOL bValidString = FALSE;

#if DBG
    IF_DEBUG(PRINT)
    {
        KdPrint(("NWSPL [AddPrinterConnection] PrinterName = %ws\n",
                pszPrinterName));
    }
#endif

    if ( !pszPrinterName )
    {
        SetLastError( ERROR_INVALID_NAME );
        return FALSE;
    }

    // check registry for permission to add printers
    if ( !bEnableAddPrinterConnection )
    {
        SetLastError( ERROR_ACCESS_DENIED );
        return FALSE;
    }
    else
    {
        // Read first value of Description attribute for "PnP Driver:Driver Name"
        DWORD               dwOid;
        NTSTATUS            ntstatus = STATUS_SUCCESS;
        LPBYTE              pObjectClass = RawResponse;
        DWORD               iterHandle = (DWORD) -1;
        UNICODE_STRING      uAttrName;
        PNDS_RESPONSE_READ_ATTRIBUTE pReadAttrResponse = (PNDS_RESPONSE_READ_ATTRIBUTE) RawResponse;
        PNDS_ATTRIBUTE pNdsAttribute = NULL;

        err = NwOpenAndGetTreeInfo( pszPrinterName,
                                    &hTreeConn,
                                    &dwOid );

        if ( err != NO_ERROR )
        {
            goto NDSExit;
        }

        RtlInitUnicodeString( &uAttrName, L"Description");

        ntstatus = NwNdsReadAttribute( hTreeConn,
                                       dwOid,
                                       &iterHandle,
                                       &uAttrName,
                                       RawResponse,
                                       sizeof(RawResponse));

        if (  !NT_SUCCESS( ntstatus )
           || ( pReadAttrResponse->CompletionCode != 0 )
           || ( pReadAttrResponse->NumAttributes == 0 )
           )
        {
            // we don't need to set the error since this attribute might be empty and
            // we might get an error indicating this.
            goto NDSExit;
        }

        pNdsAttribute = (PNDS_ATTRIBUTE)((DWORD_PTR) RawResponse+sizeof(NDS_RESPONSE_READ_ATTRIBUTE));

        pszDescription = (LPWSTR) ((DWORD_PTR) pNdsAttribute + 3*sizeof(DWORD)
                                      + pNdsAttribute->AttribNameLength + sizeof(DWORD));
        // NOTE: we only look at the first value
    }

NDSExit:

    if ( hTreeConn )
        CloseHandle( hTreeConn );

    if ( (pszDescription == NULL) || (*pszDescription == 0) )
    {
        SetLastError( ERROR_ACCESS_DENIED );
        return FALSE;
    }

    // make sure Description is NULL terminated
    pszCurrPtr = pszDescription;
    while (pszCurrPtr < (WCHAR *)(RawResponse + RawResponseSize))
    {
        if (*pszCurrPtr == 0)
        {
            bValidString = TRUE;
            break;
        }
        pszCurrPtr++;
    }
    if (!bValidString)
    {
        SetLastError( ERROR_ACCESS_DENIED );
        return FALSE;
    }

#if DBG
    IF_DEBUG(PRINT)
    {
        KdPrint(("NWSPL [AddPrinterConnection] description=%ws\n", pszDescription));
    }
#endif
    if ( _wcsnicmp ( pszDriverPrefix, pszDescription, wcslen(pszDriverPrefix)) != 0 )
    {
        SetLastError( ERROR_ACCESS_DENIED );
        return FALSE;
    }

    pszDescription += wcslen(pszDriverPrefix);
    if ( bRestrictToInboxDrivers )
    {
        LPWSTR pszSeparator = wcschr( pszDescription, L'@' );
        if ( pszSeparator )
            *pszSeparator = 0;
    }


    RpcTryExcept
    {
        err = NwrAddPrinterConnection(NULL, pszPrinterName, pszDescription);
    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
        {
            err = ERROR_INVALID_NAME;
        }
        else
        {
            err = NwpMapRpcError( code );
        }
    }
    RpcEndExcept

    if ( err )
    {
        SetLastError( err );
#if DBG
        IF_DEBUG(PRINT)
            KdPrint(("NWSPL [AddPrinterConnection] err = %d\n", err));
#endif
        return FALSE;
    }
    bNeedAddPrinterConnCleanup = TRUE;

    return TRUE;
}

//------------------------------------------------------------------
//
// Print Provider Functions not supported by NetWare provider
//
//------------------------------------------------------------------

BOOL
EnumMonitorsW(
    LPWSTR   pszName,
    DWORD    dwLevel,
    LPBYTE   pbMonitor,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded,
    LPDWORD  pcReturned
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [EnumMonitors]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}


BOOL
AddPortW(
    LPWSTR  pszName,
    HWND    hWnd,
    LPWSTR  pszMonitorName
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [AddPort]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}


HANDLE
AddPrinterW(
    LPWSTR  pszName,
    DWORD   dwLevel,
    LPBYTE  pbPrinter
)

// Creates a print queue on the netware server and returns a handle to it
{
#ifdef NOT_USED

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [AddPrinterW]\n"));
#endif

    SetLastError(ERROR_NOT_SUPPORTED);
    return FALSE;
#else

   LPTSTR     pszPrinterName = NULL;
   LPTSTR     pszPServer = NULL;
   LPTSTR     pszQueue  =  NULL;
   HANDLE     hPrinter = NULL;
   DWORD      err;
   PPRINTER_INFO_2 pPrinterInfo;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [AddPrinterW]\n"));
#endif

   pPrinterInfo = (PPRINTER_INFO_2)pbPrinter;  

   
   if (dwLevel != 2)
      {
        err = ERROR_INVALID_PARAMETER;
        goto ErrorExit;
      }


   if (!(pszPrinterName = (LPTSTR)LocalAlloc(LPTR, (wcslen(((PRINTER_INFO_2 *)pbPrinter)->pPrinterName)+1)* sizeof(WCHAR))))
      {
         err = ERROR_NOT_ENOUGH_MEMORY; 
         goto ErrorExit;
      }

      wcscpy(pszPrinterName,pPrinterInfo->pPrinterName);

   // PrinterName is the name represented as \\server\share
  //The pszPServer parameter could have multiple fields separated by semicolons

   
       if (  ( !ValidateUNCName( pszPrinterName ) )
       || ( (pszQueue = wcschr( pszPrinterName + 2, L'\\')) == NULL )
       || ( pszQueue == (pszPrinterName + 2) )
       || ( *(pszQueue + 1) == L'\0' )
       )
          {
             err =  ERROR_INVALID_NAME;
             goto ErrorExit;
          }
      


#if DBG
    IF_DEBUG(PRINT)
        KdPrint(( "NWSPL [AddPrinter] Name = %ws\n",pszPServer));
#endif

   if ( pszPrinterName == NULL  ) 
//PrinterName is a mandatory field but not the list of PServers
    {
#if DBG
            IF_DEBUG(PRINT)
            KdPrint(( "NWSPL [AddPrinter] Printername  not supplied\n" ));
#endif

        SetLastError( ERROR_INVALID_NAME );
        goto ErrorExit;
    }

   //Check to see if there is a port of the same name
   // If so, abort the addprinter operation. 
   // This code was commented earlier 

      if (PortExists(pszPrinterName, &err ) && !err )
         {
#if DBG
            IF_DEBUG(PRINT)
            KdPrint(( "NWSPL [AddPrinter], = %ws; Port exists with same name\n", pszPrinterName ));
#endif
            SetLastError(ERROR_ALREADY_ASSIGNED);
            goto ErrorExit;
         }
         

   // Put all the relevant information into the PRINTER_INFO_2 structure

    RpcTryExcept
    {
       err = NwrAddPrinter   ( NULL, 
                               (LPPRINTER_INFO_2W) pPrinterInfo,
                               (LPNWWKSTA_PRINTER_CONTEXT) &hPrinter
                             );
       if (!err)
       {
#if DBG
         IF_DEBUG(PRINT)
          KdPrint(( "NWSPL [AddPrinter] Name = %ws\n", pszPrinterName ));
#endif
    goto ErrorExit;
       }
    }
   RpcExcept(1)
   {
      DWORD code = RpcExceptionCode();
      err = NwpMapRpcError( code );
      goto ErrorExit;
   }
   RpcEndExcept
   if ( !pszPrinterName) 
      (void)  LocalFree((HLOCAL)pszPrinterName);
   
   return hPrinter;

ErrorExit:
    if ( !pszPrinterName) 
    (void)  LocalFree((HLOCAL)pszPrinterName);

         SetLastError( err);
    return (HANDLE)0x0;      
 
#endif // #ifdef NOT_USED
}

BOOL
DeletePrinter(
    HANDLE  hPrinter
)
{
#ifdef NOT_USED

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [DeletePrinter]\n"));
#endif

    SetLastError(ERROR_NOT_SUPPORTED);
    return FALSE;
#else
   LPWSTR pszPrinterName = NULL ; // Used to delete entry from registry
   DWORD err = NO_ERROR;
   DWORD DoesPortExist;

#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [DeletePrinter]\n"));
#endif

    pszPrinterName = (LPWSTR)LocalAlloc(LPTR,sizeof(WCHAR)*MAX_PATH);

   if(pszPrinterName == NULL)
      {
         err = ERROR_NOT_ENOUGH_MEMORY;
         return FALSE;
      }
   //
   // Just return success if the handle is a dummy one
   //
   if ( hPrinter == &handleDummy )
       {
#if DBG
          IF_DEBUG(PRINT)
          KdPrint(("NWSPL [DeletePrinter] Dummy handle \n"));
#endif
          SetLastError(ERROR_NO_NETWORK);
          return FALSE;
       }
    RpcTryExcept
    {  


        err = NwrDeletePrinter( NULL,
           // pszPrinterName,
                    (LPNWWKSTA_PRINTER_CONTEXT) &hPrinter );
        
    }
    RpcExcept(1)
    {
        DWORD code = RpcExceptionCode();

        if ( code == RPC_S_SERVER_UNAVAILABLE )
            err = ERROR_INVALID_HANDLE;
        else
            err = NwpMapRpcError( code );
    }
    RpcEndExcept
 
        if (!err && PortExists(pszPrinterName, &DoesPortExist) && DoesPortExist)
           {  
              
              if ( DeleteRegistryEntry (pszPrinterName))
                  (void) DeletePortEntry(pszPrinterName);
                  
           }
          
 
    if ( err )
        SetLastError( err );

    return err == NO_ERROR;

#endif // #ifdef NOT_USED
}


BOOL
DeletePrinterConnectionW(
    LPWSTR  pszName
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [DeletePrinterConnection]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}


BOOL
AddPrinterDriverW(
    LPWSTR  pszName,
    DWORD   dwLevel,
    LPBYTE  pbPrinter
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [AddPrinterDriver]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

BOOL
EnumPrinterDriversW(
    LPWSTR   pszName,
    LPWSTR   pszEnvironment,
    DWORD    dwLevel,
    LPBYTE   pbDriverInfo,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded,
    LPDWORD  pcReturned
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [EnumPrinterDrivers]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

BOOL
GetPrinterDriverW(
    HANDLE   hPrinter,
    LPWSTR   pszEnvironment,
    DWORD    dwLevel,
    LPBYTE   pbDriverInfo,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [GetPrinterDriver]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
GetPrinterDriverDirectoryW(
    LPWSTR   pszName,
    LPWSTR   pszEnvironment,
    DWORD    dwLevel,
    LPBYTE   pbDriverDirectory,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [GetPrinterDriverDirectory]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

BOOL
DeletePrinterDriverW(
    LPWSTR  pszName,
    LPWSTR  pszEnvironment,
    LPWSTR  pszDriverName
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [DeletePrinterDriver]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

BOOL
AddPrintProcessorW(
    LPWSTR  pszName,
    LPWSTR  pszEnvironment,
    LPWSTR  pszPathName,
    LPWSTR  pszPrintProcessorName
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [AddPrintProcessor]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

BOOL
EnumPrintProcessorsW(
    LPWSTR   pszName,
    LPWSTR   pszEnvironment,
    DWORD    dwLevel,
    LPBYTE   pbPrintProcessorInfo,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded,
    LPDWORD  pcReturned
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [EnumPrintProcessors]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

BOOL
EnumPrintProcessorDatatypesW(
    LPWSTR   pszName,
    LPWSTR   pszPrintProcessorName,
    DWORD    dwLevel,
    LPBYTE   pbDatatypes,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded,
    LPDWORD  pcReturned
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [EnumPrintProcessorDatatypes]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

BOOL
GetPrintProcessorDirectoryW(
    LPWSTR   pszName,
    LPWSTR   pszEnvironment,
    DWORD    dwLevel,
    LPBYTE   pbPrintProcessorDirectory,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [GetPrintProcessorDirectory]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

BOOL
StartPagePrinter(
    HANDLE  hPrinter
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [StartPagePrinter]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
EndPagePrinter(
    HANDLE  hPrinter
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [EndPagePrinter]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
ReadPrinter(
    HANDLE   hPrinter,
    LPVOID   pBuf,
    DWORD    cbBuf,
    LPDWORD  pcbRead
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [ReadPrinter]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

DWORD
GetPrinterDataW(
    HANDLE   hPrinter,
    LPWSTR   pszValueName,
    LPDWORD  pdwType,
    LPBYTE   pbData,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [GetPrinterData]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

DWORD
SetPrinterDataW(
    HANDLE  hPrinter,
    LPWSTR  pszValueName,
    DWORD   dwType,
    LPBYTE  pbData,
    DWORD   cbData
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [SetPrinterData]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
AddForm(
    HANDLE  hPrinter,
    DWORD   dwLevel,
    LPBYTE  pbForm
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [AddForm]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
DeleteFormW(
    HANDLE  hPrinter,
    LPWSTR  pszFormName
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [DeleteForm]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
GetFormW(
    HANDLE   hPrinter,
    LPWSTR   pszFormName,
    DWORD    dwLevel,
    LPBYTE   pbForm,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [GetForm]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
SetFormW(
    HANDLE  hPrinter,
    LPWSTR  pszFormName,
    DWORD   dwLevel,
    LPBYTE  pbForm
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [SetForm]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
EnumForms(
    HANDLE   hPrinter,
    DWORD    dwLevel,
    LPBYTE   pbForm,
    DWORD    cbBuf,
    LPDWORD  pcbNeeded,
    LPDWORD  pcReturned
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [EnumForms]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}


HANDLE
CreatePrinterIC(
    HANDLE     hPrinter,
    LPDEVMODE  pDevMode
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [CreatePrinterIC]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
PlayGdiScriptOnPrinterIC(
    HANDLE  hPrinterIC,
    LPBYTE  pbIn,
    DWORD   cbIn,
    LPBYTE  pbOut,
    DWORD   cbOut,
    DWORD   ul
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [PlayGdiScriptOnPrinterIC]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
DeletePrinterIC(
    HANDLE  hPrinterIC
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [DeletePrinterIC]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

DWORD
PrinterMessageBoxW(
    HANDLE  hPrinter,
    DWORD   dwError,
    HWND    hWnd,
    LPWSTR  pszText,
    LPWSTR  pszCaption,
    DWORD   dwType
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [PrinterMessageBox]\n"));
#endif

    SetLastError( ERROR_NOT_SUPPORTED );
    return FALSE;
}

BOOL
AddMonitorW(
    LPWSTR  pszName,
    DWORD   dwLevel,
    LPBYTE  pbMonitorInfo
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [AddMonitor]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

BOOL
DeleteMonitorW(
    LPWSTR  pszName,
    LPWSTR  pszEnvironment,
    LPWSTR  pszMonitorName
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [DeleteMonitor]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

BOOL
DeletePrintProcessorW(
    LPWSTR  pszName,
    LPWSTR  pszEnvironment,
    LPWSTR  pszPrintProcessorName
)
{
#if DBG
    IF_DEBUG(PRINT)
        KdPrint(("NWSPL [DeletePrintProcessor]\n"));
#endif

    SetLastError( ERROR_INVALID_NAME );
    return FALSE;
}

//------------------------------------------------------------------
//
// Print Provider Miscellaneous Functions
//
//------------------------------------------------------------------

VOID
NwpGetUserInfo(
    LPWSTR *ppszUser
)
/*++

Routine Description:

    Get the user information of the impersonating client. 

Arguments:

    ppszUser - A pointer to buffer to store the Unicode string if 
               the impersonated client's user name can be looked up
               successfully. If the conversion was unsuccessful, it points 
               to NULL.

Return Value:

    None.

--*/
{
    DWORD err;
    LPWSTR pszUserSid = NULL;
 //   LPWSTR pszLogonUserSid = NULL;    //Removed for Multi-user code merge
                                        //Terminal Server doesn't user this varible
                                        //There is no single "Logon User" in Terminal Server

    //
    // If any error occurs while trying to get the username, just
    // assume no user name and not gateway printing.
    //
    *ppszUser = NULL;

    if (  ((err = NwpGetThreadUserInfo( ppszUser, &pszUserSid )) == NO_ERROR)
//          && ((err = NwpGetLogonUserInfo( &pszLogonUserSid ))) == NO_ERROR) //Removed from Multi-user code merge
       ) {

#if DBG
            IF_DEBUG(PRINT)
            KdPrint(("NwpGetUserInfo: Thread User= %ws, Thread SID = %ws,\n",
                     *ppszUser, pszUserSid ));
#endif

//        } else {
//            if ( _wcsicmp( pszUserSid, pszLogonUserSid ) == 0 ) {
//            }

//#if DBG
//            IF_DEBUG(PRINT)
//            KdPrint(("NwpGetUserInfo: Thread User= %ws, Thread SID = %ws,\nCurrent SID = %ws\n",
//                     *ppszUser, pszUserSid, pszLogonUserSid ));
//#endif
//        }

        LocalFree( pszUserSid );
    }

}

#define SIZE_OF_TOKEN_INFORMATION   \
     sizeof( TOKEN_USER )               \
     + sizeof( SID )                    \
     + sizeof( ULONG ) * SID_MAX_SUB_AUTHORITIES

DWORD
NwpGetThreadUserInfo(
    LPWSTR  *ppszUser,
    LPWSTR  *ppszUserSid
)
/*++

Routine Description:

    Get the user name and user sid string of the impersonating client.

Arguments:

    ppszUser - A pointer to buffer to store the Unicode string 
               if the impersonated client's can be looked up. If the
               lookup was unsuccessful, it points to NULL.

    ppszUserSid - A pointer to buffer to store the string if the impersonated
               client's SID can be expanded successfully into unicode string.
               If the conversion was unsuccessful, it points to NULL.

Return Value:

    The error code.

--*/
{
    DWORD       err;
    HANDLE      TokenHandle;
    UCHAR       TokenInformation[ SIZE_OF_TOKEN_INFORMATION ];
    ULONG       ReturnLength;

    *ppszUser = NULL;
    *ppszUserSid = NULL;

    // We can use OpenThreadToken because this server thread
    // is impersonating a client

    if ( !OpenThreadToken( GetCurrentThread(),
                           TOKEN_READ,
                           TRUE,  /* Open as self */
                           &TokenHandle ))
    {
#if DBG
        IF_DEBUG(PRINT)
            KdPrint(("NwpGetThreadUserInfo: OpenThreadToken failed: Error %d\n",
                      GetLastError()));
#endif
        return(GetLastError());
    }

    // notice that we've allocated enough space for the
    // TokenInformation structure. so if we fail, we
    // return a NULL pointer indicating failure


    if ( !GetTokenInformation( TokenHandle,
                               TokenUser,
                               TokenInformation,
                               sizeof( TokenInformation ),
                               &ReturnLength ))
    {
#if DBG
        IF_DEBUG(PRINT)
            KdPrint(("NwpGetThreadUserInfo: GetTokenInformation failed: Error %d\n",
                      GetLastError()));
#endif
        return(GetLastError());
    }

    CloseHandle( TokenHandle );

    // convert the Sid (pointed to by pSid) to its
    // equivalent Unicode string representation.

    err = NwpGetUserNameFromSid( ((PTOKEN_USER)TokenInformation)->User.Sid,
                                  ppszUser );
    err = err? err : NwpConvertSid( ((PTOKEN_USER)TokenInformation)->User.Sid,
                                    ppszUserSid );

    if ( err )
    {
        if ( *ppszUser )
            LocalFree( *ppszUser );

        if ( *ppszUserSid )
            LocalFree( *ppszUserSid );
    }

    return err;
}

DWORD
NwpGetUserNameFromSid(
    PSID pUserSid,
    LPWSTR *ppszUserName
)
/*++

Routine Description:

    Lookup the user name given the user SID.

Arguments:

    pUserSid - Points to the user sid to be looked up

    ppszUserName - A pointer to buffer to store the string if the impersonated
               client's SID can be expanded successfully into unicode string.
               If the conversion was unsuccessful, it points to NULL.

Return Value:

    The error code.

--*/
{
    NTSTATUS ntstatus;

    LSA_HANDLE hlsa;
    OBJECT_ATTRIBUTES oa;
    SECURITY_QUALITY_OF_SERVICE sqos;
    PLSA_REFERENCED_DOMAIN_LIST plsardl = NULL;
    PLSA_TRANSLATED_NAME plsatn = NULL;

    sqos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
    sqos.ImpersonationLevel = SecurityImpersonation;
    sqos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
    sqos.EffectiveOnly = FALSE;
    InitializeObjectAttributes( &oa, NULL, 0L, NULL, NULL );
    oa.SecurityQualityOfService = &sqos;

    ntstatus = LsaOpenPolicy( NULL,
                              &oa,
                              POLICY_LOOKUP_NAMES,
                              &hlsa );

    if ( NT_SUCCESS( ntstatus ))
    {
        ntstatus = LsaLookupSids( hlsa,
                                  1,
                                  &pUserSid,
                                  &plsardl,
                                  &plsatn );

        if ( NT_SUCCESS( ntstatus ))
        {
            UNICODE_STRING *pUnicodeStr = &((*plsatn).Name);

            *ppszUserName = LocalAlloc( LMEM_ZEROINIT, 
                                        pUnicodeStr->Length+sizeof(WCHAR));

            if ( *ppszUserName != NULL )
            {
                memcpy( *ppszUserName, pUnicodeStr->Buffer, pUnicodeStr->Length );
            }
            else
            {
                ntstatus = STATUS_NO_MEMORY;
            }

            LsaFreeMemory( plsardl );
            LsaFreeMemory( plsatn );
        }
#if DBG
        else
        {
            KdPrint(("NwpGetUserNameFromSid: LsaLookupSids failed: Error = %d\n",
                    GetLastError()));
        }
#endif

        LsaClose( hlsa );
     }
#if DBG
     else
     {
        KdPrint(("NwpGetUserNameFromSid: LsaOpenPolicy failed: Error = %d\n",
                GetLastError()));
     }
#endif

    return RtlNtStatusToDosError( ntstatus );

}

DWORD
NwpGetLogonUserInfo(
    LPWSTR  *ppszUserSid
)
/*++

Routine Description:

    Get the logon user sid string from the registry.

Arguments:

    ppszUserSid - On return, this points to the current logon user
                  sid string. 

Return Value:

    The error code.

--*/
{
    DWORD err;
    HKEY WkstaKey;

    LPWSTR CurrentUser = NULL;

    //
    // Open HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services
    // \NWCWorkstation\Parameters to get the sid of the CurrentUser
    //
    err = RegOpenKeyExW(
              HKEY_LOCAL_MACHINE,
              NW_WORKSTATION_REGKEY,
              REG_OPTION_NON_VOLATILE,
              KEY_READ,
              &WkstaKey
              );

    if ( err == NO_ERROR) {

        //
        // Read the current user SID string so that we
        // know which key is the current user key to open.
        //
        err = NwReadRegValue(
                  WkstaKey,
                  NW_CURRENTUSER_VALUENAME,
                  &CurrentUser
                  );

        RegCloseKey( WkstaKey );

        if ( err == NO_ERROR) {
           *ppszUserSid = CurrentUser;
        }
    }

    return(err);
}


#define SIZE_OF_STATISTICS_TOKEN_INFORMATION    \
     sizeof( TOKEN_STATISTICS ) 

DWORD
ThreadIsInteractive(
    VOID
)
/*++

Routine Description:

    Determines if this is an "Interactive" logon thread

Arguments:

    none

Return Value:

    TRUE - Thread is interactive
    FALSE - Thread is not interactive

--*/
{
    HANDLE      TokenHandle;
    UCHAR       TokenInformation[ SIZE_OF_STATISTICS_TOKEN_INFORMATION ];
    WCHAR       LogonIdKeyName[NW_MAX_LOGON_ID_LEN];

    ULONG       ReturnLength;
    LUID        LogonId;
    LONG        RegError;
    HKEY        InteractiveLogonKey;
    HKEY        OneLogonKey;


    // We can use OpenThreadToken because this server thread
    // is impersonating a client

    if ( !OpenThreadToken( GetCurrentThread(),
                           TOKEN_READ,
                           TRUE,  /* Open as self */
                           &TokenHandle ))
    {
#if DBG
        IF_DEBUG(PRINT)
            KdPrint(("ThreadIsInteractive: OpenThreadToken failed: Error %d\n",
                      GetLastError()));
#endif
        return FALSE;
    }

    // notice that we've allocated enough space for the
    // TokenInformation structure. so if we fail, we
    // return a NULL pointer indicating failure


    if ( !GetTokenInformation( TokenHandle,
                               TokenStatistics,
                               TokenInformation,
                               sizeof( TokenInformation ),
                               &ReturnLength ))
    {
#if DBG
        IF_DEBUG(PRINT)
            KdPrint(("ThreadIsInteractive: GetTokenInformation failed: Error %d\n",
                      GetLastError()));
#endif
        return FALSE;
    }

    CloseHandle( TokenHandle );

    LogonId = ((PTOKEN_STATISTICS)TokenInformation)->AuthenticationId;

    RegError = RegOpenKeyExW(
                   HKEY_LOCAL_MACHINE,
                   NW_INTERACTIVE_LOGON_REGKEY,
                   REG_OPTION_NON_VOLATILE,
                   KEY_READ,
                   &InteractiveLogonKey
                   );

    if (RegError != ERROR_SUCCESS) {
#if DBG
        IF_DEBUG(PRINT)
            KdPrint(("ThreadIsInteractive: RegOpenKeyExW failed: Error %d\n",
                      GetLastError()));
#endif
        return FALSE;
    }

    NwLuidToWStr(&LogonId, LogonIdKeyName);

    //
    // Open the <LogonIdKeyName> key under Logon
    //
    RegError = RegOpenKeyExW(
                   InteractiveLogonKey,
                   LogonIdKeyName,
                   REG_OPTION_NON_VOLATILE,
                   KEY_READ,
                   &OneLogonKey
                   );

    if ( RegError == ERROR_SUCCESS ) {
        (void) RegCloseKey(OneLogonKey);
        (void) RegCloseKey(InteractiveLogonKey);
        return TRUE;  /* We found it */
    }
    else {
        (void) RegCloseKey(InteractiveLogonKey);
        return FALSE;  /* We did not find it */
    }

}

DWORD
NwpCitrixGetUserInfo(
    LPWSTR  *ppszUserSid
)
/*++

Routine Description:

    Get the user sid string of the client.

Arguments:

    ppszUserSid - A pointer to buffer to store the string.

Return Value:

    The error code.

--*/
{
    DWORD       err;
    HANDLE      TokenHandle;
    UCHAR       TokenInformation[ SIZE_OF_TOKEN_INFORMATION ];
    ULONG       ReturnLength;

    *ppszUserSid = NULL;

    // We can use OpenThreadToken because this server thread
    // is impersonating a client

    if ( !OpenThreadToken( GetCurrentThread(),
                           TOKEN_READ,
                           TRUE,  /* Open as self */
                           &TokenHandle ))
    {
        err = GetLastError();
    if ( err == ERROR_NO_TOKEN ) {
            if ( !OpenProcessToken( GetCurrentProcess(),
                           TOKEN_READ,
                           &TokenHandle )) {
#if DBG
               IF_DEBUG(PRINT)
               KdPrint(("NwpGetThreadUserInfo: OpenThreadToken failed: Error %d\n",
                      GetLastError()));
#endif

               return(GetLastError());
            }
        }
    else
           return( err );
    }

    // notice that we've allocated enough space for the
    // TokenInformation structure. so if we fail, we
    // return a NULL pointer indicating failure


    if ( !GetTokenInformation( TokenHandle,
                               TokenUser,
                               TokenInformation,
                               sizeof( TokenInformation ),
                               &ReturnLength ))
    {
#if DBG
        IF_DEBUG(PRINT)
            KdPrint(("NwpGetThreadUserInfo: GetTokenInformation failed: Error %d\n",
                      GetLastError()));
#endif
        return(GetLastError());
    }

    CloseHandle( TokenHandle );

    // convert the Sid (pointed to by pSid) to its
    // equivalent Unicode string representation.

    err = NwpConvertSid( ((PTOKEN_USER)TokenInformation)->User.Sid,
                                    ppszUserSid );

    if ( err )
    {
        if ( *ppszUserSid )
            LocalFree( *ppszUserSid );
    }

    return err;
}