/*++

Copyright (c) 1994  Microsoft Corporation

Module Name:

    svcsrv.cxx

Abstract:

    Contains server side code of service location protocol.

Author:

    Madan Appiah (madana)  15-May-1995

Environment:

    User Mode - Win32

Revision History:

--*/

#include <svcloc.hxx>

DWORD
MakeResponseBuffer(
    VOID
    )
/*++

Routine Description:

    This function sets up the response buffer sent to the client.

Arguments:

    NONE.

Return Value:

    Windows Error Code.

--*/
{
    DWORD ReqMsgLength;

    //
    // compute space required for all services info and server info.
    //

    ReqMsgLength = GlobalSrvInfoObj->ComputeResponseLength();

    //
    // add space for the head and tail.
    //

    ReqMsgLength += (
        sizeof(DWORD) + // for message length.
        sizeof(DWORD) + // for check sum
        sizeof(DWORD)); // for termination dword.

    if( ReqMsgLength <= GlobalSrvAllotedRespMsgLen ) {

        //
        // wipe of previous response buffer content.
        //

        TcpsvcsDbgAssert(
            GlobalSrvRespMsgLength <=
                GlobalSrvAllotedRespMsgLen );

        memset( GlobalSrvRespMsg, 0x0, GlobalSrvRespMsgLength );
        GlobalSrvRespMsgLength = ReqMsgLength;
    }
    else {

        LPBYTE NewBuffer;
        DWORD NewBufferLength;

        NewBufferLength = (ReqMsgLength & ~(0x400 - 1)) + 0x400;
            // ceil in KBytes

        NewBuffer = (LPBYTE)SvclocHeap->Alloc( NewBufferLength );

        if( NewBuffer == NULL ) {
            return( ERROR_NOT_ENOUGH_MEMORY );
        }

        //
        // free old buffer.
        //

        SvclocHeap->Free( GlobalSrvRespMsg );
        GlobalSrvRespMsg = NewBuffer;
        GlobalSrvAllotedRespMsgLen = NewBufferLength;
        GlobalSrvRespMsgLength =  ReqMsgLength;
    }

    //
    // copy server and services info.
    //

    DWORD Error;

    Error = GlobalSrvInfoObj->MakeResponseMessage(
                    GlobalSrvRespMsg + sizeof(DWORD),
                    GlobalSrvRespMsgLength - 3 * sizeof(DWORD) );

    if( Error != ERROR_SUCCESS ) {
        GlobalSrvRespMsgLength = 0;
        return( Error );
    }

    *(DWORD *)GlobalSrvRespMsg = GlobalSrvRespMsgLength;

    LPBYTE EndMessageBuffer;

    EndMessageBuffer = GlobalSrvRespMsg + GlobalSrvRespMsgLength;

    //
    // fill in check sum.
    //

    *(DWORD *)(EndMessageBuffer - 2 * sizeof(DWORD) ) =
        ComputeCheckSum(
            GlobalSrvRespMsg + sizeof(DWORD),
            GlobalSrvRespMsgLength - 3 * sizeof(DWORD) );

    *(DWORD *)(EndMessageBuffer - sizeof(DWORD) ) = 0xFFFFFFFF;

    //
    // DONE.
    //

    return( ERROR_SUCCESS );
}

DWORD
ServerRegisterAndListen(
    VOID
    )
