/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    network.c

Abstract:

    This module contains the network interface for the BINL server.

Author:

    Colin Watson (colinw)  2-May-1997

Environment:

    User Mode - Win32

Revision History:

--*/

#include "binl.h"
#pragma hdrstop

DWORD
BinlWaitForMessage(
    BINL_REQUEST_CONTEXT *pRequestContext
    )
/*++

Routine Description:

    This function waits for a request on the BINL port on any of the
    configured interfaces.

Arguments:

    RequestContext - A pointer to a request context block for
        this request.

Return Value:

    The status of the operation.

--*/
{
    DWORD       length;
    DWORD       error;
    fd_set      readSocketSet;
    DWORD       i;
    int         readySockets;
    struct timeval timeout = { 0x7FFFFFFF, 0 }; // forever.

    LPOPTION    Option;
    LPBYTE      EndOfScan;
    LPBYTE      MagicCookie;
    BOOLEAN     informPacket;

    #define CLIENTOPTIONSTRING "PXEClient"
    #define CLIENTOPTIONSIZE (sizeof(CLIENTOPTIONSTRING) - 1)

    //
    //  Loop until we get an extended DHCP request or an error
    //

    while (1) {

        //
        // Setup the file descriptor set for select.
        //

        FD_ZERO( &readSocketSet );
        for ( i = 0; i < BinlGlobalNumberOfNets ; i++ ) {
            if (BinlGlobalEndpointList[i].Socket) {
                FD_SET(
                    BinlGlobalEndpointList[i].Socket,
                    &readSocketSet
                    );
            }
        }

        readySockets = select( 0, &readSocketSet, NULL, NULL, &timeout );

        //
        // return to caller when the service is shutting down or select()
        // times out.
        //

        if( (readySockets == 0)  ||
            (WaitForSingleObject( BinlGlobalProcessTerminationEvent, 0 ) == 0) ) {

            return( ERROR_SEM_TIMEOUT );
        }

        if( readySockets == SOCKET_ERROR) {
            continue;   //  Closed the DHCP socket?
        }

        //
        // Time to play 20 question with winsock.  Which socket is ready?
        //

        pRequestContext->ActiveEndpoint = NULL;

        for ( i = 0; i < BinlGlobalNumberOfNets ; i++ ) {
            if ( FD_ISSET( BinlGlobalEndpointList[i].Socket, &readSocketSet ) ) {
                pRequestContext->ActiveEndpoint = &BinlGlobalEndpointList[i];
                break;
            }
        }


        //BinlAssert(pRequestContext->ActiveEndpoint != NULL );
        if ( pRequestContext->ActiveEndpoint == NULL ) {
            return ERROR_SEM_TIMEOUT;
        }


        //
        // Read data from the net.  If multiple sockets have data, just
        // process the first available socket.
        //

        pRequestContext->SourceNameLength = sizeof( struct sockaddr );

        //
        // clean the receive buffer before receiving data in it. We clear
        // out one more byte than we actually hand to recvfrom, so we can
        // be sure the message has a NULL after it (in case we do a
        // wcslen etc. into the received packet).
        //

        RtlZeroMemory( pRequestContext->ReceiveBuffer, DHCP_MESSAGE_SIZE + 1 );
        pRequestContext->ReceiveMessageSize = DHCP_MESSAGE_SIZE;

        length = recvfrom(
                     pRequestContext->ActiveEndpoint->Socket,
                     (char *)pRequestContext->ReceiveBuffer,
                     pRequestContext->ReceiveMessageSize,
                     0,
                     &pRequestContext->SourceName,
                     (int *)&pRequestContext->SourceNameLength
                     );

        if ( length == SOCKET_ERROR ) {
            error = WSAGetLastError();
            BinlPrintDbg(( DEBUG_ERRORS, "Recv failed, error = %ld\n", error ));
        } else {

            //
            // Ignore all messages that do not look like DHCP or doesn't have the
            // option "PXEClient", OR that is not an oschooser message (they
            // all start with 0x81).
            //

            if ( ((LPDHCP_MESSAGE)pRequestContext->ReceiveBuffer)->Operation == OSC_REQUEST) {

                //
                // All OSC request packets have a 4-byte signature (first byte
                // is OSC_REQUEST) followed by a DWORD length (that does not
                // include the signature/length). Make sure the length matches
                // what we got from recvfrom (we allow padding at the end). We
                // use SIGNED_PACKET but any of the XXX_PACKET structures in
                // oscpkt.h would work.
                //

                if (length < FIELD_OFFSET(SIGNED_PACKET, SequenceNumber)) {
                    BinlPrintDbg(( DEBUG_OSC_ERROR, "Discarding runt packet %d bytes\n", length ));
                    continue;
                }

                if ((length - FIELD_OFFSET(SIGNED_PACKET, SequenceNumber)) <
                        ((SIGNED_PACKET UNALIGNED *)pRequestContext->ReceiveBuffer)->Length) {
                    BinlPrintDbg(( DEBUG_OSC_ERROR, "Discarding invalid length message %d bytes (header said %d)\n",
                        length, ((SIGNED_PACKET UNALIGNED *)pRequestContext->ReceiveBuffer)->Length));
                    continue;
                }

                BinlPrintDbg(( DEBUG_MESSAGE, "Received OSC message\n", 0 ));
                error = ERROR_SUCCESS;

            } else {

                if ( length < FIELD_OFFSET(DHCP_MESSAGE, Option) + 4 ) {
                    //
                    // Message isn't long enough to include the magic cookie, ignore it.
                    //
                    continue;
                }

                if ( ((LPDHCP_MESSAGE)pRequestContext->ReceiveBuffer)->Operation != BOOT_REQUEST) {
                    continue; // Doesn't look like an interesting DHCP frame
                }

                //  Stop scanning when there isn't room for a ClientOption, including
                //  the type, length, and the CLIENTOPTIONSTRING.
                EndOfScan = pRequestContext->ReceiveBuffer +
                            pRequestContext->ReceiveMessageSize -
                            (FIELD_OFFSET(OPTION, OptionValue[0]) + CLIENTOPTIONSIZE);

                //
                // check magic cookie.
                //

                MagicCookie = (LPBYTE)&((LPDHCP_MESSAGE)pRequestContext->ReceiveBuffer)->Option;

                if( (*MagicCookie != (BYTE)DHCP_MAGIC_COOKIE_BYTE1) ||
                    (*(MagicCookie+1) != (BYTE)DHCP_MAGIC_COOKIE_BYTE2) ||
                    (*(MagicCookie+2) != (BYTE)DHCP_MAGIC_COOKIE_BYTE3) ||
                    (*(MagicCookie+3) != (BYTE)DHCP_MAGIC_COOKIE_BYTE4))
                {
                    continue; // this is a vendor specific magic cookie.
                }

                Option = (LPOPTION) (MagicCookie + 4);
                informPacket = FALSE;

                while (((LPBYTE)Option <= EndOfScan) &&
                       ((Option->OptionType != OPTION_CLIENT_CLASS_INFO) ||
                        (Option->OptionLength < CLIENTOPTIONSIZE) ||
                        (memcmp(Option->OptionValue, CLIENTOPTIONSTRING, CLIENTOPTIONSIZE) != 0))) {

                    if ( Option->OptionType == OPTION_END ){
                        break;
                    } else if ( Option->OptionType == OPTION_PAD ){
                        Option = (LPOPTION)( (LPBYTE)(Option) + 1);
                    } else {
                        if (( Option->OptionType == OPTION_MESSAGE_TYPE ) &&
                            ( Option->OptionLength == 1 ) &&
                            ( Option->OptionValue[0] == DHCP_INFORM_MESSAGE )) {
                            informPacket = TRUE;
                        }
                        Option = (LPOPTION)( (LPBYTE)(Option) + Option->OptionLength + 2);
                    }
                }

                if ((((LPBYTE)Option > EndOfScan) ||
                     (Option->OptionType == OPTION_END)) &&
                     (informPacket == FALSE)) {
                    continue;   //  Not an extended DHCP packet so ignore it
                }

                BinlPrintDbg(( DEBUG_MESSAGE, "Received message\n", 0 ));
                error = ERROR_SUCCESS;

            }
        }

        pRequestContext->ReceiveMessageSize = length;
        return( error );
    }
}

