/*++

Copyright (c) 1998 Microsoft Corporation

Module Name:

    compacct.c

Abstract:

    Implements DoesComputerAccountExistOnDomain, which determines if a computer
    account exists given an NT domain and computer name.  This is used to
    warn the user if they are going to be joining an NT domain but do not have
    a computer account ready.

Author:

    Jim Schmidt (jimschm) 02-Jan-1998

Revision History:

    jimschm     23-Sep-1998 Added 20 retries for datagram write

--*/

#include "pch.h"
#include <netlogon.h>                                           // private\inc

//
// Contants from sdk\inc\ntsam.h -- copied here because ntsam.h redefines things
//

//
// User account control flags...
//

#define USER_ACCOUNT_DISABLED                (0x00000001)
#define USER_HOME_DIRECTORY_REQUIRED         (0x00000002)
#define USER_PASSWORD_NOT_REQUIRED           (0x00000004)
#define USER_TEMP_DUPLICATE_ACCOUNT          (0x00000008)
#define USER_NORMAL_ACCOUNT                  (0x00000010)
#define USER_MNS_LOGON_ACCOUNT               (0x00000020)
#define USER_INTERDOMAIN_TRUST_ACCOUNT       (0x00000040)
#define USER_WORKSTATION_TRUST_ACCOUNT       (0x00000080)
#define USER_SERVER_TRUST_ACCOUNT            (0x00000100)
#define USER_DONT_EXPIRE_PASSWORD            (0x00000200)
#define USER_ACCOUNT_AUTO_LOCKED             (0x00000400)
#define USER_ENCRYPTED_TEXT_PASSWORD_ALLOWED (0x00000800)
#define USER_SMARTCARD_REQUIRED              (0x00001000)


#define USER_MACHINE_ACCOUNT_MASK      \
            ( USER_INTERDOMAIN_TRUST_ACCOUNT |\
              USER_WORKSTATION_TRUST_ACCOUNT |\
              USER_SERVER_TRUST_ACCOUNT)

#define USER_ACCOUNT_TYPE_MASK         \
            ( USER_TEMP_DUPLICATE_ACCOUNT |\
              USER_NORMAL_ACCOUNT |\
              USER_MACHINE_ACCOUNT_MASK )


//
// Defines
//

#define LM20_TOKENBYTE    0xFF                                  // net\inc\logonp.h
#define LMNT_TOKENBYTE    0xFF

#define MAX_INBOUND_MESSAGE     400
#define PING_RETRY_MAX          3               // number of attempts made against domain

#define NETRES_INITIAL_SIZE     16384


//
// Types
//

typedef enum {
    ACCOUNT_FOUND,
    ACCOUNT_NOT_FOUND,
    DOMAIN_NOT_FOUND
} SCAN_STATE;

//
// Local prototypes
//

BOOL
pEnumNetResourceWorker (
    IN OUT  PNETRESOURCE_ENUM EnumPtr
    );



//
// Implementation
//

VOID
pGenerateLogonMailslotNameA (
    OUT     PSTR SlotName,
    IN      PCSTR DomainName
    )

/*++

Routine Description:

  pGenerateLogonMailslotNameA creates the mailslot name needed to query
  an NT domain server.  It uses an undocumented syntax to open a mailslot
  to DomainName with the 16th character 1Ch.

Arguments:

  SlotName - Receives the mailslot name.  Should be a MAX_PATH buffer.

  DomainName - Specifies the name of the domain to query.

Return Value:

  none

--*/

{
    StringCopyA (SlotName, "\\\\");
    StringCatA (SlotName, DomainName);
    StringCatA (SlotName, "*");
    StringCatA (SlotName, NETLOGON_NT_MAILSLOT_A);
}



PSTR
pAppendStringA (
    OUT     PSTR Buffer,
    IN      PCSTR Source
    )

