/*++

Copyright (c) 1996-2000  Microsoft Corporation

Module Name:

    update.c

Abstract:

    Domain Name System (DNS) Library

    Update client routines.

Author:

    Jim Gilroy (jamesg)     October, 1996

Environment:

    User Mode - Win32

Revision History:

--*/


#include "local.h"

//
//  Update Timeouts
//
//  note, max is a little longer than might be expected as DNS server
//  may have to contact primary and wait for primary to do update (inc.
//  disk access) then response
//

#define INITIAL_UPDATE_TIMEOUT  (4)     // 4 seconds
#define MAX_UPDATE_TIMEOUT      (60)    // 60 seconds



PCHAR
Dns_WriteNoDataUpdateRecordToMessage(
    IN      PCHAR           pch,
    IN      PCHAR           pchStop,
    IN      WORD            wClass,
    IN      WORD            wType
    )
/*++

Routine Description:

    No data RR cases:

    This includes prereqs and deletes except for specific record cases.

Arguments:

    pch - ptr to next byte in packet buffer

    pchStop - end of packet buffer

    wClass - class

    wType - desired RR type

Return Value:

    Ptr to next postion in buffer, if successful.
    NULL on error.

--*/
{
    PDNS_WIRE_RECORD    pdnsRR;

    DNSDBG( WRITE, (
        "Writing update RR to packet buffer at %p.\n",
        pch ));

    //
    //  out of space
    //

    pdnsRR = (PDNS_WIRE_RECORD) pch;
    pch += sizeof( DNS_WIRE_RECORD );
    if ( pch >= pchStop )
    {
        DNS_PRINT(( "ERROR  out of space writing record to packet.\n" ));
        return( NULL );
    }

    //
    //  set type and class
    //

    *(UNALIGNED WORD *) &pdnsRR->RecordType  = htons( wType );
    *(UNALIGNED WORD *) &pdnsRR->RecordClass = htons( wClass );

    //
    //  TTL and datalength zero for all no data cases
    //      - prereqs except specific record delete
    //      - deletes except specific record delete
    //

    *(UNALIGNED DWORD *) &pdnsRR->TimeToLive = 0;
    *(UNALIGNED WORD *) &pdnsRR->DataLength = 0;

    return( pch );
}



PCHAR
Dns_WriteDataUpdateRecordToMessage(
    IN      PCHAR           pch,
    IN      PCHAR           pchStop,
    IN      WORD            wClass,
    IN      WORD            wType,
    IN      DWORD           dwTtl,
    IN      WORD            wDataLength
    )
/*++

Routine Description:

    No data RR cases:

    This includes prereqs and deletes except for specific record cases.

Arguments:

    pch - ptr to next byte in packet buffer

    pchStop - end of packet buffer

    wClass - class

    wType - desired RR type

    dwTtl - time to live

    wDataLength - data length

Return Value:

    Ptr to next postion in buffer, if successful.
    NULL on error.

--*/
{
    PDNS_WIRE_RECORD    pdnsRR;

    DNSDBG( WRITE2, (
        "Writing RR to packet buffer at %p.\n",
        pch ));

    //
    //  out of space
    //

    pdnsRR = (PDNS_WIRE_RECORD) pch;
    pch += sizeof( DNS_WIRE_RECORD );
    if ( pch + wDataLength >= pchStop )
    {
        DNS_PRINT(( "ERROR  out of space writing record to packet.\n" ));
        return( NULL );
    }

    //
    //  set type and class
    //

    *(UNALIGNED WORD *) &pdnsRR->RecordType  = htons( wType );
    *(UNALIGNED WORD *) &pdnsRR->RecordClass = htons( wClass );

    //
    //  TTL and datalength zero for all no data cases
    //      - prereqs except specific record delete
    //      - deletes except specific record delete
    //

    *(UNALIGNED DWORD *) &pdnsRR->TimeToLive = htonl( dwTtl );
    *(UNALIGNED WORD *) &pdnsRR->DataLength = htons( wDataLength );

    return( pch );
}



//
//  Host update routines
//

#if 0
PDNS_MSG_BUF
Dns_BuildHostUpdateMessage(
    IN OUT  PDNS_MSG_BUF    pMsg,
    IN      LPSTR           pszZone,
    IN      LPSTR           pszName,
    IN      PIP_ARRAY       aipAddresses,
    IN      DWORD           dwTtl
    )