DWORD
BinlSendMessage(
    LPBINL_REQUEST_CONTEXT RequestContext
    )
/*++

Routine Description:

    This function send a response to a BINL client.

Arguments:

    RequestContext - A pointer to the BinlRequestContext block for
        this request.

Return Value:

    The status of the operation.

--*/
{
    DWORD error;
    struct sockaddr_in *source;
    LPDHCP_MESSAGE binlMessage;
    LPDHCP_MESSAGE binlReceivedMessage;
    DWORD MessageLength;
    BOOL  ArpCacheUpdated = FALSE;

    binlMessage = (LPDHCP_MESSAGE) RequestContext->SendBuffer;
    binlReceivedMessage = (LPDHCP_MESSAGE) RequestContext->ReceiveBuffer;

    //
    // if the request arrived from a relay agent, then send the reply
    // on server port otherwise leave it as the client's source port.
    //

    source = (struct sockaddr_in *)&RequestContext->SourceName;
    if ( binlReceivedMessage->RelayAgentIpAddress != 0 ) {
        source->sin_port = htons( DHCP_SERVR_PORT );
    }

    //
    // if this request arrived from relay agent then send the
    // response to the address the relay agent says.
    //

    if ( binlReceivedMessage->RelayAgentIpAddress != 0 ) {
        source->sin_addr.s_addr = binlReceivedMessage->RelayAgentIpAddress;
    }
    else {

        //
        // if the client didnt specify broadcast bit and if
        // we know the ipaddress of the client then send unicast.
        //

        //
        // But if IgnoreBroadcastFlag is set in the registry and
        // if the client requested to broadcast or the server is
        // nacking or If the client doesn't have an address yet,
        // respond via broadcast.
        // Note that IgnoreBroadcastFlag is off by default. But it
        // can be set as a workaround for the clients that are not
        // capable of receiving unicast
        // and they also dont set the broadcast bit.
        //

        if ( (RequestContext->MessageType == DHCP_INFORM_MESSAGE) &&
             (ntohs(binlMessage->Reserved) & DHCP_BROADCAST) ) {

            source->sin_addr.s_addr = (DWORD)-1;

        } else if ( BinlGlobalIgnoreBroadcastFlag ) {

            if ((ntohs(binlReceivedMessage->Reserved) & DHCP_BROADCAST) ||
                    (binlReceivedMessage->ClientIpAddress == 0) ||
                    (source->sin_addr.s_addr == 0) ) {

                source->sin_addr.s_addr = (DWORD)-1;

                binlMessage->Reserved = 0;
                    // this flag should be zero in the local response.
            }

        } else {

            if( (ntohs(binlReceivedMessage->Reserved) & DHCP_BROADCAST) ||
                (!source->sin_addr.s_addr ) ){

                source->sin_addr.s_addr = (DWORD)-1;

                binlMessage->Reserved = 0;
                    // this flag should be zero in the local response.
            } else {

                //
                //  Send back to the same IP address that the request came in on (
                //  i.e. source->sin_addr.s_addr)
                //
            }

        }
    }

    BinlPrint(( DEBUG_STOC, "Sending response to = %s, XID = %lx.\n",
        inet_ntoa(source->sin_addr), binlMessage->TransactionID));


    //
    // send minimum DHCP_MIN_SEND_RECV_PK_SIZE (300) bytes, otherwise
    // bootp relay agents don't like the packet.
    //

    MessageLength = (RequestContext->SendMessageSize >
                    DHCP_MIN_SEND_RECV_PK_SIZE) ?
                        RequestContext->SendMessageSize :
                            DHCP_MIN_SEND_RECV_PK_SIZE;
    error = sendto(
                 RequestContext->ActiveEndpoint->Socket,
                (char *)RequestContext->SendBuffer,
                MessageLength,
                0,
                &RequestContext->SourceName,
                RequestContext->SourceNameLength
                );

    if ( error == SOCKET_ERROR ) {
        error = WSAGetLastError();
        BinlPrintDbg(( DEBUG_ERRORS, "Send failed, error = %ld\n", error ));
    } else {
        error = ERROR_SUCCESS;
    }

    return( error );
}

