/*++

Copyright (c) 1991-92  Microsoft Corporation

Module Name:

    msginit.c

Abstract:

    Messenger Service Initialization Routines.
    The following is a list of functions in this file:

        MsgInitializeMsgr
        BufferInit
        InitSharedData
        SetComputerName
        GetNumNets
        MsgGetBufSize
        SetUpMessageFile

Author:

    Dan Lafferty (danl) 18-Jul-1991

Environment:

    User Mode - Win32

Notes:

    optional-notes

Revision History:

    19-Aug-1997 wlees
        PNP support.  Retry if lana's not present yet.

    27-Jun-1995 AnirudhS
        LocalFree(dataPtr) must be called AFTER MsgFreeSupportSeg, because
        the latter tries to _close a handle stored in dataPtr.

    08-Feb-1994 Danl
        Removed the restriction that the memory allocated had to be
        restricted to less than a 64K segment.  We don't worry about
        segments anymore.

    12-Jan-1993 Danl
        In error paths where we call MsgCloseWakeupSems, I need to do the
        LocalFree(dataPtr) after the call to MsgCloseWakeupSems.  Otherwise,
        it access violates because MsgCloseWakeupSems uses the shared date
        in the block pointed to by the dataPtr.

    21-Apr-1992 JohnRo
        Fixed bug printing a status when message name add fails.
        Changed to use FORMAT_ equates throughout.

    18-Feb-1992 ritaw
        Convert to Win32 service control APIs.

    18-Jul-1991 danl
        Created as a composite of the original LM2,0 routines.


--*/
//
// Includes
//

#include <stdlib.h>     // atol
#include "msrv.h"       // Messenger prototypes and constants
#include <winsvc.h>     // Service control APIs
#include <winsock2.h>   // Windows sockets

#include <netdebug.h>   // NetpAssert, FORMAT_ equates.
#include <rpc.h>        // DataTypes and runtime APIs
#include <msgsvc.h>     // generated by the MIDL complier

#include <netlibnt.h>   // NetpNtStatusToApiStatus prototypes

#include <tstring.h>    // Unicode string macros
#include <string.h>     // memcpy
#include <lmwksta.h>    // NetWrkstaTransportEnum
#include <lmapibuf.h>   // NetApiBufferFree
#include <netlib.h>     // UNUSED macro
#include <msgrutil.h>   // NetpNetBiosReset
#include <apperr2.h>    // APE2_ALERTER_PRINTING_SUCCESS

#include "msgdbg.h"     // MSG_LOG
#include "heap.h"       // heap management routines and macros.
#include "msgdata.h"    // Global data
#include "msgsec.h"     // Messenger security information

#include "msgnames.h"   // MSGR_INTERFACE_NAME
#include "msgtext.h"    // MTXT_MsgsvcTitle

#include "msgsvcsend.h"   // Broadcast message send interface

#include "apiutil.h"      // for MsgAddSessionInList

// The per net data and heap buffer are allocated once. Later on we may
// wish to keep them separate.
#define BOOKKEEPING_SIZE(n) (n * sizeof(NET_DATA))

#define MAXSEG              (0xffff)

#define LMI_PARM_M_SIZMESSBUF   TEXT("/sizmessbuf")


//
//  Global Data
//

    static DWORD    bufferSize;         // Message buffer size
    static DWORD    msrv_pid;           // pid of message server

    extern LPTSTR   MessageFileName;

//
// Local Function Prototypes
//

VOID
MsgBufferInit(
    IN DWORD  dwBufLen
    );

NET_API_STATUS
MsgInitSharedData(
    DWORD NumNets
    );

NET_API_STATUS
MsgSetComputerName(
    DWORD NumNets
    );

DWORD
MsgGetNumNets(VOID);

NET_API_STATUS
MsgGetBufSize (
    OUT LPDWORD bufferSize
    );

DWORD
MsgSetUpMessageFile(VOID);


STATIC VOID
MsgInitMessageBoxTitle(
    VOID
    );

NET_API_STATUS
MsgrInitializeMsgrInternal1(
    void
    );

NET_API_STATUS
MsgrInitializeMsgrInternal2(
    void
    );

VOID
MsgInitEndpoint(
    PVOID  Context           // This passed in as context
    );


NET_API_STATUS
MsgInitializeMsgr(
    IN  DWORD   argc,
    IN  LPTSTR  *argv
    )

/*++

Routine Description:

    Registers the control handler with the dispatcher thread.  Then it
    performs all initialization including the starting of the RPC server.
    If any of the initialization fails, MsgStatusUpdate is called so that the
    status is updated and the thread is terminated.

Arguments:


Return Value:



--*/