/*++

Routine Description:

  pAppendStringA appends the specified string in Source to the specified
  Buffer.  The entire string, including the nul, is copied.  The return
  value points to the character after the nul in Buffer.

Arguments:

  Buffer - Receives the copy of Source, up to and including the nul.

  Source - Specifies the nul-terminated string to copy.

Return Value:

  A pointer to the next character after the newly copied string in Buffer.
  The caller will use this pointer for additional append operations.

--*/

{
    while (*Source) {
        *Buffer++ = *Source++;
    }

    *Buffer++ = 0;

    return Buffer;
}


PWSTR
pAppendStringW (
    OUT     PWSTR Buffer,
    IN      PCWSTR Source
    )

/*++

Routine Description:

  pAppendStringW appends the specified string in Source to the specified
  Buffer.  The entire string, including the nul, is copied.  The return
  value points to the character after the nul in Buffer.

Arguments:

  Buffer - Receives the copy of Source, up to and including the nul.

  Source - Specifies the nul-terminated string to copy.

Return Value:

  A pointer to the next character after the newly copied string in Buffer.
  The caller will use this pointer for additional append operations.

--*/

{
    while (*Source) {
        *Buffer++ = *Source++;
    }

    *Buffer++ = 0;

    return Buffer;
}


PBYTE
pAppendBytes (
    OUT     PBYTE Buffer,
    IN      PBYTE Source,
    IN      UINT Len
    )

/*++

Routine Description:

  pAppendBytes appends the specified block of data in Source to the specified
  Buffer.  Len specifies the size of Source.  The return value points to
  the byte after the copied block of data in Buffer.

Arguments:

  Buffer - Receives the copy of Source

  Source - Specifies the block of data to copy

  Len - Specifies the number of bytes in Source

Return Value:

  A pointer to the next byte after the newly copied blcok of data in Buffer.
  The caller will use this pointer for additional append operations.

--*/

{
    while (Len > 0) {
        *Buffer++ = *Source++;
        Len--;
    }

    return Buffer;
}


INT
pBuildDomainPingMessageA (
    OUT     PBYTE Buffer,               // must be sizeof (NETLOGON_SAM_LOGON_REQUEST) + sizeof (DWORD)
    IN      PCSTR LookUpName,
    IN      PCSTR ReplySlotName
    )

/*++

Routine Description:

  pBuildDomainPingMessageA generates a SAM logon SMB that can be sent to
  the NT domain server's NTLOGON mailslot.  If the server receives this
  message, it will reply with either LOGON_SAM_USER_UNKNOWN, LOGON_SAM_LOGON_RESPONSE
  or LOGON_SAM_LOGON_PAUSED.

Arguments:

  Buffer - Receives the SMB message

  LookUpName - Specifies the name of the computer account that may be on
               the domain.  (The domain is specified by the mailslot name.)

  ReplySlotName - Specifies the name of an open mailslot that will receive the
                  server's response, if any.

Return Value:

  The number of bytes used in Buffer, or zero if an error occurred, such as
  out of memory.

--*/