NTSTATUS
GetIpAddressInfo (
    ULONG Delay
    )
{
    PDNS_ADDRESS_INFO pAddressInfo = NULL;
    ULONG count;

    //
    //  We can get out ahead of the dns cached info here... let's delay a bit
    //  if the pnp logic told us there was a change.
    //

    if (Delay) {
        Sleep( Delay );
    }

    count = DnsGetIpAddressInfoList( &pAddressInfo );

    if (count == 0) {

        //
        //  we don't know what went wrong, we'll fall back to old APIs.
        //

        DHCP_IP_ADDRESS ipaddr = 0;
        PHOSTENT Host = gethostbyname( NULL );

        if (Host) {

            ipaddr = *(PDHCP_IP_ADDRESS)Host->h_addr;

            if ((Host->h_addr_list[0] != NULL) &&
                (Host->h_addr_list[1] != NULL)) {

                BinlIsMultihomed = TRUE;

            } else {

                BinlIsMultihomed = FALSE;
            }

            BinlGlobalMyIpAddress = ipaddr;

        } else {

            //
            //  what's with the ip stack?  we can't get any type of address
            //  info out of it... for now, we won't answer any if we don't
            //  already have the info we need.
            //

            if (BinlDnsAddressInfo == NULL) {

                BinlIsMultihomed = TRUE;
            }
        }
        return STATUS_SUCCESS;
    }

    EnterCriticalSection(&gcsParameters);

    if (BinlDnsAddressInfo) {
        LocalFree( BinlDnsAddressInfo );
    }

    BinlDnsAddressInfo = pAddressInfo;
    BinlDnsAddressInfoCount = count;

    BinlIsMultihomed = (count != 1);

    if (!BinlIsMultihomed) {

        BinlGlobalMyIpAddress = pAddressInfo->ipAddress;
    }

    LeaveCriticalSection(&gcsParameters);

    return STATUS_SUCCESS;
}