/*++

Routine Description:

    Build server update message.

Arguments:

    pMsg -- existing message buffer, to use;  NULL to allocate new one

    pszZone -- zone name for update

    pszName -- full DNS hostname being updated

    aipAddresses -- IP addresses to be updated

Return Value:

    ERROR_SUCCESS if successful.
    Error status on failure.

--*/
{
    PDNS_HEADER     pdnsMsg;
    PCHAR           pch;
    PCHAR           pchstop;
    DWORD           i;
    WORD            nameOffset;

    IF_DNSDBG( UPDATE )
    {
        DNS_PRINT((
            "Enter Dns_BuildHostUpdateMessage()\n"
            "\tpMsg     = %p\n"
            "\tpszZone  = %s\n"
            "\tpszName  = %s\n"
            "\tdwTtl    = %d\n",
            pMsg,
            pszZone,
            pszName,
            dwTtl ));
        DnsDbg_IpArray(
            "\tHost IP array\n",
            "host",
            aipAddresses );
    }

    //
    //  create message buffer
    //

    if ( !pMsg )
    {
        DNS_PRINT(( "Allocating new UPDATE message buffer.\n" ));

        pMsg = ALLOCATE_HEAP( DNS_UDP_ALLOC_LENGTH );
        if ( !pMsg )
        {
            return( NULL );
        }
        RtlZeroMemory(
            pMsg,
            DNS_UDP_ALLOC_LENGTH );

        pMsg->BufferLength = DNS_UDP_MAX_PACKET_LENGTH;
        pMsg->pBufferEnd = (PCHAR)&pMsg->MessageHead + pMsg->BufferLength;

        //
        //  set default sockaddr info
        //      - caller MUST choose remote IP address

        pMsg->RemoteAddress.sin_family = AF_INET;
        pMsg->RemoteAddress.sin_port = NET_ORDER_DNS_PORT;
        pMsg->RemoteAddressLength = sizeof( SOCKADDR_IN );

        //  set header for update

        pMsg->MessageHead.Opcode = DNS_OPCODE_UPDATE;
    }

    //
    //  existing message, just verify
    //

    ELSE_ASSERT( pMsg->MessageHead.Opcode == DNS_OPCODE_UPDATE );

    //
    //  reset current pointer after header
    //      - note:  send length is set based on this ptr
    //

    pMsg->pCurrent = pMsg->MessageBody;


    //
    //  build message
    //

    pch = pMsg->pCurrent;
    pchstop = pMsg->pBufferEnd;

    //
    //  zone section
    //

    pMsg->MessageHead.QuestionCount = 1;
    pch = Dns_WriteDottedNameToPacket(
            pch,
            pchstop,
            pszZone,
            NULL,
            0,
            FALSE );
    if ( !pch )
    {
        return( NULL );
    }
    *(UNALIGNED WORD *) pch = DNS_RTYPE_SOA;
    pch += sizeof(WORD);
    *(UNALIGNED WORD *) pch = DNS_RCLASS_INTERNET;
    pch += sizeof(WORD);

    //
    //  prerequisites -- no records
    //

    pMsg->MessageHead.AnswerCount = 0;

    //
    //  update
    //      - delete A records at name
    //      - add new A records
    //

    //  save offset to host name for future writes

    nameOffset = (WORD)(pch - (PCHAR) &pMsg->MessageHead);

    pch = Dns_WriteDottedNameToPacket(
                pch,
                pchstop,
                pszName,
                pszZone,
                DNS_OFFSET_TO_QUESTION_NAME,
                FALSE );
    if ( !pch )
    {
        DNS_PRINT(( "ERROR writing dotted name to packet.\n" ));
        return( NULL );
    }
    pch = Dns_WriteNoDataUpdateRecordToMessage(
                pch,
                pchstop,
                DNS_CLASS_ALL,  //  delete all
                DNS_TYPE_A     //  A records
                );
    DNS_ASSERT( pch );

    //
    //  add A record for each address in array
    //      - use offset for name
    //      - write IP

    for ( i=0; i<aipAddresses->AddrCount; i++ )
    {
        *(UNALIGNED WORD *) pch = htons( (WORD)(nameOffset|(WORD)0xC000) );
        pch += sizeof( WORD );
        pch = Dns_WriteDataUpdateRecordToMessage(
                    pch,
                    pchstop,
                    DNS_CLASS_INTERNET,
                    DNS_TYPE_A,        //  A records
                    dwTtl,
                    sizeof(IP_ADDRESS)
                    );
        DNS_ASSERT( pch );
        *(UNALIGNED DWORD *) pch = aipAddresses->AddrArray[i];
        pch += sizeof(DWORD);
    }

    //  total update sections RRs
    //      one delete RR, plus one for each new IP

    pMsg->MessageHead.NameServerCount = (USHORT)(aipAddresses->AddrCount + 1);

    //
    //  additional section - no records
    //

    pMsg->MessageHead.AdditionalCount = 0;

    //
    //  reset current ptr -- need for send routine
    //

    pMsg->pCurrent = pch;

    IF_DNSDBG( SEND )
    {
        DnsDbg_Message(
            "UPDATE packet built",
            pMsg );
    }
    return( pMsg );
}
#endif