{
    NET_API_STATUS      status;
    DWORD               msgrState;
    DWORD               bufLen;
    WSADATA             wsaData;
    NTSTATUS            ntStatus;

    //
    // Initialize the Thread Manager.  This initializes some locks used
    // on the Thread and Status databases.
    //
    status = MsgThreadManagerInit();

    if (status != NO_ERROR)
    {
        MSG_LOG1(ERROR,
                 "MsgInitializeMsgr:  Thread manager init failed %d\n",
                 status);

        return MsgBeginForcedShutdown(IMMEDIATE, status);
    }

    //
    // Initialize the status structure
    //
    MsgStatusInit();

    //
    // Register this service with the ControlHandler.
    // Now we can accept control requests and be requested to UNINSTALL.
    //

    MSG_LOG(TRACE, "Calling RegisterServiceCtrlHandlerEx\n",0);
    if ((MsgrStatusHandle = RegisterServiceCtrlHandlerEx(
                                SERVICE_MESSENGER,
                                MsgrCtrlHandler,
                                NULL
                                )) == (SERVICE_STATUS_HANDLE) NULL) {

        status = GetLastError();

        MSG_LOG(ERROR,
            "FAILURE: RegisterServiceCtrlHandlerEx status = " FORMAT_API_STATUS
            "\n", status);

        return( MsgBeginForcedShutdown (
                    IMMEDIATE,
                    status));
    }

    //
    // Notify that installation is pending
    //

    msgrState = MsgStatusUpdate(STARTING);

    if (msgrState != STARTING) {
        //
        // An UNINSTALL control request must have been received
        //
        return(msgrState);
    }

    //
    // Init the _HYDRA_ WinStation message support
    //
    status = MultiUserInitMessage();

    if (status != NERR_Success)
    {
        MSG_LOG(ERROR, "MultiUser Initialization Failed " FORMAT_RPC_STATUS "\n",
                status);

        return (MsgBeginForcedShutdown(
                IMMEDIATE,
                status));
    }

    //
    // Check that the workstation is started
    //

    MSG_LOG(TRACE, "Calling NetServiceControl\n",0);

    if (! NetpIsServiceStarted(SERVICE_WORKSTATION)) {

        MSG_LOG(ERROR, "WorkStation Service is not started\n",0);

        return (MsgBeginForcedShutdown(
                     IMMEDIATE,
                     NERR_WkstaNotStarted));
    }

    // *** INSTALLATION HINT ***
    msgrState = MsgStatusUpdate(STARTING);
    if (msgrState != STARTING) {
        return(msgrState);
    }

    //
    //  Get the default buffer size.
    //

    status = MsgGetBufSize(&bufferSize);

    if (status != NERR_Success)
    {
        MSG_LOG(ERROR, "MsgGetBufSize Failed\n",0);
        return (MsgBeginForcedShutdown(
                    IMMEDIATE,
                    status));
    }

    // *** INSTALLATION HINT ***
    msgrState = MsgStatusUpdate(STARTING);
    if (msgrState != STARTING) {
        return(msgrState);
    }


    if (bufferSize > MAX_SIZMESSBUF || bufferSize < MIN_SIZMESSBUF) {
        MSG_LOG(ERROR, "Message Buffer Size is illegal\n",0);
        return (MsgBeginForcedShutdown(
                    IMMEDIATE,
                    ERROR_INVALID_PARAMETER));
    }

    //
    // This is the size of the buffer (that SDBUFFER points to) in the
    // shared data area.  This is calculated as:
    //
    //   The size of a message buffer (bufferSize)
    //          plus
    //   space for 4 Multi-block message headers and names,
    //          plus
    //   space for one Multi-block text header for each text block that
    //   fits into the message buffer.  (bufferSize/TXTMAX).
    //
    //  The number of headers is rounded up by one.
    //      (bufferSize+TXTMAX-1)/TXTMAX
    //
    bufferSize += (4 * (sizeof(MBB) + (2 * NCBNAMSZ))) +
        ((( (bufferSize+TXTMAX-1)/TXTMAX) + 1) * sizeof(MBT));



    // ***** INSTALLATION HINT *****
    msgrState = MsgStatusUpdate(STARTING);
    if (msgrState != STARTING) {
        return(msgrState);
    }

    //
    // Ask the Worksta for the computer name.  If the computer
    // has no name, then abort.
    //
    // The computername and the username are in unicode format.
    //
    // NOTE:  the username that is returned is a name we may want to add
    //        to the table.
    //

    MSG_LOG(TRACE, "Getting the ComputerName\n",0);

    bufLen = sizeof(machineName);

    *machineName = TEXT('\0');

    if (!GetComputerName(machineName,&bufLen)) {
        MSG_LOG(ERROR,"GetComputerName failed \n",0);
        status = GetLastError();
    }

    if ( (status != NERR_Success) ||
         (*machineName == TEXT('\0')) || (*machineName == TEXT(' ')))
    {
        //
        // fatal error if no name
        //
        MSG_LOG(ERROR, "GetWkstaNames Failed\n",0);
        return (MsgBeginForcedShutdown(
                    IMMEDIATE,
                    NERR_NoComputerName));
    }

    machineName[NCBNAMSZ] = TEXT('\0');           // make sure it's terminated
    MachineNameLen = (SHORT) STRLEN(machineName);


    // ***** INSTALLATION HINT *****
    msgrState = MsgStatusUpdate(STARTING);

    if (msgrState != STARTING)
    {
        return(msgrState);
    }

    //
    // Initialize the configuration lock.  This lock covers the allocation and deallocation
    // of the data structures related to lan adapters.
    //
    if (MsgConfigurationLock(MSG_INITIALIZE,"MsgInitializeMsgr"))
    {
        //
        // Do first phase of lan adapter related configuration
        //
        status = MsgrInitializeMsgrInternal1();

        if (status != NERR_Success)
        {
            MSG_LOG1(ERROR, "MsgrInitializeMsgrInternal1 Failure %d\n", status);
        }
    }
    else
    {
        MSG_LOG0(ERROR, "MsgConfigurationLock -- MSG_INITIALIZE failed\n");

        status = ERROR_NOT_ENOUGH_MEMORY;
    }

    if (status != NERR_Success)
    {
        MsgFreeSharedData();

        if (wakeupSem != NULL)
        {
            MsgFreeSupportSeg();
        }

        MsgCloseWakeupSems();          // Close the ones that have been created

        return (MsgBeginForcedShutdown(
                    IMMEDIATE,
                    status));
    }


    // ***** INSTALLATION HINT *****
    msgrState = MsgStatusUpdate(STARTING);

    if (msgrState != STARTING)
    {
        return(msgrState);
    }

    //
    // Change from IMMEDIATE Shutdowns to PENDING shutdowns.
    // This is because at this point we have names on the adapters
    // to clean up.
    //

    status = MsgrInitializeMsgrInternal2();

    if (status != NERR_Success)
    {
        MSG_LOG1(ERROR, "MsgrInitializeMsgrInternal2 Failure\n", status);

        return (MsgBeginForcedShutdown(
                    PENDING,
                    status));
    }

    //
    // Build the name of the file that is to be used to get the
    // message header and tail.  Don't care about errors, since
    // any error will result in the file not being used and is
    // resorting to the old standby strings.
    //

    MSG_LOG(TRACE, "Calling MsgSetUpMessageFile\n",0);
    MsgSetUpMessageFile();

    //
    // Start the Group messenger thread to handle all domain messaging
    //

    MSG_LOG(TRACE, "Calling MsgInitGroupSupport\n",0);

    if ( status = MsgInitGroupSupport( SD_NUMNETS() ) )
    {
        MSG_LOG(ERROR, "InitGroupSupport Failed\n",0);
        return (MsgBeginForcedShutdown(
                    PENDING,
                    NERR_GrpMsgProcessor));
    }

    //
    // Initialize the Display Code
    //
    status = MsgDisplayInit();

    if (status != NO_ERROR)
    {
        MSG_LOG1(ERROR, "Could not initialize the display functions %d\n", status);
        return (MsgBeginForcedShutdown(
                    PENDING,
                    status));
    }


    //
    // Create the security descriptor that is to be used in access
    // checks on the API interface.
    //

    MSG_LOG(TRACE, "Calling MsgCreateMessageNameObject\n",0);
    status = MsgCreateMessageNameObject();

    if (status != NERR_Success)
    {
        MSG_LOG(ERROR, "MsgCreateMessageNameObject (security descriptor) "
                "Failed\n", 0);

        return (MsgBeginForcedShutdown(
                    PENDING,
                    status));
    }

    //
    // Initialize the text for the message box title.
    //
    MsgInitMessageBoxTitle();

    //
    // Start the Messengers RPC server.
    //
    // NOTE:  Now all RPC servers in services.exe share the same pipe name.
    // However, in order to support communication with version 1.0 of WinNt,
    // it is necessary for the Client Pipe name to remain the same as
    // it was in version 1.0.  Mapping to the new name is performed in
    // the Named Pipe File System code.
    //

    MSG_LOG(TRACE,
        "MsgInitializeMsgr:Getting ready to start RPC server\n",0);

    status = MsgsvcGlobalData->StartRpcServer(
                MSGR_INTERFACE_NAME,
                msgsvc_ServerIfHandle);

    if (!NT_SUCCESS(status)) {
        MSG_LOG(ERROR, "RPC Initialization Failed " FORMAT_RPC_STATUS "\n",
                status);

        return (MsgBeginForcedShutdown(
                PENDING,
                status));
    }

    //
    // Start thread to register with endpoint mapper (may take a while)
    //
    ntStatus = RtlQueueWorkItem(MsgInitEndpoint,      // Callback
                                NULL,                 // pContext
                                WT_EXECUTEONLYONCE |  // Long one-shot callback
                                  WT_EXECUTELONGFUNCTION);

    if (!NT_SUCCESS(ntStatus)) {
        MSG_LOG(ERROR, "MsgInit: failed to start endpoint registration thread: %#x\n",
                ntStatus);

        return (MsgBeginForcedShutdown(
                PENDING,
                status));
    }

    // Initialize winsock (needed for name resolution)
    //

    status = WSAStartup(MAKEWORD(2,1),&wsaData);
    if (status != ERROR_SUCCESS) {
        MSG_LOG(ERROR, "Initialization of Winsock DLL failed " FORMAT_RPC_STATUS "\n",
                status);

        return (MsgBeginForcedShutdown(
                PENDING,
                status));
    }

    //
    //  Update the status to indicate that installation is complete.
    //  Get the current state back in case the ControlHandling thread has
    //  told us to shutdown.
    //

    MSG_LOG(TRACE, "Exiting MsgInitializeMsgr - Init Done!\n",0);

    return (MsgStatusUpdate(RUNNING));
}