/*++

Routine Description:

    This function call registers the server and creates a thread to listen
    to the discovery requests. Also it initializes the winsock data structures.

    ASSUME : global data crit sect is locked.

Arguments:

    none.

Return Value:

    Windows Error Code.

--*/
{
    DWORD Error;
    SOCKET IpxSocket;
    int SocketAddressLength;

    SOCKADDR_IPX IpxSocketAddress;
    SERVICE_INFOA ServiceInfo;
    SERVICE_ADDRESSES ServiceAddresses;
    PSERVICE_ADDRESS ServiceAddr;
    DWORD StatusSetService;

    CHAR SapSvcName[SAP_SERVICE_NAME_LEN + 1];

    //
    // init winsock.
    //

    if ( !GlobalWinsockStarted ) {

        Error = WSAStartup( WS_VERSION_REQUIRED, &GlobalWinsockStartupData );

        if( Error != ERROR_SUCCESS ) {
            goto Cleanup;
        }

        GlobalWinsockStarted = TRUE;
    }

    //
    // cleanup global socket array.
    //

    memset( &GlobalSrvSockets, 0x0, sizeof(GlobalSrvSockets) );

    //
    // make IPX local socket address.
    //

    IpxSocket = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX);

    if( IpxSocket == INVALID_SOCKET ) {
        Error = WSAGetLastError();
        goto NBRegister;
    }

    //
    // make this socket non blocking.
    //

    DWORD Arg;
    Arg = 1;
    if( (ioctlsocket( IpxSocket, FIONBIO, &Arg )) == SOCKET_ERROR ) {

        Error = WSAGetLastError();
        closesocket( IpxSocket );
        goto NBRegister;
    }

    //
    // get socket address.
    //

    memset( &IpxSocketAddress, 0x0, sizeof(SOCKADDR_IPX) );

    IpxSocketAddress.sa_family = PF_IPX;

    if( bind(
            IpxSocket,
            (PSOCKADDR)&IpxSocketAddress,
            sizeof(SOCKADDR_IPX) ) == SOCKET_ERROR ) {

        Error = WSAGetLastError();
        closesocket( IpxSocket );
        goto NBRegister;
    }

    //
    // now query address.
    //

    SocketAddressLength = sizeof( IpxSocketAddress );

    if( getsockname(
            IpxSocket,
            (PSOCKADDR)&IpxSocketAddress,
            &SocketAddressLength ) == SOCKET_ERROR ) {

        Error = WSAGetLastError();
        closesocket( IpxSocket );
        goto NBRegister;
    }

    Error = MakeSapServiceName( SapSvcName, sizeof(SapSvcName) );

    if( Error != ERROR_SUCCESS ) {
        closesocket( IpxSocket );
        goto NBRegister;
    }

    //
    // prepare service address.
    //

    ServiceAddresses.dwAddressCount = 1;
    ServiceAddr = &ServiceAddresses.Addresses[0];

    ServiceAddr->dwAddressType = PF_IPX;
    ServiceAddr->dwAddressFlags = 0;
    ServiceAddr->dwAddressLength = SocketAddressLength;
    ServiceAddr->dwPrincipalLength = 0;
    ServiceAddr->lpAddress = (LPBYTE)&IpxSocketAddress;
    ServiceAddr->lpPrincipal = NULL;

    //
    // prepare service info.
    //

    ServiceInfo.lpServiceType = &GlobalSapGuid;
    ServiceInfo.lpServiceName = SapSvcName;
    ServiceInfo.lpComment = NULL ;
    ServiceInfo.lpLocale = 0;
    ServiceInfo.dwDisplayHint = 0;
    ServiceInfo.dwVersion =
        MAKEWORD( INET_MAJOR_VERSION, INET_MINOR_VERSION ) ;
    ServiceInfo.dwTime = 0; // ??
    ServiceInfo.lpMachineName = GlobalComputerName;
    ServiceInfo.lpServiceAddress = &ServiceAddresses;
    ServiceInfo.ServiceSpecificInfo.pBlobData = 0;
    ServiceInfo.ServiceSpecificInfo.cbSize = 0;

    //
    // register service info.
    //

    if( SetServiceA(
            NS_SAP,
            SERVICE_REGISTER,
            0,
            &ServiceInfo,
            0,
            &StatusSetService ) == SOCKET_ERROR ) {

        Error = WSAGetLastError();
        closesocket( IpxSocket );
        goto NBRegister;
    }

    if( StatusSetService != ERROR_SUCCESS ) {
        Error = StatusSetService;
        closesocket( IpxSocket );
        goto NBRegister;
    }

    GlobalRNRRegistered = TRUE;

    //
    // remember this Ipx socket in our global socket array.
    //

    FD_SET( IpxSocket, &GlobalSrvSockets );
    Error = ERROR_SUCCESS;