PDNS_RECORD
Dns_HostUpdateRRSet(
    IN      LPSTR           pszHostName,
    IN      PIP_ARRAY       AddrArray,
    IN      DWORD           dwTtl
    )
/*++

Routine Description:

    Create records for host update:
        -- whack of all A records
        -- add of all A records in new set

Arguments:

    pszHostName -- host name, FULL FQDN

    AddrArray -- new IPs of host

    dwTtl -- TTL for records

Return Value:

    Ptr to record list.
    NULL on error.

--*/
{
    DNS_RRSET   rrset;
    PDNS_RECORD prr;
    DWORD       i;

    //
    //  create whack
    //

    prr = Dns_AllocateRecord( 0 );
    if ( ! prr )
    {
        return( NULL );
    }
    prr->pName = pszHostName;
    prr->wType = DNS_TYPE_A;
    prr->Flags.S.Section = DNSREC_UPDATE;
    prr->Flags.S.Delete = TRUE;

    //
    //  create update record for each address
    //

    if ( !AddrArray )
    {
        return( prr );
    }
    DNS_RRSET_INIT( rrset );
    DNS_RRSET_ADD( rrset, prr );

    for ( i=0; i<AddrArray->AddrCount; i++ )
    {
        prr = Dns_AllocateRecord( sizeof(DNS_A_DATA) );
        if ( ! prr )
        {
            Dns_RecordListFree( rrset.pFirstRR, FALSE );
            return( NULL );
        }
        prr->pName = pszHostName;
        prr->wType = DNS_TYPE_A;
        prr->Flags.S.Section = DNSREC_UPDATE;
        prr->dwTtl = dwTtl;
        prr->Data.A.IpAddress = AddrArray->AddrArray[i];

        DNS_RRSET_ADD( rrset, prr );
    }

    //  return ptr to first record in list

    return( rrset.pFirstRR );
}



//
//  DCR:  dead code, remove
//
DNS_STATUS
Dns_UpdateHostAddrs(
    IN      LPSTR           pszName,
    IN      PIP_ARRAY       aipAddresses,
    IN      PIP_ARRAY       aipServers,
    IN      DWORD           dwTtl
    )