{
    CHAR ComputerName[MAX_COMPUTER_NAMEA];
    DWORD Size;
    PNETLOGON_SAM_LOGON_REQUEST SamLogonRequest;
    PSTR p;
    DWORD ControlBits;
    DWORD DomainSidSize;
    DWORD NtVersion;
    BYTE NtTokenByte;
    BYTE LmTokenByte;
    PCWSTR UnicodeComputerName;
    PCWSTR UnicodeLookUpName;

    //
    // Get computer name
    //

    Size = sizeof (ComputerName) / sizeof (ComputerName[0]);
    if (!GetComputerNameA (ComputerName, &Size)) {
        LOG ((LOG_ERROR, "Can't get computer name."));
        return FALSE;
    }

    //
    // Create unicode strings
    //

    UnicodeComputerName = CreateUnicode (ComputerName);
    if (!UnicodeComputerName) {
        return 0;
    }

    UnicodeLookUpName = CreateUnicode (LookUpName);
    if (!UnicodeLookUpName) {
        DestroyUnicode (UnicodeComputerName);
        return 0;
    }

    //
    // Init pointers
    //

    SamLogonRequest = (PNETLOGON_SAM_LOGON_REQUEST) Buffer;
    p = (PSTR) (SamLogonRequest->UnicodeComputerName);

    //
    // Initialize request packet
    //

    SamLogonRequest->Opcode = LOGON_SAM_LOGON_REQUEST;
    SamLogonRequest->RequestCount = 0;

    //
    // Append the rest of the params together
    //

    p = (PSTR) pAppendStringW ((PWSTR) p, UnicodeComputerName);
    p = (PSTR) pAppendStringW ((PWSTR) p, UnicodeLookUpName);
    p = pAppendStringA (p, ReplySlotName);

    ControlBits = USER_MACHINE_ACCOUNT_MASK;
    p = (PSTR) pAppendBytes ((PBYTE) p, (PBYTE) (&ControlBits), sizeof (DWORD));

    DomainSidSize = 0;
    p = (PSTR) pAppendBytes ((PBYTE) p, (PBYTE) (&DomainSidSize), sizeof (DWORD));

    NtVersion = NETLOGON_NT_VERSION_1;
    p = (PSTR) pAppendBytes ((PBYTE) p, (PBYTE) (&NtVersion), sizeof (DWORD));

    NtTokenByte = LMNT_TOKENBYTE;
    LmTokenByte = LM20_TOKENBYTE;

    p = (PSTR) pAppendBytes ((PBYTE) p, &NtTokenByte, sizeof (BYTE));
    p = (PSTR) pAppendBytes ((PBYTE) p, &NtTokenByte, sizeof (BYTE));
    p = (PSTR) pAppendBytes ((PBYTE) p, &LmTokenByte, sizeof (BYTE));
    p = (PSTR) pAppendBytes ((PBYTE) p, &LmTokenByte, sizeof (BYTE));

    DestroyUnicode (UnicodeComputerName);
    DestroyUnicode (UnicodeLookUpName);

    return p - Buffer;
}


LONG
DoesComputerAccountExistOnDomain (
    IN      PCTSTR DomainName,
    IN      PCTSTR LookUpName,
    IN      BOOL WaitCursorEnable
    )

/*++

Routine Description:

  DoesComputerAccountExistOnDomain queries a domain for the existence of
  a computer account.  It does the following:

  1. Open inbound mailslot to receive server's reply
  2. Open outbound mailslot to domain server
  3. Perpare a message to query the domain server
  4. Send the message on the outbound mailslot
  5. Wait 5 seconds for a reply; stop when a response is obtained.
  6. Repeat 3, 4 and 5 three times if no repsonce

Arguments:

  DomainName - Specifies the domain to query

  LookUpName - Specifies the name of the computer account that may be on
               the domain.

Return Value:

  1 if the account was found
  0 if the account does not exist
  -1 if the domain did not respond

--*/