NET_API_STATUS
MsgrInitializeMsgrInternal1(
    void
    )

/*++

Routine Description:

Initialize volatile state related to lanas.
These initializations can be undone immediately if there is an error.

Arguments:

    None

Return Value:

    None

--*/

{
    DWORD NumNets;
    NET_API_STATUS      status;

    MSG_LOG(TRACE, "Calling MsgGetNumNets\n",0);
    NumNets = MsgGetNumNets();

    if (NumNets == 0)
    {
        MSG_LOG(TRACE, "FYI: No lana's enabled at this time\n",0);
        // Not having any networks is no longer an error, ie Numnets == 0 is ok
    }

    //
    // Initialize shared memory areas.
    //
    MSG_LOG(TRACE, "Calling MsgInitSharedData\n",0);
    status = MsgInitSharedData(NumNets);

    if (status != NERR_Success)
    {
        return status;
    }

    //*****************************************
    //
    // STUFF FROM Init_msrv() in MSRV.C
    //
    //*****************************************

    heap   = SD_BUFFER();           // Initialize data heap pointer
    heapln = SD_BUFLEN();           // Initialize data heap length

    //
    // Set up the segement to hold the net bios handles, lana-nums
    // and wakeup Semaphores.
    //

    MSG_LOG(TRACE, "Calling MsgInitSupportSeg\n",0);

    status = MsgInitSupportSeg();

    if (status != NERR_Success)
    {
        MSG_LOG(ERROR, "InitSupportSeg Failed\n",0);
        return status;
    }

    //
    // Now initialize global net bios handles & lana nums. Initializes net_lana_num[]
    //

    MSG_LOG(TRACE, "Calling MsgInit_NetBios\n",0);

    status = MsgInit_NetBios();

    if (status != NERR_Success)
    {
        MSG_LOG1(ERROR, "MsgInit_NetBios failed %d\n", status);
        return status;
    }

    //
    // Get the wake up semaphore handles.  Initializes wakeupSem[]
    //

    MSG_LOG(TRACE, "Calling MsgCreateWakeupSems\n",0);

    //
    // This always returns TRUE
    //
    MsgCreateWakeupSems(SD_NUMNETS());

    //
    // Open NETBIOS for use by messenger.
    // If any failures occur beyond this we must remember to close.
    //
    MsgsvcGlobalData->NetBiosOpen();

    //
    // Set computer name on adapters - if any
    //
    MSG_LOG(TRACE, "Calling MsgSetComputerName\n",0);

    status = MsgSetComputerName(SD_NUMNETS());

    if(status != NERR_Success)
    {
        MSG_LOG1(ERROR, "SetComputerName failed %d\n", status);
        MsgsvcGlobalData->NetBiosClose();
        return status;
    }

    return NERR_Success;
}