NBRegister:

    if( Error != ERROR_SUCCESS ) {

        TcpsvcsDbgPrint(( DEBUG_ERRORS,
            "Ipx Registration failed, %ld.\n", Error ));
    }

    //
    // now create sockets for NET BIOS 1C group name and UNIQUE server
    // name.
    //

    SOCKADDR_NB NB1CSocketAddress;
    SOCKADDR_NB NBUniqueSocketAddress;
    SOCKET NBSocket;

    memset( &NB1CSocketAddress, 0x0, sizeof(SOCKADDR_NB) );
    NB1CSocketAddress.snb_family = AF_NETBIOS;
    NB1CSocketAddress.snb_type = TDI_ADDRESS_NETBIOS_TYPE_GROUP;

    TcpsvcsDbgAssert(
        sizeof(NB1CSocketAddress.snb_name) >=
            NETBIOS_INET_GROUP_NAME_LEN );

    memcpy(
        NB1CSocketAddress.snb_name,
        NETBIOS_INET_GROUP_NAME,
        NETBIOS_INET_GROUP_NAME_LEN );

    memset( &NBUniqueSocketAddress, 0x0, sizeof(SOCKADDR_NB) );
    NBUniqueSocketAddress.snb_family = AF_NETBIOS;
    NBUniqueSocketAddress.snb_type = TDI_ADDRESS_NETBIOS_TYPE_UNIQUE;

    TcpsvcsDbgAssert( GlobalComputerName[0] != '\0' );

    MakeUniqueServerName(
        (LPBYTE)NBUniqueSocketAddress.snb_name,
        sizeof(NBUniqueSocketAddress.snb_name),
        GlobalComputerName );

    //
    // enumurate lanas first.
    //

    LANA_ENUM Lanas;

    if( GetEnumNBLana( &Lanas ) ) {

        //
        // try only the lanas that are returned.
        //

        TcpsvcsDbgAssert( Lanas.length > 0 );

        DWORD i;
        for( i = 0; i < Lanas.length; i++ ) {

            //
            // create a socket for this lana to bind with the IC name.
            //

            if ( MakeNBSocketForLana(
                    Lanas.lana[i],
                    (PSOCKADDR)&NB1CSocketAddress,
                    &NBSocket ) ) {

                //
                // add the sockets to our global list.
                //

                FD_SET( NBSocket, &GlobalSrvSockets );
            }

            //
            // now create a socket for this lana to bind with the unique
            // server name.
            //

            if ( MakeNBSocketForLana(
                    Lanas.lana[i],
                    (PSOCKADDR)&NBUniqueSocketAddress,
                    &NBSocket ) ) {

                //
                // add the sockets to our global list.
                //

                FD_SET( NBSocket, &GlobalSrvSockets );
            }
        }
    }
    else {

        UCHAR Lana;

        //
        // try all possible lanas and accept all valid lana sockets.
        //

        for( Lana = 0; Lana < MAX_LANA ; Lana-- ) {

            //
            // create a socket for this lana to bind with the IC name.
            //

            if ( MakeNBSocketForLana(
                    Lana,
                    (PSOCKADDR)&NB1CSocketAddress,
                    &NBSocket ) ) {

                //
                // add the sockets to our global list.
                //

                FD_SET( NBSocket, &GlobalSrvSockets );
            }

            //
            // now create a socket for this lana to bind with the unique
            // server name.
            //

            if ( MakeNBSocketForLana(
                    Lana,
                    (PSOCKADDR)&NBUniqueSocketAddress,
                    &NBSocket ) ) {

                //
                // add the sockets to our global list.
                //

                FD_SET( NBSocket, &GlobalSrvSockets );
            }
        }
    }

    //
    // we should have at least one socket to listen on, otherwise,
    // it is an error, return so.
    //

    TcpsvcsDbgAssert( GlobalSrvSockets.fd_count > 0 );

    if( GlobalSrvSockets.fd_count == 0 ) {
        Error = ERROR_NO_NETWORK;
        goto Cleanup;
    }

    //
    // create listen thread.
    //

    DWORD ThreadId;

    GlobalSrvListenThreadHandle =
        CreateThread(
            NULL,       // default security
            0,          // default stack size
            (LPTHREAD_START_ROUTINE)SocketListenThread,
            NULL,          // no parameter
            0,          // creatation flag, no suspend
            &ThreadId );

    if( GlobalSrvListenThreadHandle  == NULL ) {
        Error = GetLastError();
        goto Cleanup;
    }

    Error = ERROR_SUCCESS;

Cleanup:

    if( Error != ERROR_SUCCESS ) {

        TcpsvcsDbgPrint(( DEBUG_ERRORS,
            "ServerRegisterAndListen failed, %ld.\n", Error ));

        //
        // if we are not successful, cleanup
        // before we return.
        //

        DWORD LocalError;

        LocalError = ServerDeregisterAndStopListen();

        TcpsvcsDbgAssert( LocalError == ERROR_SUCCESS );
    }

    return( Error );
}

DWORD
ProcessSvclocQuery(
    SOCKET ReceivedSocket,
    LPBYTE ReceivedMessage,
    DWORD ReceivedMessageLength,
    struct sockaddr *SourcesAddress,
    DWORD SourcesAddressLength
    )