{
    BYTE Buffer[MAX_INBOUND_MESSAGE];
    CHAR InboundSlotSubName[MAX_MBCHAR_PATH];
    CHAR InboundSlotName[MAX_MBCHAR_PATH];
    CHAR OutboundSlotName[MAX_MBCHAR_PATH];
    PCSTR AnsiDomainName;
    PCSTR AnsiLookUpName;
    PCSTR AnsiLookUpNameWithDollar = NULL;
    HANDLE InboundSlot, OutboundSlot;
    INT OutData, InData;
    INT Size;
    INT Retry;
    BYTE OpCode;
    SCAN_STATE State = DOMAIN_NOT_FOUND;
    BOOL b;
    INT WriteRetries;
    static UINT Sequencer = 0;

#ifdef PRERELEASE
    //
    // Stress mode: do not search the net
    //

    if (g_Stress) {
        DEBUGMSG ((DBG_WARNING, "Domain lookup skipped because g_Stress is TRUE"));
        return TRUE;
    }
#endif

    //
    // Create an inbound mailslot
    //

    wsprintf (InboundSlotSubName, "\\MAILSLOT\\WIN9XUPG\\NETLOGON\\%u", Sequencer);
    InterlockedIncrement (&Sequencer);

    StringCopyA (InboundSlotName, "\\\\.");
    StringCatA (InboundSlotName, InboundSlotSubName);

    InboundSlot = CreateMailslotA (
                      InboundSlotName,
                      MAX_INBOUND_MESSAGE,
                      1000,
                      NULL
                      );

    if (InboundSlot == INVALID_HANDLE_VALUE) {
        LOG ((LOG_ERROR, "DoesComputerAccountExistOnDomain: Can't open %hs", InboundSlotName));
        return -1;
    }

    __try {
        if (WaitCursorEnable) {
            TurnOnWaitCursor();
        }

        //
        // Generate ANSI versions of domain and lookup names
        //

        AnsiDomainName = CreateDbcs (DomainName);
        AnsiLookUpName = CreateDbcs (LookUpName);

        __try {
            if (!AnsiDomainName || !AnsiLookUpName) {
                LOG ((LOG_ERROR, "Can't convert DomainName or LookUpName to ANSI"));
                __leave;
            }

            AnsiLookUpNameWithDollar = JoinTextA (AnsiLookUpName, "$");
            if (!AnsiLookUpNameWithDollar) {
                __leave;
            }

            //
            // Create outbound mailslot
            //

            pGenerateLogonMailslotNameA (OutboundSlotName, AnsiDomainName);

            OutboundSlot = CreateFileA (
                               OutboundSlotName,
                               GENERIC_WRITE,
                               FILE_SHARE_READ,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL
                               );

            if (OutboundSlot == INVALID_HANDLE_VALUE) {
                LOG ((LOG_ERROR, "Can't open %s", OutboundSlotName));
                __leave;
            }

            for (Retry = 0, State = DOMAIN_NOT_FOUND;
                 State == DOMAIN_NOT_FOUND && Retry < PING_RETRY_MAX;
                 Retry++
                 ) {

                //
                // Generate message
                //

                Size = pBuildDomainPingMessageA (Buffer, AnsiLookUpNameWithDollar, InboundSlotSubName);

                if (Size > 0) {
                    //
                    // Send the message and wait for a response
                    //

                    WriteRetries = 20;
                    do {
                        b = WriteFile (OutboundSlot, Buffer, Size, (PDWORD) &OutData, NULL);

                        if (!b || OutData != Size) {
                            if (WriteRetries && GetLastError() == ERROR_NETWORK_BUSY) {
                                b = TRUE;
                                OutData = Size;
                                WriteRetries--;
                                Sleep (50);

                                DEBUGMSG ((DBG_WARNING, "DoesComputerAccountExistOnDomain: Network busy!  Retrying..."));

                            } else {
                                LOG ((LOG_ERROR, "Machine account query failed: can't write to network mailslot."));
                                __leave;
                            }
                        }
                    } while (!b || OutData != Size);

                    //
                    // Sit on mailslot for 5 seconds until data is available.
                    // If no data comes back, assume failure.
                    // If an unrecognized response comes back, wait for another response.
                    //

                    do {
                        if (!WaitCursorEnable) {
                            //
                            // Only wait 1 second in search mode
                            //

                            Size = CheckForWaitingData (InboundSlot, sizeof (BYTE), 1000);
                        } else {
                            Size = CheckForWaitingData (InboundSlot, sizeof (BYTE), 5000);
                        }

                        if (Size > 0) {
                            //
                            // Response available!
                            //

                            if (!ReadFile (InboundSlot, Buffer, Size, (PDWORD) &InData, NULL)) {
                                LOG ((LOG_ERROR, "Failed while reading from network mail slot."));
                                __leave;
                            }

                            OpCode = *((PBYTE) Buffer);

                            if (OpCode == LOGON_SAM_USER_UNKNOWN || OpCode == LOGON_SAM_USER_UNKNOWN_EX) {
                                State = ACCOUNT_NOT_FOUND;
                            } else if (OpCode == LOGON_SAM_LOGON_RESPONSE || OpCode == LOGON_SAM_LOGON_RESPONSE_EX) {
                                State = ACCOUNT_FOUND;
                            }
                        }
                    } while (State != ACCOUNT_FOUND && Size > 0);

                } else {
                    DEBUGMSG ((DBG_WHOOPS, "Can't build domain ping message"));
                    __leave;
                }
            }
        }
        __finally {
            FreeText (AnsiLookUpNameWithDollar);
            DestroyDbcs (AnsiDomainName);   // this routine checks for NULL
            DestroyDbcs (AnsiLookUpName);
        }
    }
    __finally {
        CloseHandle (InboundSlot);
        if (WaitCursorEnable) {
            TurnOffWaitCursor();
        }
    }

    if (State == ACCOUNT_FOUND) {
        return 1;
    }

    if (State == ACCOUNT_NOT_FOUND) {
        return 0;
    }

    return -1;
}