NET_API_STATUS
MsgrInitializeMsgrInternal2(
    void
    )

/*++

Routine Description:

Initialize volatile lana state.  These initializations cannot be undone easily.  If this routine
fails we must go through a full shutdown in order to clean up.

Arguments:

    None

Return Value:

    None

--*/

{
    //
    // Installation is successful and complete. If there is a
    // user logged on then an attempt is made to add the user name
    // to this message server. No attempt is made at error reporting
    // if this fails, there may not be a user logged on, and if there is,
    // the user name may already exist as a message name on another
    // station.
    //
    //  This is when we add usernames to the message table if we can.
    //  Sometime this needs to handle multiple users??? (not in version 1)
    //

    if (g_IsTerminalServer)
    {
        MsgAddAlreadyLoggedOnUserNames();
    }
    else
    {
        MsgAddUserNames();
    }

    return NERR_Success;
}


VOID
MsgBufferInit(
    IN DWORD  dwBufLen
    )

/*++

Routine Description:

    This function is called during initialization to set up
    the message buffer in the shared data area.

    This function assumes that the shared data area is locked
    in memory, that the access semaphore for the shared data
    area is set, and that the global far pointer, dataPtr, is
    valid.  BufferInit() initializes the heap structure of the
    buffer.

    SIDE EFFECTS

    The buffer in shared memory is initialized.

Arguments:

    dwBuflen - buffer length

Return Value:

    none

--*/

{
    LPHEAPHDR   hp;         // Heap block pointer

    hp = (LPHEAPHDR) SD_BUFFER();       // Get the address of buffer
    HP_SIZE(*hp) = dwBufLen;            // Set the size of the first block
    HP_FLAG(*hp) = 0;                   // Unallocated
    SD_BUFLEN() = dwBufLen;             // Save the length of the buffer
}

DWORD
MsgInitSharedData(
    DWORD   NumNets
    )

/*++

Routine Description:

    This function creates and initializes the shared data area.
    It sets up the computer name and initializes the message
    buffer.

    SIDE EFFECTS

    Calls MsgBufferInit().


Arguments:

    NumNets - Number of network adapters to support.

Return Value:

    RETURN
        NERR_Success if the operation was successful

        ERROR_NOT_ENOUGH_MEMORY - If the memory alloc for the shared
            memory segment fails.

--*/