/*++

Routine Description:

    This function processes the query message and sends response to the
    query.

    The query message format.

    1st DWORD : message length.
    2nd DWORD : message version.
    3rd DWORD : services mask the client interested in.
    4th DWORD : client name
    ..
    ..

    Last but one DWORD : check sum.
    LAST DWORD : terminating DWORD 0xFFFFFFFF

Arguments:

    ReceivedMessage - pointer to a message buffer.

    ReceivedMessageLength - length of the above message.

    SourcesAddress - address of the sender.

    SourcesAddressLength - length of the above address.

Return Value:

    Windows Error Code.

--*/
{
    DWORD Error;
    LPBYTE MessagePtr;
    LPBYTE MessageEndPtr;

    MessagePtr = ReceivedMessage;
    MessageEndPtr = ReceivedMessage + ReceivedMessageLength;

    //
    // message length should be multiple of sizeof(DWORD).
    //

    if( ReceivedMessageLength % sizeof(DWORD) != 0 ) {
        return( ERROR_INVALID_PARAMETER );
    }

    //
    // message length should match.
    //

    DWORD MsgLength;

    MsgLength = *(DWORD *)MessagePtr;
    MessagePtr += sizeof(DWORD);

    if( MsgLength != ReceivedMessageLength ) {
        return( ERROR_INVALID_PARAMETER );
    }

    //
    // message should terminate with 0xFFFFFFFF
    //

    if( *(DWORD *)(MessageEndPtr - sizeof(DWORD)) != 0xFFFFFFFF ) {
        return( ERROR_INVALID_PARAMETER );
    }

    //
    // verify checksum.
    //

    DWORD CheckSum;

    CheckSum = ComputeCheckSum(
            ReceivedMessage + sizeof(DWORD),
            ReceivedMessageLength - (3 * sizeof(DWORD)) );

    if( CheckSum != *(DWORD *)(MessageEndPtr - 2 * sizeof(DWORD)) ) {
        return( ERROR_INVALID_PARAMETER );
    }

    //
    // validate version number.
    //

    INET_VERSION_NUM VersionNumber;

    VersionNumber.VersionNumber = *(DWORD *)MessagePtr;
    MessagePtr += sizeof(DWORD);

    if( (VersionNumber.Version.Major != INET_MAJOR_VERSION) ||
        (VersionNumber.Version.Minor != INET_MINOR_VERSION) ) {

        //
        // in future, we can apply different logic to reject messages with
        // differnet version number.
        //

        return( ERROR_INVALID_PARAMETER );
    }

    //
    // get services mask.
    //

    ULONGLONG ServicesMask;

    ServicesMask = *(ULONGLONG *)MessagePtr;
    MessagePtr += sizeof(ULONGLONG);

    //
    // if we aren't supporting any of the clients requested services,
    // ignore these message.
    //

    if( (ServicesMask & GlobalSrvInfoObj->GetServicesMask()) == 0) {
        return( ERROR_INVALID_PARAMETER );
    }

    //
    // retrieve the computer name from the message. currently
    // it has been used just for debugging purpose. It can be used for
    // something else in future.
    //

    LPSTR ClientComputerName;
    LPSTR ComputerNamePtr;

    ClientComputerName = (LPSTR)MessagePtr;

    //
    // validate computer name.
    //

    ComputerNamePtr = ClientComputerName;
    while( (*ComputerNamePtr != '\0') &&
                ((LPBYTE)ComputerNamePtr < MessageEndPtr) ) {
        ComputerNamePtr++;
    }

    if( (LPBYTE)ComputerNamePtr >= MessageEndPtr ) {
        return( ERROR_INVALID_PARAMETER );
    }

    TcpsvcsDbgPrint((
        DEBUG_SVCLOC_MESSAGE,
            "Received a valid discovery message from, %ws.\n",
                ClientComputerName ));

    //
    // send response message to client.
    //

    LOCK_SVC_GLOBAL_DATA();

    TcpsvcsDbgAssert( GlobalSrvRespMsgLength != 0 );

    if( GlobalSrvRespMsgLength != 0 ) {
        Error = sendto(
                    ReceivedSocket,
                    (LPCSTR)GlobalSrvRespMsg,
                    GlobalSrvRespMsgLength,
                    0,
                    SourcesAddress,
                    SourcesAddressLength );


        if( Error == SOCKET_ERROR ) {

            Error = WSAGetLastError();
            TcpsvcsDbgPrint(( DEBUG_ERRORS, "sendto failed, %ld.\n", Error ));
        }
        else {
            Error = ERROR_SUCCESS;
        }
    }
    else {

        TcpsvcsDbgPrint(( DEBUG_ERRORS,
            "GlobalSrvRespMsgLength is zero.\n" ));
        Error = ERROR_INVALID_PARAMETER;
    }

    UNLOCK_SVC_GLOBAL_DATA();
    return( Error );
}