/*++

Routine Description:

    Updates client's A records registered with DNS server.

Arguments:

    pszName -- name (FQDN) of client to update

    aipAddresses -- counted array of new client IP addrs

    aipServers -- counted array of DNS server IP addrs

    dwTtl -- TTL for new A records

Return Value:

    ERROR_SUCCESS if successful.
    Error status on failure.

--*/
{
    PDNS_RECORD prr;
    DNS_STATUS  status;

    IF_DNSDBG( UPDATE )
    {
        DNS_PRINT((
            "Enter Dns_UpdateHostAddrs()\n"
            "\tpszName  = %s\n"
            "\tdwTtl    = %d\n",
            pszName,
            dwTtl ));
        DnsDbg_IpArray(
            "\tHost IP array\n",
            "\tHost",
            aipAddresses );
        DnsDbg_IpArray(
            "\tNS IP array\n",
            "\tNS",
            aipServers );
    }

    //
    //  never let anyone set a TTL longer than 1 hour
    //
    //  DCR:  define policy on what our clients should use for TTL
    //      one hour is not bad
    //      could key off type of address
    //          RAS -- 15 minutes (as may be back up again quickly)
    //          DHCP -- one hour (may move)
    //          static -- one day (machines may be reconfigured)
    //

    if ( dwTtl > 3600 )
    {
        dwTtl = 3600;
    }

    //
    //  build update RR set
    //

    prr = Dns_HostUpdateRRSet(
            pszName,
            aipAddresses,
            dwTtl );
    if ( ! prr )
    {
        status = GetLastError();
        DNS_ASSERT( status == DNS_ERROR_NO_MEMORY );
        return status;
    }

    //
    //  do the update
    //

    status = Dns_UpdateLib(
                prr,
                0,          // no flags
                NULL,       // no adapter list
                NULL,       // use default credentials
                NULL        // response not desired
                );

    Dns_RecordListFree( prr, FALSE );

    DNSDBG( UPDATE, (
        "Leave Dns_UpdateHostAddrs() status=%p %s\n",
        status,
        Dns_StatusString(status) ));

    return( status );
}



DNS_STATUS
Dns_UpdateLib(
    IN      PDNS_RECORD         pRecord,
    IN      DWORD               dwFlags,
    IN      PDNS_NETINFO        pNetworkInfo,
    IN      HANDLE              hCreds          OPTIONAL,
    OUT     PDNS_MSG_BUF *      ppMsgRecv       OPTIONAL
    )
/*++

Routine Description:

    Send DNS update.

Arguments:

    pRecord -- list of records to send in update

    dwFlags -- update flags;  primarily security

    pNetworkInfo -- adapter list with necessary info for update
                        - zone name
                        - primary name server name
                        - primary name server IP

    ppMsgRecv -- OPTIONAL addr to recv ptr to response message

Return Value:

    ERROR_SUCCESS if successful.
    Error status on failure.

--*/
{
    PDNS_MSG_BUF    pmsgSend = NULL;
    PDNS_MSG_BUF    pmsgRecv = NULL;
    DNS_STATUS      status = NO_ERROR;
    WORD            length;
    PIP_ARRAY       parrayServers = NULL;
    LPSTR           pszzone;
    LPSTR           pszserverName;
    BOOL            fsecure = FALSE;
    BOOL            fswitchToTcp = FALSE;
    DNS_HEADER      header;
    PCHAR           pCreds=NULL;

    DNSDBG( UPDATE, (
        "Dns_UpdateLib()\n"
        "\tflags        = %p\n"
        "\tpRecord      = %p\n"
        "\t\towner      = %s\n",
        dwFlags,
        pRecord,
        pRecord ? pRecord->pName : NULL ));

    //
    //  if not a UPDATE compatibile adapter list -- no action
    //

    if ( !IS_UPDATE_NETWORK_INFO(pNetworkInfo) )
    {
        return( ERROR_INVALID_PARAMETER );
    }

    //
    //  suck info from adapter list
    //

    pszzone = pNetworkInfo->pSearchList->pszDomainOrZoneName;

    parrayServers = Dns_ConvertNetworkInfoToIpArray( pNetworkInfo );

    pszserverName = pNetworkInfo->aAdapterInfoList[0]->pszAdapterDomain;

    DNS_ASSERT( pszzone && parrayServers );

    //
    //  build recv message buffer
    //      - must be big enough for TCP
    //

    pmsgRecv = Dns_AllocateMsgBuf( DNS_TCP_DEFAULT_PACKET_LENGTH );
    if ( !pmsgRecv )
    {
        status = DNS_ERROR_NO_MEMORY;
        goto Cleanup;
    }

    //
    //  build update packet
    //  note currently this function allocates TCP sized buffer if records
    //      given;  if this changes need to alloc TCP buffer here
    //

    CLEAR_DNS_HEADER_FLAGS_AND_XID( &header );
    header.Opcode = DNS_OPCODE_UPDATE;

    pmsgSend = Dns_BuildPacket(
                    &header,        // copy header
                    TRUE,           //  ... but not header counts
                    pszzone,        // question zone\type SOA
                    DNS_TYPE_SOA,
                    pRecord,
                    0,              // no other flags
                    TRUE            // building an update packet
                    );
    if ( !pmsgSend)
    {
        DNS_PRINT(( "ERROR:  failed send buffer allocation.\n" ));
        status = DNS_ERROR_NO_MEMORY;
        goto Cleanup;
    }

    //
    //  try non-secure first unless explicitly secure only
    //

    fsecure = (dwFlags & DNS_UPDATE_SECURITY_ONLY);

    if ( !fsecure )
    {
        status = Dns_SendAndRecv(
                    pmsgSend,
                    & pmsgRecv,
                    NULL,           // no response records
                    dwFlags,
                    parrayServers,
                    pNetworkInfo );

        if ( status == ERROR_SUCCESS )
        {
            status = Dns_MapRcodeToStatus( pmsgRecv->MessageHead.ResponseCode );
        }

        if ( status != DNS_ERROR_RCODE_REFUSED ||
            dwFlags & DNS_UPDATE_SECURITY_OFF )
        {
            goto Cleanup;
        }

        DNSDBG( UPDATE, (
            "Failed unsecure update, switching to secure!\n"
            "\tcurrent time (ms) = %d\n",
            GetCurrentTime() ));
        fsecure = TRUE;
    }

    //
    //  security
    //      - must have server name
    //      - must start package
    //

    if ( fsecure )
    {
        if ( !pszserverName )
        {
            status = ERROR_INVALID_PARAMETER;
            goto Cleanup;
        }
        status = Dns_StartSecurity( FALSE );
        if ( status != ERROR_SUCCESS )
        {
            goto Cleanup;
        }

        pCreds = Dns_GetApiContextCredentials(hCreds);


        status = Dns_DoSecureUpdate(
                    pmsgSend,
                    pmsgRecv,
                    NULL,
                    dwFlags,
                    pNetworkInfo,
                    parrayServers,
                    pszserverName,
                    pCreds,         // initialized in DnsAcquireContextHandle
                    NULL            // default context name
                    );
        if ( status == ERROR_SUCCESS )
        {
            status = Dns_MapRcodeToStatus( pmsgRecv->MessageHead.ResponseCode );
        }
    }


Cleanup:

    //  free server array sucked from adapter list

    if ( parrayServers )
    {
        FREE_HEAP( parrayServers );
    }

    //  return recv message buffer

    if ( ppMsgRecv )
    {
        *ppMsgRecv = pmsgRecv;
    }
    else
    {
        FREE_HEAP( pmsgRecv );
    }
    FREE_HEAP( pmsgSend);

    DNSDBG( UPDATE, (
        "Dns_UpdateLib() completed, status = %p %s.\n",
        status,
        Dns_StatusString(status) ));

    return( status );
}