{

    DWORD       i,j;        // Index
    ULONG       size;
    PNCB_DATA   pNcbData;
    DWORD       MinimumNumNets = ((NumNets == 0)? 1 : NumNets); // 1 is the minimum

    //
    // Create and initialize shared data area.
    //
    size = bufferSize + BOOKKEEPING_SIZE(MinimumNumNets);

    if ((GlobalData.NetData = (PNET_DATA)LocalAlloc(LMEM_ZEROINIT, size)) == NULL) {
        goto NoMemory;
    }
    //
    //  In case NumNets = 0, keep 1 dummy NetData.
    //  This is not very pretty but should avoid any trouble, without having to modify
    //  too much code.
    //
    GlobalData.Buffer = (PCHAR) (&GlobalData.NetData[MinimumNumNets]); 

    for (i = 0; i < NumNets ; i++ )
    {
        // Allocate the list array at the maximum size, but only allocate
        // a small initial number of NCBs.
        if ((GlobalData.NetData[i].NcbList =
             LocalAlloc(LMEM_ZEROINIT,
                        sizeof(PNCB_DATA) * NCB_MAX_ENTRIES)) == NULL)
        {
            goto NoMemory;
        }
        GlobalData.NetData[i].NumNcbs = NCB_INIT_ENTRIES;

        for (j=0; j < NCB_INIT_ENTRIES; j++)
        {
            if ((GlobalData.NetData[i].NcbList[j] = pNcbData =
                (PNCB_DATA) LocalAlloc(LMEM_ZEROINIT,
                                       sizeof(NCB_DATA))) == NULL)
            {
                goto NoMemory;
            }

            pNcbData->Ncb.ncb_cmd_cplt = 0xff;
            pNcbData->Ncb.ncb_retcode = 0;
        }
    }

    //
    // Initialize the shared data lock.  The shared data is shared between
    // the API threads and the worker threads.
    //
    if (!MsgDatabaseLock(MSG_INITIALIZE,"InitSharedData"))
    {
        MSG_LOG0(ERROR,
                 "MsgInitSharedData: MsgDatabaseLock failed\n");

        goto NoMemory;
    }

    //
    // Initialize the "used-to-be shared" data
    //
    SD_NUMNETS()   = NumNets;
    SD_MSRV()      = 0;         // No message server active
    SD_LOGNAM()[0] = '\0';      // No log file yet
    SD_MESLOG()    = 0;         // Message logging disabled
    SD_MESQF()     = INULL;     // Message queue is empty
    SD_MESQB()     = INULL;

    for ( j = 0; j < SD_NUMNETS(); j++ )
    {
        for(i = 0; i < NCBMAX(j); ++i)
        {
            //
            // Mark entries as free
            //
            SD_NAMEFLAGS(j,i) = NFDEL;

            //create empty session lists
            InitializeListHead(&(SD_SIDLIST(j,i)));
        }
    }

    //
    // Initialize the message buffer
    //
    MsgBufferInit(bufferSize);

    //
    // NT NOTE:
    // Skip Initializing the Support Set and Wakeup sems.
    // Init_msrv will end up doing that.
    //

    return(NERR_Success);

NoMemory:
    MSG_LOG(ERROR,"[MSG]InitSharedData:LocalAlloc Failure "
            FORMAT_API_STATUS "\n", GetLastError());
    return(ERROR_NOT_ENOUGH_MEMORY);

}

VOID
MsgFreeSharedData(VOID)
{
    PNET_DATA pNetData;
    PNCB_DATA pNcbData;
    DWORD i,j;

    if (pNetData = GlobalData.NetData) {
        for (i = 0; i < SD_NUMNETS() ; i++, pNetData++ ) {
            if (pNetData->NcbList) {
                for (j = 0; j < NCBMAX(i) ; j++ ) {
                    if (pNcbData = GETNCBDATA(i,j)) {
                        LocalFree(pNcbData);
                    }
                }
                LocalFree(pNetData->NcbList);
            }
        }
        LocalFree(GlobalData.NetData);
        GlobalData.NetData = NULL;
    }
}


NET_API_STATUS
MsgSetComputerName(
    IN DWORD NumNets
    )

/*++

Routine Description:

    This function sets up the shared data area for the computer name
    so that it can receive messages.

    This function sets things up so that the computer name will be able
    to receive messages.  First, it adds the user name form of the computer
    name to the local adapter.  If successful, it then initializes one slot
    in the name table in the shared data area: a computer name
    receiving messages

    SIDE EFFECTS

    Locks the init data segment around net bios usage.
    Calls the net bios.  Makes entries in the shared data area.


Arguments:

    NumNets - The number of network adapters that is supported

Return Value:

        0 = success
        non-zero = failure

--*/