VOID
SocketListenThread(
    LPVOID Parameter
    )
/*++

Routine Description:

    This function is the main thread for the service location protocol. It
    receives messages that arrive from all sockets (that have
    been established by the ServerRegisterAndListen call) and response to
    each of the messages. This thread will stop when all sockets are
    closed by the ServerDeregisterAndStopListen call.

Arguments:

    none.

Return Value:

    none.

--*/
{

    fd_set ReadSockets;
    int NumReadySockets;
    DWORD Error;
    BOOL BoolError;

    //
    // increase the priority of this thread, so that it could handle as
    // many discovery queries as possible.
    //

    BoolError = SetThreadPriority(
                    GetCurrentThread(),
                    THREAD_PRIORITY_ABOVE_NORMAL );

    if(BoolError) {

        TcpsvcsDbgPrint(( DEBUG_ERRORS,
            "SetThreadPriority, %ld.\n", GetLastError() ));
    }

    for( ;; ) {

        //
        // COMMENT
        // select on all above sockets.
        //

        //
        // copy all sockets.
        //

        LOCK_SVC_GLOBAL_DATA();
        memcpy(&ReadSockets, &GlobalSrvSockets, sizeof(GlobalSrvSockets));
        UNLOCK_SVC_GLOBAL_DATA();

        //
        // do select now.
        //

        NumReadySockets =
            select(
                0,  // compatibility argument, ignored.
                &ReadSockets, // sockets to test for readability.
                NULL,   // no write wait
                NULL,   // no error check.
                NULL ); // NO timeout.

        if( NumReadySockets == SOCKET_ERROR ) {

            //
            // ALL sockets are closed and we are asked to return or
            // something else has happpened which we can't handle.
            //

            Error = WSAGetLastError();
            goto Cleanup;
        }

        TcpsvcsDbgAssert( (DWORD)NumReadySockets == ReadSockets.fd_count );

        DWORD i;
        for( i = 0; i < (DWORD)NumReadySockets; i++ ) {

            DWORD ReadMessageLength;
            struct sockaddr_nb SourcesAddress;
            int SourcesAddressLength;

            //
            // read next message.
            //

            SourcesAddressLength = sizeof(SourcesAddress);
            ReadMessageLength =
                recvfrom(
                    ReadSockets.fd_array[i],
                    (LPSTR)GlobalSrvRecvBuf,
                    GlobalSrvRecvBufLength,
                    0,
                    (struct sockaddr *)&SourcesAddress,
                    &SourcesAddressLength );

            if( ReadMessageLength == SOCKET_ERROR ) {

                //
                // ALL sockets are closed and we are asked to return or
                // something else has happpened which we can't handle.
                //

                Error = WSAGetLastError();
                continue;
            }

            TcpsvcsDbgAssert( ReadMessageLength <= GlobalSrvRecvBufLength );

            //
            // received a message.
            //

            TcpsvcsDbgPrint((
                DEBUG_SVCLOC_MESSAGE,
                    "Received a discovery message, %ld.\n",
                        ReadMessageLength ));

            Error = ProcessSvclocQuery(
                            ReadSockets.fd_array[i],
                            GlobalSrvRecvBuf,
                            (DWORD)ReadMessageLength,
                            (struct sockaddr *)&SourcesAddress,
                            (DWORD)SourcesAddressLength );

            if( Error != ERROR_SUCCESS ) {
                TcpsvcsDbgPrint(( DEBUG_ERRORS,
                    "SendSvclocResponse failed, %ld.\n", Error ));
            }
        }
    }

Cleanup:

    TcpsvcsDbgPrint(( DEBUG_ERRORS,  "SocketListenThread returning, %ld.\n", Error ));
    return;
}

DWORD
ServerDeregisterAndStopListen(
    VOID
    )