DNS_STATUS
Dns_UpdateLibEx(
    IN      PDNS_RECORD         pRecord,
    IN      DWORD               dwFlags,
    IN      PDNS_NAME           pszZone,
    IN      PDNS_NAME           pszServerName,
    IN      PIP_ARRAY           aipServers,
    IN      HANDLE              hCreds          OPTIONAL,
    OUT     PDNS_MSG_BUF *      ppMsgRecv       OPTIONAL
    )
/*++

Routine Description:

    Send DNS update.

    This routine builds an UPDATE compatible pNetworkInfo from the
    information given.  Then calls Dns_Update().

Arguments:

    pRecord -- list of records to send in update

    pszZone -- zone name for update

    pszServerName -- server name

    aipServers -- DNS servers to send update to

    hCreds -- Optional Credentials info

    ppMsgRecv -- addr for ptr to recv buffer, if desired

Return Value:

    ERROR_SUCCESS if successful.
    Error status on failure.

--*/
{
    PDNS_NETINFO        pNetworkInfo;
    DNS_STATUS          status = NO_ERROR;

    //
    //  convert params into UPDATE compatible adapter list
    //

    pNetworkInfo = Dns_CreateUpdateNetworkInfo(
                        pszZone,
                        pszServerName,
                        aipServers,
                        0 );

    if ( !pNetworkInfo )
    {
        return( ERROR_INVALID_PARAMETER );
    }

    //
    //  call real update function
    //

    status = Dns_UpdateLib(
                pRecord,
                dwFlags,
                pNetworkInfo,
                hCreds,
                ppMsgRecv );

    Dns_FreeNetworkInfo( pNetworkInfo );

    return status;
}

//
//  End update.c
//