{
    NET_API_STATUS          status = NERR_Success;
    NCB                     ncb;
    UCHAR                   res;
    DWORD                   i;
    unsigned short          j;

    struct {
        ADAPTER_STATUS  AdapterStatus;
        NAME_BUFFER     NameBuffer[16];
    } Astat;


    //
    // Loop for each net.
    //

    for ( i = 0; i < NumNets; i++ )
    {
        // NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW
        //
        // Reset the adapter
        //
        MSG_LOG1(TRACE,"Calling NetBiosReset for lana #%d\n",GETNETLANANUM(i));

        status = MsgsvcGlobalData->NetBiosReset(GETNETLANANUM(i));

        if (status != NERR_Success)
        {
            MSG_LOG(ERROR,"MsgSetComputerName: NetBiosReset failed "
                    FORMAT_API_STATUS "\n", status);
            MSG_LOG(ERROR,"MsgSetComputerName: AdapterNum " FORMAT_DWORD
                    "\n",i);
            //
            // If it fails, skip to the Next Net.
            //
            continue;
        }

        //
        //
        // NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW-NEW

        //
        // Set call name for local adapter
        //
        clearncb(&ncb);
        status = MsgFmtNcbName(ncb.ncb_name, machineName, 3);

        if (status != NERR_Success)
        {
            MSG_LOG1(ERROR, "SetComputerName: Format name failed!", status);
            return status;
        }

        ncb.ncb_command = NCBADDNAME;              // Add name (wait)
        ncb.ncb_lana_num = GETNETLANANUM(i);        // Use the LANMAN adapter

        //
        // Copy the name
        // (At this point the name is ansi - not unicode)
        //
        memcpy(SD_NAMES(i,0), ncb.ncb_name, NCBNAMSZ);

        if (g_IsTerminalServer)
        {
            MSG_LOG(TRACE,"SetComputerName: Adding session EVERYBODY_SESSION_ID in the list\n",0);
            MsgAddSessionInList(&(SD_SIDLIST(i,0)),(ULONG)EVERYBODY_SESSION_ID);
        }

        MSG_LOG1(TRACE,"MsgSetComputerName: Adding ComputerName to lana #%d\n",
            GETNETLANANUM(i));

        res = Msgsendncb( &ncb, i);

        //
        // If the lana is being reinitialized we need to force a reset
        //
        if ((res & 0xff) == NRC_ENVNOTDEF)
        {
            MSG_LOG1(TRACE,"SetComputerName: NetBios ADDNAME failed 0x%x - doing reset\n",res);
            status = NetpNetBiosReset(GETNETLANANUM(i));

            if (status == NERR_Success)
            {
                //
                // rebuild the add name request
                //
                clearncb(&ncb);
                status = MsgFmtNcbName(ncb.ncb_name, machineName, 3);

                if (status != NERR_Success)
                {
                    MSG_LOG1(ERROR, "SetComputerName: Format name failed %d!", status);
                    return status;
                }

                ncb.ncb_command = NCBADDNAME;              // Add name (wait)
                ncb.ncb_lana_num = GETNETLANANUM(i);       // Use the LANMAN adapter

                MSG_LOG1(TRACE,"MsgSetComputerName: Adding ComputerName<03> to lana #%d\n",
                         GETNETLANANUM(i));

                res = Msgsendncb(&ncb, i);  // reissue the ncb
            }
        }

        if(res != 0)
        {
            MSG_LOG1(TRACE,"SetComputerName: NetBios ADDNAME failed 0x%x\n",res);

            if((res & 0xff) == NRC_DUPNAME)
            {
                //
                // If the name already exists on the adapter card (the
                // workstation may have added it), we want to get the
                // name number and pretend that we just added it.
                //
                // Name already exists. Issue an ASTAT to find the name
                // number.
                //
                clearncb(&ncb);
                ncb.ncb_buffer = (char FAR *) &Astat;   // Set buffer address
                ncb.ncb_length = sizeof(Astat);         // Set buffer length
                ncb.ncb_callname[0] = '*';              // local adapter status
                ncb.ncb_command = NCBASTAT;             // Adapter status (wait)

                res = Msgsendncb(&ncb,i);
                if( res != NRC_GOODRET)
                {
                    //
                    // Failed to add name
                    //
                    MSG_LOG1(ERROR, "SetComputerName:sendncb (ASTAT) failed 0x%x\n", res);
                    return MsgMapNetError(res);
                }

                //
                // Loop to name number
                //
                for(j = 0; j< Astat.AdapterStatus.name_count; ++j)
                {
                    if (((Astat.NameBuffer[j].name_flags & 7) == 4)
                          &&
                        (memcmp( Astat.NameBuffer[j].name,
                                 SD_NAMES(i,0),
                                 NCBNAMSZ) == 0))
                    {
                        break;            // Found the name
                    }
                }

                if (j == Astat.AdapterStatus.name_count)
                {
                    //
                    // Failed to find
                    //

                    MSG_LOG(ERROR,
                        "SetComputerName:DupName-failed to find NameNum\n",0);

                    return NERR_NoComputerName;
                }

                SD_NAMENUMS(i,0) = Astat.NameBuffer[j].name_num; // Save num
                MSG_LOG1(TRACE,"SetComputerName: use existing name num (%d) instead\n",
                    Astat.NameBuffer[j].name_num);
            }
            else
            {
                //
                // Fail if name not on the card after the call
                //
                MSG_LOG(ERROR, "SetComputerName:Name Not on Card. netbios rc = 0x%x\n",res);
                return NERR_NoComputerName;
            }
        }
        else
        {
            SD_NAMENUMS(i,0) = ncb.ncb_num; // Save the name number
        }


        SD_NAMEFLAGS(i,0) = NFNEW | NFMACHNAME;   // Name is new


    }   // End for all nets

    return NERR_Success;
}


DWORD
MsgGetNumNets(VOID)