/*++

Routine Description:

    This function call stops the discovery thread and deregisters the
    server name. Also it cleans up the winsock data structures.

    ASSUME : global data crit sect is locked.

Arguments:

    none.

Return Value:

    Windows Error Code.

--*/
{
    DWORD Error;
    DWORD i;

    //
    // close all sockets, if any opened.
    //

    for( i = 0; i < GlobalSrvSockets.fd_count; i++ ) {

        Error = closesocket( GlobalSrvSockets.fd_array[i] );

        if( Error == SOCKET_ERROR ) {
            Error = WSAGetLastError();

            TcpsvcsDbgPrint(( DEBUG_ERRORS,
                "closesocket call failed, %ld\n", Error ));
        }
    }

    //
    // invalidate GlobalSrvSockets count
    //

    GlobalSrvSockets.fd_count = 0;

    //
    // closing all above sockets should stop the service location thread,
    // check to see this is the case and close the thread handle.
    //

    if( GlobalSrvListenThreadHandle != NULL ) {

        //
        // Wait for the service location thread to stop, but don't wait
        // for longer than THREAD_TERMINATION_TIMEOUT msecs (60 secs)
        //

        DWORD WaitStatus =
            WaitForSingleObject(
                    GlobalSrvListenThreadHandle,
                    THREAD_TERMINATION_TIMEOUT );

        TcpsvcsDbgAssert( WaitStatus != WAIT_FAILED );

        if(  WaitStatus == WAIT_FAILED ) {
            TcpsvcsDbgPrint((DEBUG_ERRORS,
                "WaitForSingleObject call failed, %ld\n", GetLastError() ));
        }

        CloseHandle( GlobalSrvListenThreadHandle );
        GlobalSrvListenThreadHandle = NULL;
    }

    //
    // deregister rnr.
    //

    if( GlobalRNRRegistered ) {

        SOCKADDR_IPX IpxSocketAddress;
        SERVICE_INFO ServiceInfo;
        SERVICE_ADDRESSES ServiceAddresses;
        PSERVICE_ADDRESS ServiceAddr;
        DWORD StatusSetService;
        CHAR SapSvcName[SAP_SERVICE_NAME_LEN + 1];
        CHAR *SapSvcNamePtr;

        Error = MakeSapServiceName( SapSvcName, sizeof(SapSvcName) );

        if( Error != ERROR_SUCCESS ) {
            SapSvcNamePtr = NULL;
        }
        else {
            SapSvcNamePtr = SapSvcName;
        }

        //
        // prepare service address.
        //

        ServiceAddresses.dwAddressCount = 1;
        ServiceAddr = &ServiceAddresses.Addresses[0];

        memset( &IpxSocketAddress, 0x0, sizeof(IpxSocketAddress) );

        ServiceAddr->dwAddressType = PF_IPX;
        ServiceAddr->dwAddressFlags = 0;
        ServiceAddr->dwAddressLength = sizeof( IpxSocketAddress );
        ServiceAddr->dwPrincipalLength = 0;
        ServiceAddr->lpAddress = (LPBYTE)&IpxSocketAddress;
        ServiceAddr->lpPrincipal = NULL;

        //
        // prepare service info.
        //

        ServiceInfo.lpServiceType = &GlobalSapGuid;
        ServiceInfo.lpServiceName = SapSvcNamePtr;
        ServiceInfo.lpComment = NULL ;
        ServiceInfo.lpLocale = 0;
        ServiceInfo.dwDisplayHint = 0;
        ServiceInfo.dwVersion =
            MAKEWORD( INET_MAJOR_VERSION, INET_MINOR_VERSION ) ;
        ServiceInfo.dwTime = 0; // ??
        ServiceInfo.lpMachineName = GlobalComputerName;
        ServiceInfo.lpServiceAddress = &ServiceAddresses;
        ServiceInfo.ServiceSpecificInfo.pBlobData = 0;
        ServiceInfo.ServiceSpecificInfo.cbSize = 0;

        //
        // register service info.
        //

        if( SetServiceA(
                NS_SAP,
                SERVICE_DEREGISTER,
                0,
                &ServiceInfo,
                0,
                &StatusSetService ) == SOCKET_ERROR ) {

            Error = WSAGetLastError();
            TcpsvcsDbgPrint(( DEBUG_ERRORS,
                "SetServiceW call failed, %ld\n", Error ));
        }

        GlobalRNRRegistered = FALSE;
    }

    //
    // DONE.
    //

     return( ERROR_SUCCESS );
}