BOOL
EnumFirstNetResource (
    OUT     PNETRESOURCE_ENUM EnumPtr,
    IN      DWORD WNetScope,                OPTIONAL
    IN      DWORD WNetType,                 OPTIONAL
    IN      DWORD WNetUsage                 OPTIONAL
    )

/*++

Routine Description:

  EnumFirstNetResource begins an enumeration of the network resources.  It
  uses pEnumNetResourceWorker to do the enumeration.

Arguments:

  EnumPtr - Receives the first enumerated network resource

  WNetScope - Specifies the RESOURCE_* flag to limit the enumeration.  If zero,
              the default scope is RESOURCE_GLOBALNET.

  WNetType - Specifies the RESOURCETYPE_* flag(s) to limit the enumerattion.
             If zero, the default type is RESOURCETYPE_ANY.

  WNetUsage - Specifies the RESOURCEUSAGE_* flag(s) to limit the enumeration.
              If zero, the default usage is all resources.

Return Value:

  TRUE if a network resource was enumerated, or FALSE if none were found.
  If return value is FALSE, GetLastError will return an error code, or
  ERROR_SUCCESS if all items were successfully enumerated.

--*/

{
    ZeroMemory (EnumPtr, sizeof (NETRESOURCE_ENUM));
    EnumPtr->State = NETRES_INIT;
    EnumPtr->EnumScope = WNetScope ? WNetScope : RESOURCE_GLOBALNET;
    EnumPtr->EnumType  = WNetType ? WNetType : RESOURCETYPE_ANY;
    EnumPtr->EnumUsage = WNetUsage ? WNetUsage : 0;         // 0 is "any"

    return pEnumNetResourceWorker (EnumPtr);
}


BOOL
EnumNextNetResource (
    IN OUT  PNETRESOURCE_ENUM EnumPtr
    )

/*++

Routine Description:

  EnumNextNetResource continues an enumeration of the network resources.  It
  uses pEnumNetResourceWorker to do the enumeration.

Arguments:

  EnumPtr - Specifies the previously enumerated item, receives the first
            enumerated network resource

Return Value:

  TRUE if a network resource was enumerated, or FALSE if none were found.
  If return value is FALSE, GetLastError will return an error code, or
  ERROR_SUCCESS if all items were successfully enumerated.

--*/

{
    return pEnumNetResourceWorker (EnumPtr);
}


BOOL
pEnumNetResourceWorker (
    IN OUT  PNETRESOURCE_ENUM EnumPtr
    )