/*++

Routine Description:



Arguments:



Return Value:



--*/
{

    NCB             ncb;
    LANA_ENUM       lanaBuffer;
    unsigned char   nbStatus;

    //
    // Find the number of networks by sending an enum request via Netbios.
    //

    clearncb(&ncb);
    ncb.ncb_command = NCBENUM;          // Enumerate LANA nums (wait)
    ncb.ncb_buffer = (char FAR *)&lanaBuffer;
    ncb.ncb_length = sizeof(LANA_ENUM);

    nbStatus = Netbios (&ncb);
    if (nbStatus != NRC_GOODRET) {
        MSG_LOG(ERROR, "GetNumNets:Netbios LanaEnum failed rc="
                FORMAT_DWORD "\n", (DWORD) nbStatus);
        return(FALSE);
    }

    return((DWORD)lanaBuffer.length);

#ifdef replaced

    LPBYTE          transportInfo;
    int             count=0;
    USHORT          loopback_found = 0;
    NET_API_STATUS  status;
    DWORD           entriesRead;
    DWORD           totalEntries;

    //
    // First try and find the networks mananged by the LAN manager
    //
    // NOTE: This call will fail if there are more than MSNGR_MAX_NETS
    // in the machine.    This is not a problem unless there are fewer
    // than MSNGR_MAX_NETS that would qualify for messaging service.
    // In this case, it might be argued that the messenger should start.
    // For now, this is not the case. - ERICPE
    //

    status = NetWkstaTransportEnum (
                NULL,               // server name (local)
                0,                  // level
                &transportInfo,     // bufptr
                -1,                 // preferred maximum length
                &entriesRead,       // entries read
                &totalEntries,      // total entries
                NULL);              // resumeHandle

    //
    // Free up the buffer that RPC allocated for us.
    //
    NetApiBufferFree(transportInfo);

    if (status != NERR_Success) {
        MSG_LOG(ERROR,"GetNumNets:NetWkstaTransportEnum failed "
                FORMAT_API_STATUS "\n", status);
        return(0);
    }
    MSG_LOG(TRACE,"GetNumNets: numnets = " FORMAT_DWORD "\n", totalEntries);

    return(totalEntries);
#endif
}

NET_API_STATUS
MsgGetBufSize (
    OUT LPDWORD bufferSize
    )

/*++

Routine Description:

    This routine fills in the default buffer size

Arguments:

    bufferSize - This is a pointer to where the buffer size is to be stored.

Return Value:

    NERR_Success - No errors, the returned bufferSize is valid.

--*/
{
    //
    // Use the default.
    //

    *bufferSize = 8192;

    return NERR_Success;
}


DWORD
MsgSetUpMessageFile (
    VOID
    )

/*++

Routine Description:

    Builds the name of the message file that is to be used in any
    subsequent DosGetMessage calls.  The name is built in the Global
    variable MessageFileName.

Arguments:

    none

Return Value:

    NERR_Success - The operation was successful

    ERROR_NOT_ENOUGH_MEMORY - Couldn't allocate memory for MessageFileName.


--*/