DHCP_IP_ADDRESS
BinlGetMyNetworkAddress (
    LPBINL_REQUEST_CONTEXT RequestContext
    )
{
    ULONG RemoteIp;
    DHCP_IP_ADDRESS ipaddr;
    ULONG i;
    ULONG subnetMask;
    ULONG localAddr;

    BinlAssert( RequestContext != NULL);

    //
    //  If we're not multihomed, then we know the address since there's just one.
    //

    if (!BinlIsMultihomed) {
        return BinlGlobalMyIpAddress;
    }

    RemoteIp = ((struct sockaddr_in *)&RequestContext->SourceName)->sin_addr.s_addr;

    if (RemoteIp == 0) {

        //
        //  If we're multihomed and the client doesn't yet have an IP address,
        //  then we return 0, because we don't know which of our addresses to
        //  use to talk to the client.
        //

        return 0;
    }

    EnterCriticalSection(&gcsParameters);

    if (BinlDnsAddressInfo == NULL) {

        LeaveCriticalSection(&gcsParameters);
        return (BinlIsMultihomed ? 0 : BinlGlobalMyIpAddress);
    }

    ipaddr = 0;

    for (i = 0; i < BinlDnsAddressInfoCount; i++) {

        localAddr = BinlDnsAddressInfo[i].ipAddress;
        subnetMask = BinlDnsAddressInfo[i].subnetMask;

        //
        //  check that the remote ip address may have come from this subnet.
        //  note that the address could be the address of a dhcp relay agent,
        //  which is fine since we're just looking for the address of the
        //  local subnet to broadcast the response on.
        //

        if ((RemoteIp & subnetMask) == (localAddr & subnetMask)) {

            ipaddr = localAddr;
            break;
        }
    }

    LeaveCriticalSection(&gcsParameters);

    return ipaddr;
}


VOID
FreeIpAddressInfo (
    VOID
    )
{
    EnterCriticalSection(&gcsParameters);

    if (BinlDnsAddressInfo != NULL) {
        LocalFree( BinlDnsAddressInfo );
    }
    BinlDnsAddressInfo = NULL;
    BinlDnsAddressInfoCount = 0;

    LeaveCriticalSection(&gcsParameters);

    return;
}