/*++

Routine Description:

  pEnumNetResourceWorker implements a state machine to enumerate network
  resources.  The WNet APIs are used to do the enumeration.  Each call
  to the WNetEnumResources function returns up to 64 items, but
  pEnumNetResourceWorker returns only one at a time.  For this reason,
  a stack of handles and buffers are maintained by the state machine,
  simplifying the work for the caller.

Arguments:

  EnumPtr - Specifies the current enumeration state, receives the next
            enumerated network resource

Return Value:

  TRUE if a network resource was enumerated, or FALSE if none were found.
  If return value is FALSE, GetLastError will return an error code, or
  ERROR_SUCCESS if all items were successfully enumerated.

--*/

{
    LPNETRESOURCE CurrentResBase;
    LPNETRESOURCE CurrentRes;
    LPNETRESOURCE ParentRes;
    HANDLE CurrentHandle;
    UINT Entries;
    UINT Pos;
    DWORD rc;
    UINT Size;
    UINT u;

    for (;;) {
        u = EnumPtr->StackPos;

        Entries = EnumPtr->Entries[u];
        Pos = EnumPtr->Pos[u];
        CurrentResBase = (LPNETRESOURCE) EnumPtr->ResStack[u];
        CurrentRes = &CurrentResBase[Pos];
        CurrentHandle = EnumPtr->HandleStack[u];

        if (EnumPtr->StackPos) {
            ParentRes = (LPNETRESOURCE) EnumPtr->ResStack[EnumPtr->StackPos - 1];
        } else {
            ParentRes = NULL;
        }

        switch (EnumPtr->State) {

        case NETRES_INIT:
            EnumPtr->State = NETRES_OPEN_ENUM;
            break;

        case NETRES_OPEN_ENUM:
            EnumPtr->ResStack[EnumPtr->StackPos] = (PBYTE) MemAlloc (
                                                                g_hHeap,
                                                                0,
                                                                NETRES_INITIAL_SIZE
                                                                );

            rc = WNetOpenEnum (
                     EnumPtr->EnumScope,
                     EnumPtr->EnumType,
                     EnumPtr->EnumUsage,
                     ParentRes,
                     &CurrentHandle
                     );

            if (rc != NO_ERROR) {
                AbortNetResourceEnum (EnumPtr);
                SetLastError (rc);
                LOG ((LOG_ERROR, "Failed to open network resource enumeration. (%u)", rc));
                return FALSE;
            }

            EnumPtr->HandleStack[EnumPtr->StackPos] = CurrentHandle;
            EnumPtr->State = NETRES_ENUM_BLOCK;
            break;

        case NETRES_ENUM_BLOCK:
            Entries = 64;
            Size = NETRES_INITIAL_SIZE;

            rc = WNetEnumResource (
                     CurrentHandle,
                     &Entries,
                     (PBYTE) CurrentResBase,
                     &Size
                     );

            if (rc == ERROR_NO_MORE_ITEMS) {
                EnumPtr->State = NETRES_CLOSE_ENUM;
                break;
            }

            if (rc != NO_ERROR) {
                AbortNetResourceEnum (EnumPtr);
                SetLastError (rc);
                LOG ((LOG_ERROR, "Failure while enumerating network resources. (%u)", rc));
                return FALSE;
            }

            EnumPtr->Entries[EnumPtr->StackPos] = Entries;
            EnumPtr->Pos[EnumPtr->StackPos] = 0;
            EnumPtr->State = NETRES_RETURN_ITEM;
            break;

        case NETRES_RETURN_ITEM:
            EnumPtr->Connected  = (CurrentRes->dwScope & RESOURCE_CONNECTED) != 0;
            EnumPtr->GlobalNet  = (CurrentRes->dwScope & RESOURCE_GLOBALNET) != 0;
            EnumPtr->Persistent = (CurrentRes->dwScope & RESOURCE_REMEMBERED) != 0;

            EnumPtr->DiskResource  = (CurrentRes->dwType & RESOURCETYPE_DISK) != 0;
            EnumPtr->PrintResource = (CurrentRes->dwType & RESOURCETYPE_PRINT) != 0;
            EnumPtr->TypeUnknown   = (CurrentRes->dwType & RESOURCETYPE_ANY) != 0;

            EnumPtr->Domain     = (CurrentRes->dwDisplayType & RESOURCEDISPLAYTYPE_DOMAIN) != 0;
            EnumPtr->Generic    = (CurrentRes->dwDisplayType & RESOURCEDISPLAYTYPE_GENERIC) != 0;
            EnumPtr->Server     = (CurrentRes->dwDisplayType & RESOURCEDISPLAYTYPE_SERVER) != 0;
            EnumPtr->Share      = (CurrentRes->dwDisplayType & RESOURCEDISPLAYTYPE_SHARE) != 0;

            EnumPtr->Connectable = (CurrentRes->dwUsage & RESOURCEUSAGE_CONNECTABLE) != 0;
            EnumPtr->Container   = (CurrentRes->dwUsage & RESOURCEUSAGE_CONTAINER) != 0;

            EnumPtr->RemoteName = CurrentRes->lpRemoteName ? CurrentRes->lpRemoteName : S_EMPTY;
            EnumPtr->LocalName  = CurrentRes->lpLocalName ? CurrentRes->lpLocalName : S_EMPTY;
            EnumPtr->Comment    = CurrentRes->lpComment;
            EnumPtr->Provider   = CurrentRes->lpProvider;

            if (EnumPtr->Container) {
                //
                // Enum container resource
                //

                if (EnumPtr->StackPos + 1 < MAX_NETENUM_DEPTH) {
                    EnumPtr->StackPos += 1;
                    EnumPtr->State = NETRES_OPEN_ENUM;
                }
            }

            if (EnumPtr->State == NETRES_RETURN_ITEM) {
                EnumPtr->State = NETRES_ENUM_BLOCK_NEXT;
            }
            return TRUE;

        case NETRES_ENUM_BLOCK_NEXT:
            u = EnumPtr->StackPos;
            EnumPtr->Pos[u] += 1;
            if (EnumPtr->Pos[u] >= EnumPtr->Entries[u]) {
                EnumPtr->State = NETRES_ENUM_BLOCK;
            } else {
                EnumPtr->State = NETRES_RETURN_ITEM;
            }

            break;

        case NETRES_CLOSE_ENUM:
            WNetCloseEnum (CurrentHandle);
            MemFree (g_hHeap, 0, EnumPtr->ResStack[EnumPtr->StackPos]);

            if (!EnumPtr->StackPos) {
                EnumPtr->State = NETRES_DONE;
                break;
            }

            EnumPtr->StackPos -= 1;
            EnumPtr->State = NETRES_ENUM_BLOCK_NEXT;
            break;

        case NETRES_DONE:
            SetLastError (ERROR_SUCCESS);
            return FALSE;
        }
    }
}


VOID
AbortNetResourceEnum (
    IN OUT  PNETRESOURCE_ENUM EnumPtr
    )

/*++

Routine Description:

  AbortNetResourceEnum cleans up all allocated memory and open handles,
  and then sets the enumeration state to NETRES_DONE to stop any
  subsequent enumeration.

  If enumeration has already completed or was previously aborted, this
  routine simply returns without doing anything.

Arguments:

  EnumPtr - Specifies the enumeration to stop, receives an enumeration
            structure that will not enumerate any more items unless it
            is given back to EnumFirstNetResource.

Return Value:

  none

--*/

{
    UINT u;

    if (EnumPtr->State == NETRES_DONE) {
        return;
    }

    for (u = 0 ; u <= EnumPtr->StackPos ; u++) {
        WNetCloseEnum (EnumPtr->HandleStack[u]);
        MemFree (g_hHeap, 0, EnumPtr->ResStack[u]);
    }

    EnumPtr->State = NETRES_DONE;
}