{

    //
    // allocate some space for the message file name to be built.
    //
    MessageFileName = (LPTSTR)LocalAlloc(LMEM_ZEROINIT, (MSGFILENAMLEN+sizeof(TCHAR)));

    if (MessageFileName == NULL) {
        MSG_LOG(ERROR,"[MSG]SetUpMessageFile:LocalAlloc Failure "
                FORMAT_API_STATUS "\n", GetLastError());
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    //
    // This message filename (netmsg.dll) is defined in lmcons.h
    //

    STRCPY(MessageFileName,MESSAGE_FILENAME);

    return (NERR_Success);

}

STATIC VOID
MsgInitMessageBoxTitle(
    VOID
    )

/*++

Routine Description:

    Obtains the title text for the message box used to display messages.
    If the title is successfully obtained from the message file, then
    that title is pointed to by GlobalAllocatedMsgTitle and
    GlobalMessageBoxTitle.  If unsuccessful, then GlobalMessageBoxTitle
    left pointing to the DefaultMessageBoxTitle.

    NOTE:  If successful, a buffer is allocated by this function.  The
    pointer stored in GlobalAllocatedMsgTitle and it should be freed when
    done with this buffer.

Arguments:

Return Value:

    none

--*/
{
    LPVOID      hModule;
    DWORD       msgSize;
    DWORD       status=NO_ERROR;

    GlobalAllocatedMsgTitle = NULL;

    hModule = LoadLibrary( L"netmsg.dll");
    if ( hModule == NULL) {
        status = GetLastError();
        MSG_LOG1(ERROR, "LoadLibrary() fails with winError = %d\n", GetLastError());
        return;
    }

    msgSize = FormatMessageW(
                FORMAT_MESSAGE_FROM_HMODULE |       //  dwFlags
                  FORMAT_MESSAGE_ARGUMENT_ARRAY |
                  FORMAT_MESSAGE_ALLOCATE_BUFFER,
                hModule,
                MTXT_MsgsvcTitle,                   //  MessageId
                0,                                  //  dwLanguageId
                (LPWSTR)&GlobalAllocatedMsgTitle,   //  lpBuffer
                0,                                  //  nSize
                NULL);

    if (msgSize == 0) {
        status = GetLastError();
        MSG_LOG1(ERROR,"Could not find MessageBox title in a message file %d\n",
        status);
    }
    else {
        GlobalMessageBoxTitle = GlobalAllocatedMsgTitle;
    }

    //
    // Get the messages as Ansi since we'll be comparing them to an
    // Ansi message that comes in from a remote Alerter service.
    //

    msgSize = FormatMessageA(
                FORMAT_MESSAGE_FROM_HMODULE |       //  dwFlags
                  FORMAT_MESSAGE_IGNORE_INSERTS |
                  FORMAT_MESSAGE_ALLOCATE_BUFFER,
                hModule,
                APE2_ALERTER_PRINTING_SUCCESS,      //  MessageId
                0,                                  //  dwLanguageId
                (LPSTR) &g_lpAlertSuccessMessage,   //  lpBuffer
                0,                                  //  nSize
                NULL);

    if (msgSize == 0)
    {
        // No loss -- we just won't be able to filter print success alerts

        status = GetLastError();
        MSG_LOG1(ERROR,
                 "Could not find Alerter print success message %d\n",
                 status);
    }
    else
    {
        //
        // Trim the message to end after the "Printing Complete" line.
        //

        LPSTR  lpTemp = g_lpAlertSuccessMessage;

        g_dwAlertSuccessLen = 0;

        while (*lpTemp && *lpTemp != '\r')
        {
            lpTemp++;
            g_dwAlertSuccessLen++;
        }

        *lpTemp = '\0';
    }

    msgSize = FormatMessageA(
                FORMAT_MESSAGE_FROM_HMODULE |       //  dwFlags
                  FORMAT_MESSAGE_IGNORE_INSERTS |
                  FORMAT_MESSAGE_ALLOCATE_BUFFER,
                hModule,
                APE2_ALERTER_PRINTING_FAILURE,      //  MessageId
                0,                                  //  dwLanguageId
                (LPSTR) &g_lpAlertFailureMessage,   //  lpBuffer
                0,                                  //  nSize
                NULL);

    if (msgSize == 0)
    {
        // No loss -- we just won't be able to filter print success alerts

        status = GetLastError();
        MSG_LOG1(ERROR,
                 "Could not find Alerter print failure message %d\n",
                 status);
    }
    else
    {
        //
        // Trim the message to end after the "Printing Complete" line.
        //

        LPSTR  lpTemp = g_lpAlertFailureMessage;

        g_dwAlertFailureLen = 0;

        while (*lpTemp && *lpTemp != '\r')
        {
            lpTemp++;
            g_dwAlertFailureLen++;
        }

        *lpTemp = '\0';
    }

    FreeLibrary(hModule);
    return;
}


VOID
MsgInitEndpoint(
    PVOID   Context              // This passed in as context.
    )

/*++

Routine Description:

    This function is called to initialize our RPC server entry point. We
    do this in a separate thread because we may have to wait because
    RpcSS is not ready yet.

Arguments:

    Context - Context parameter

Return Value:

    None

--*/
{
    RPC_BINDING_VECTOR *bindingVector = NULL;
    DWORD status, tries;

    MSG_LOG(TRACE, "MsgInitEndpoint starting in separate thread\n",0);

    //
    // Create endpoint for receiving RPC calls
    // This is for netbiosless notifications
    //

    for( tries = 0; tries < 3; tries++ ) {

        status = RpcServerUseProtseq(
            TEXT("ncadg_ip_udp"),
            RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
            NULL         // Do we need an empty SD here?
            );

        if ( (status == RPC_S_OK) || (status == RPC_S_DUPLICATE_ENDPOINT) ) {
            break;
        }

        MSG_LOG(ERROR, "RPC Init (UseProt Udp) Failed "
                FORMAT_RPC_STATUS " - trying again\n", status);
        Sleep( 30 * 1000 );
    }

    if ( (status != RPC_S_OK) && (status != RPC_S_DUPLICATE_ENDPOINT) ) {
        if (status == RPC_S_SERVER_UNAVAILABLE) {
            MSG_LOG( ERROR, "Failed to use UDP, check RPCSS service\n",0 );
        } else {
            MSG_LOG( ERROR, "Failed to use UDP, check TCP/IP\n",0 );
        }
        // give up
        return;
    }

    status = RpcServerInqBindings(
        &bindingVector
        );
    if (status != ERROR_SUCCESS) {
        MSG_LOG( ERROR, "RpcServerInqBindings failed with %d\n",status );
        return;
    }

    // Try to register in a loop in case RPCSS is not running yet

    for( tries = 0; tries < 3; tries++ ) {

        status = RpcEpRegister(
            msgsvcsend_ServerIfHandle,
            bindingVector,
            NULL,
            TEXT("Messenger Service")
            );

        if (status == RPC_S_OK) {
            break;
        }

        MSG_LOG( ERROR, "Msgr: RpcEpRegister failed with %d - trying again\n", status );

        RpcEpUnregister( msgsvcsend_ServerIfHandle,
                         bindingVector,
                         NULL );
        // ignore error

        Sleep( 10 * 1000 );
    }

    RpcBindingVectorFree( &bindingVector );

    if (status != RPC_S_OK) {
        // give up
        return;
    }

    //
    // Register RPC interface
    //

    status = RpcServerRegisterIf(
        msgsvcsend_ServerIfHandle,  // interface to register
        NULL,   // MgrTypeUuid
        NULL);  // MgrEpv; null means use default

    if (status != RPC_S_OK) {
        MSG_LOG(ERROR, "RPC Init (RegIf MsgSvcSend) Failed "
                FORMAT_RPC_STATUS "\n", status);
        return;
    }

    MSG_LOG(TRACE, "MsgInitEndpoint final status %d\n", status);

    return;
}
