/*
 * Copyright (C) 1996-2026 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

/*
 * Low level DNS protocol routines
 *
 * KNOWN BUGS:
 *
 * UDP replies with TC set should be retried via TCP
 */

#include "squid.h"
#include "dns/rfc1035.h"
#include "dns/rfc2671.h"
#include "util.h"

#include <cassert>
#include <cstring>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_MEMORY_H
#include <memory.h>
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#if HAVE_STRINGS_H
#include <strings.h>
#endif

#define RFC1035_MAXLABELSZ 63
#define rfc1035_unpack_error 15

#if 0
#define RFC1035_UNPACK_DEBUG  fprintf(stderr, "unpack error at %s:%d\n", __FILE__,__LINE__)
#else
#define RFC1035_UNPACK_DEBUG  (void)0
#endif

/*
 * rfc1035HeaderPack()
 *
 * Packs a rfc1035_header structure into a buffer.
 * Returns number of octets packed (should always be 12)
 */
int
rfc1035HeaderPack(char *buf, size_t sz, rfc1035_message * hdr)
{
    int off = 0;
    unsigned short s;
    unsigned short t;
    assert(sz >= 12);
    s = htons(hdr->id);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    t = 0;
    t |= hdr->qr << 15;
    t |= (hdr->opcode << 11);
    t |= (hdr->aa << 10);
    t |= (hdr->tc << 9);
    t |= (hdr->rd << 8);
    t |= (hdr->ra << 7);
    t |= hdr->rcode;
    s = htons(t);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    s = htons(hdr->qdcount);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    s = htons(hdr->ancount);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    s = htons(hdr->nscount);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    s = htons(hdr->arcount);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    assert(off == 12);
    return off;
}

/*
 * rfc1035LabelPack()
 *
 * Packs a label into a buffer.  The format of
 * a label is one octet specifying the number of character
 * bytes to follow.  Labels must be smaller than 64 octets.
 * Returns number of octets packed.
 */
static int
rfc1035LabelPack(char *buf, size_t sz, const char *label)
{
    assert(label);
    assert(!strchr(label, '.'));

    int off = 0;
    auto len = strlen(label);
    if (len > RFC1035_MAXLABELSZ)
        len = RFC1035_MAXLABELSZ;
    assert(sz >= len + 1);
    *(buf + off) = (char) len;
    off++;
    memcpy(buf + off, label, len);
    off += len;
    return off;
}

/*
 * rfc1035NamePack()
 *
 * Packs a name into a buffer.  Names are packed as a
 * sequence of labels, terminated with NULL label.
 * Note message compression is not supported here.
 * Returns number of octets packed.
 */
static int
rfc1035NamePack(char *buf, size_t sz, const char *name)
{
    unsigned int off = 0;
    char *copy = xstrdup(name);
    char *t;
    /*
     * NOTE: use of strtok here makes names like foo....com valid.
     */
    for (t = strtok(copy, "."); t; t = strtok(nullptr, "."))
        off += rfc1035LabelPack(buf + off, sz - off, t);
    xfree(copy);

    // add a terminating root (i.e. zero length) label
    assert(off < sz);
    buf[off] = 0;
    ++off;

    return off;
}

/*
 * rfc1035QuestionPack()
 *
 * Packs a QUESTION section of a message.
 * Returns number of octets packed.
 */
int
rfc1035QuestionPack(char *buf,
                    const size_t sz,
                    const char *name,
                    const unsigned short type,
                    const unsigned short _class)
{
    unsigned int off = 0;
    unsigned short s;
    off += rfc1035NamePack(buf + off, sz - off, name);
    s = htons(type);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    s = htons(_class);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    assert(off <= sz);
    return off;
}

/*
 * rfc1035HeaderUnpack()
 *
 * Unpacks a RFC1035 message header buffer into the header fields
 * of the rfc1035_message structure.
 *
 * Updates the buffer offset, which is the same as number of
 * octects unpacked since the header starts at offset 0.
 *
 * Returns 0 (success) or 1 (error)
 */
int
rfc1035HeaderUnpack(const char *buf, size_t sz, unsigned int *off, rfc1035_message * h)
{
    unsigned short s;
    unsigned short t;
    assert(*off == 0);
    /*
     * The header is 12 octets.  This is a bogus message if the size
     * is less than that.
     */
    if (sz < 12)
        return 1;
    memcpy(&s, buf + (*off), sizeof(s));
    (*off) += sizeof(s);
    h->id = ntohs(s);
    memcpy(&s, buf + (*off), sizeof(s));
    (*off) += sizeof(s);
    t = ntohs(s);
    h->qr = (t >> 15) & 0x01;
    h->opcode = (t >> 11) & 0x0F;
    h->aa = (t >> 10) & 0x01;
    h->tc = (t >> 9) & 0x01;
    h->rd = (t >> 8) & 0x01;
    h->ra = (t >> 7) & 0x01;
    /*
     * We might want to check that the reserved 'Z' bits (6-4) are
     * all zero as per RFC 1035.  If not the message should be
     * rejected.
     * NO! RFCs say ignore inbound reserved, they may be used in future.
     *  NEW messages need to be set 0, that's all.
     */
    h->rcode = t & 0x0F;
    memcpy(&s, buf + (*off), sizeof(s));
    (*off) += sizeof(s);
    h->qdcount = ntohs(s);
    memcpy(&s, buf + (*off), sizeof(s));
    (*off) += sizeof(s);
    h->ancount = ntohs(s);
    memcpy(&s, buf + (*off), sizeof(s));
    (*off) += sizeof(s);
    h->nscount = ntohs(s);
    memcpy(&s, buf + (*off), sizeof(s));
    (*off) += sizeof(s);
    h->arcount = ntohs(s);
    assert((*off) == 12);
    return 0;
}

/*
 * rfc1035NameUnpack()
 *
 * Unpacks a Name in a message buffer into a char*.
 * Note 'buf' points to the beginning of the whole message,
 * 'off' points to the spot where the Name begins, and 'sz'
 * is the size of the whole message.  'name' must be allocated
 * by the caller.
 *
 * Supports the RFC1035 message compression through recursion.
 *
 * Updates the new buffer offset.
 *
 * Returns 0 (success) or 1 (error)
 */
static int
rfc1035NameUnpack(const char *buf, size_t sz, unsigned int *off, unsigned short *rdlength, char *name, size_t ns, int rdepth)
{
    unsigned int no = 0;
    unsigned char c;
    size_t len;
    assert(ns > 0);
    do {
        if ((*off) >= sz) {
            RFC1035_UNPACK_DEBUG;
            return 1;
        }
        c = *(buf + (*off));
        if (c > 191) {
            /* blasted compression */
            unsigned short s;
            unsigned int ptr;
            if (rdepth > 64) {  /* infinite pointer loop */
                RFC1035_UNPACK_DEBUG;
                return 1;
            }
            /* before copying compression offset value, ensure it is inside the buffer */
            if ((*off) + sizeof(s) > sz) {
                RFC1035_UNPACK_DEBUG;
                return 1;
            }
            memcpy(&s, buf + (*off), sizeof(s));
            s = ntohs(s);
            (*off) += sizeof(s);
            ptr = s & 0x3FFF;
            /* Make sure the pointer is inside this message */
            if (ptr >= sz) {
                RFC1035_UNPACK_DEBUG;
                return 1;
            }
            return rfc1035NameUnpack(buf, sz, &ptr, rdlength, name + no, ns - no, rdepth + 1);
        } else if (c > RFC1035_MAXLABELSZ) {
            /*
             * "(The 10 and 01 combinations are reserved for future use.)"
             */
            RFC1035_UNPACK_DEBUG;
            return 1;
        } else {
            (*off)++;
            len = (size_t) c;
            if (len == 0)
                break;
            if (len > (ns - no - 1)) {  /* label won't fit */
                RFC1035_UNPACK_DEBUG;
                return 1;
            }
            if ((*off) + len >= sz) {   /* message is too short */
                RFC1035_UNPACK_DEBUG;
                return 1;
            }
            memcpy(name + no, buf + (*off), len);
            (*off) += len;
            no += len;
            *(name + (no++)) = '.';
            if (rdlength)
                *rdlength += len + 1;
        }
    } while (c > 0 && no < ns);
    if (no)
        *(name + no - 1) = '\0';
    else
        *name = '\0';
    /* make sure we didn't allow someone to overflow the name buffer */
    assert(no <= ns);
    return 0;
}

/*
 * rfc1035RRPack()
 *
 * Packs a RFC1035 Resource Record into a message buffer from 'RR'.
 * The caller must allocate and free RR->rdata and RR->name!
 *
 * Updates the new message buffer.
 *
 * Returns the number of bytes added to the buffer or 0 for error.
 */
int
rfc1035RRPack(char *buf, const size_t sz, const rfc1035_rr * RR)
{
    unsigned int off;
    uint16_t s;
    uint32_t i;

    off = rfc1035NamePack(buf, sz, RR->name);

    /*
     * Make sure the remaining message has enough octets for the
     * rest of the RR fields.
     */
    if ((off + sizeof(s)*3 + sizeof(i) + RR->rdlength) > sz) {
        return 0;
    }
    s = htons(RR->type);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    s = htons(RR->_class);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    i = htonl(RR->ttl);
    memcpy(buf + off, &i, sizeof(i));
    off += sizeof(i);
    s = htons(RR->rdlength);
    memcpy(buf + off, &s, sizeof(s));
    off += sizeof(s);
    memcpy(buf + off, RR->rdata, RR->rdlength);
    off += RR->rdlength;
    assert(off <= sz);
    return off;
}

/*
 * rfc1035RRUnpack()
 *
 * Unpacks a RFC1035 Resource Record into 'RR' from a message buffer.
 * The caller must free RR->rdata!
 *
 * Updates the new message buffer offset.
 *
 * Returns 0 (success) or 1 (error)
 */
static int
rfc1035RRUnpack(const char *buf, size_t sz, unsigned int *off, rfc1035_rr * RR)
{
    unsigned short s;
    unsigned int i;
    unsigned short rdlength;
    unsigned int rdata_off;
    if (rfc1035NameUnpack(buf, sz, off, nullptr, RR->name, RFC1035_MAXHOSTNAMESZ, 0)) {
        RFC1035_UNPACK_DEBUG;
        memset(RR, '\0', sizeof(*RR));
        return 1;
    }
    /*
     * Make sure the remaining message has enough octets for the
     * rest of the RR fields.
     */
    if ((*off) + 10 > sz) {
        RFC1035_UNPACK_DEBUG;
        memset(RR, '\0', sizeof(*RR));
        return 1;
    }
    memcpy(&s, buf + (*off), sizeof(s));
    (*off) += sizeof(s);
    RR->type = ntohs(s);
    memcpy(&s, buf + (*off), sizeof(s));
    (*off) += sizeof(s);
    RR->_class = ntohs(s);
    memcpy(&i, buf + (*off), sizeof(i));
    (*off) += sizeof(i);
    RR->ttl = ntohl(i);
    memcpy(&s, buf + (*off), sizeof(s));
    (*off) += sizeof(s);
    rdlength = ntohs(s);
    if ((*off) + rdlength > sz) {
        /*
         * We got a truncated packet.  'dnscache' truncates UDP
         * replies at 512 octets, as per RFC 1035.
         */
        RFC1035_UNPACK_DEBUG;
        memset(RR, '\0', sizeof(*RR));
        return 1;
    }
    RR->rdlength = rdlength;
    switch (RR->type) {
    case RFC1035_TYPE_PTR:
        RR->rdata = (char*)xmalloc(RFC1035_MAXHOSTNAMESZ);
        rdata_off = *off;
        RR->rdlength = 0;   /* Filled in by rfc1035NameUnpack */
        if (rfc1035NameUnpack(buf, sz, &rdata_off, &RR->rdlength, RR->rdata, RFC1035_MAXHOSTNAMESZ, 0)) {
            RFC1035_UNPACK_DEBUG;
            xfree(RR->rdata);
            memset(RR, '\0', sizeof(*RR));
            return 1;
        }
        if (rdata_off > ((*off) + rdlength)) {
            /*
             * This probably doesn't happen for valid packets, but
             * I want to make sure that NameUnpack doesn't go beyond
             * the RDATA area.
             */
            RFC1035_UNPACK_DEBUG;
            xfree(RR->rdata);
            memset(RR, '\0', sizeof(*RR));
            return 1;
        }
        break;
    case RFC1035_TYPE_A:
    default:
        RR->rdata = (char*)xmalloc(rdlength);
        memcpy(RR->rdata, buf + (*off), rdlength);
        break;
    }
    (*off) += rdlength;
    assert((*off) <= sz);
    return 0;
}

const char *
rfc1035ErrorMessage(int n)
{
    if (n < 0)
        n = -n;
    switch (n) {
    case 0:
        return "No error condition";
        break;
    case 1:
        return "Format Error: The name server was "
               "unable to interpret the query.";
        break;
    case 2:
        return "Server Failure: The name server was "
               "unable to process this query.";
        break;
    case 3:
        return "Name Error: The domain name does "
               "not exist.";
        break;
    case 4:
        return "Not Implemented: The name server does "
               "not support the requested kind of query.";
        break;
    case 5:
        return "Refused: The name server refuses to "
               "perform the specified operation.";
        break;
    case rfc1035_unpack_error:
        return "The DNS reply message is corrupt or could "
               "not be safely parsed.";
        break;
    default:
        return "Unknown Error";
        break;
    }
}

void
rfc1035RRDestroy(rfc1035_rr ** rr, int n)
{
    if (*rr == nullptr) {
        return;
    }

    while (n-- > 0) {
        if ((*rr)[n].rdata)
            xfree((*rr)[n].rdata);
    }
    xfree(*rr);
    *rr = nullptr;
}

/*
 * rfc1035QueryUnpack()
 *
 * Unpacks a RFC1035 Query Record into 'query' from a message buffer.
 *
 * Updates the new message buffer offset.
 *
 * Returns 0 (success) or 1 (error)
 */
static int
rfc1035QueryUnpack(const char *buf, size_t sz, unsigned int *off, rfc1035_query * query)
{
    uint16_t s;
    if (rfc1035NameUnpack(buf, sz, off, nullptr, query->name, RFC1035_MAXHOSTNAMESZ, 0)) {
        RFC1035_UNPACK_DEBUG;
        memset(query, '\0', sizeof(*query));
        return 1;
    }
    if (*off + 4 > sz) {
        RFC1035_UNPACK_DEBUG;
        memset(query, '\0', sizeof(*query));
        return 1;
    }
    memcpy(&s, buf + *off, sizeof(s));
    *off += sizeof(s);
    query->qtype = ntohs(s);
    memcpy(&s, buf + *off, sizeof(s));
    *off += sizeof(s);
    query->qclass = ntohs(s);
    return 0;
}

void
rfc1035MessageDestroy(rfc1035_message ** msg)
{
    if (!*msg)
        return;
    if ((*msg)->query)
        xfree((*msg)->query);
    if ((*msg)->answer)
        rfc1035RRDestroy(&(*msg)->answer, (*msg)->ancount);
    xfree(*msg);
    *msg = nullptr;
}

/*
 * rfc1035QueryCompare()
 *
 * Compares two rfc1035_query entries
 *
 * Returns 0 (equal) or !=0 (different)
 */
int
rfc1035QueryCompare(const rfc1035_query * a, const rfc1035_query * b)
{
    size_t la, lb;
    if (a->qtype != b->qtype)
        return 1;
    if (a->qclass != b->qclass)
        return 1;
    la = strlen(a->name);
    lb = strlen(b->name);
    if (la != lb) {
        /* Trim root label(s) */
        while (la > 0 && a->name[la - 1] == '.')
            la--;
        while (lb > 0 && b->name[lb - 1] == '.')
            lb--;
    }
    if (la != lb)
        return 1;

    return strncasecmp(a->name, b->name, la);
}

/*
 * rfc1035MessageUnpack()
 *
 * Takes the contents of a DNS reply and fills in an array
 * of resource record structures.  The records array is allocated
 * here, and should be freed by calling rfc1035RRDestroy().
 *
 * Returns number of records unpacked, zero if DNS reply indicates
 * zero answers, or an error number < 0.
 */

int
rfc1035MessageUnpack(const char *buf,
                     size_t sz,
                     rfc1035_message ** answer)
{
    unsigned int off = 0;
    unsigned int i, j;
    unsigned int nr = 0;
    rfc1035_message *msg = nullptr;
    rfc1035_rr *recs = nullptr;
    rfc1035_query *querys = nullptr;
    msg = (rfc1035_message*)xcalloc(1, sizeof(*msg));
    if (rfc1035HeaderUnpack(buf + off, sz - off, &off, msg)) {
        RFC1035_UNPACK_DEBUG;
        xfree(msg);
        return -rfc1035_unpack_error;
    }
    i = (unsigned int) msg->qdcount;
    if (i != 1) {
        /* This can not be an answer to our queries.. */
        RFC1035_UNPACK_DEBUG;
        xfree(msg);
        return -rfc1035_unpack_error;
    }
    querys = msg->query = (rfc1035_query*)xcalloc(i, sizeof(*querys));
    for (j = 0; j < i; j++) {
        if (rfc1035QueryUnpack(buf, sz, &off, &querys[j])) {
            RFC1035_UNPACK_DEBUG;
            rfc1035MessageDestroy(&msg);
            return -rfc1035_unpack_error;
        }
    }
    *answer = msg;
    if (msg->rcode) {
        RFC1035_UNPACK_DEBUG;
        return -msg->rcode;
    }
    if (msg->ancount == 0)
        return 0;
    i = (unsigned int) msg->ancount;
    recs = msg->answer = (rfc1035_rr*)xcalloc(i, sizeof(*recs));
    for (j = 0; j < i; j++) {
        if (off >= sz) {    /* corrupt packet */
            RFC1035_UNPACK_DEBUG;
            break;
        }
        if (rfc1035RRUnpack(buf, sz, &off, &recs[j])) {     /* corrupt RR */
            RFC1035_UNPACK_DEBUG;
            break;
        }
        nr++;
    }
    if (nr == 0) {
        /*
         * we expected to unpack some answers (ancount != 0), but
         * didn't actually get any.
         */
        rfc1035MessageDestroy(&msg);
        *answer = nullptr;
        return -rfc1035_unpack_error;
    }
    return nr;
}

/*
 * rfc1035BuildAQuery()
 *
 * Builds a message buffer with a QUESTION to lookup A records
 * for a hostname.  Caller must allocate 'buf' which should
 * probably be at least 512 octets.  The 'szp' initially
 * specifies the size of the buffer, on return it contains
 * the size of the message (i.e. how much to write).
 * Returns the size of the query
 */
ssize_t
rfc1035BuildAQuery(const char *hostname, char *buf, size_t sz, unsigned short qid, rfc1035_query * query, ssize_t edns_sz)
{
    static rfc1035_message h;
    size_t offset = 0;
    memset(&h, '\0', sizeof(h));
    h.id = qid;
    h.qr = 0;
    h.rd = 1;
    h.opcode = 0;       /* QUERY */
    h.qdcount = (unsigned int) 1;
    h.arcount = (edns_sz > 0 ? 1 : 0);
    offset += rfc1035HeaderPack(buf + offset, sz - offset, &h);
    offset += rfc1035QuestionPack(buf + offset,
                                  sz - offset,
                                  hostname,
                                  RFC1035_TYPE_A,
                                  RFC1035_CLASS_IN);
    if (edns_sz > 0)
        offset += rfc2671RROptPack(buf + offset, sz - offset, edns_sz);
    if (query) {
        query->qtype = RFC1035_TYPE_A;
        query->qclass = RFC1035_CLASS_IN;
        xstrncpy(query->name, hostname, sizeof(query->name));
    }
    assert(offset <= sz);
    return offset;
}

/*
 * rfc1035BuildPTRQuery()
 *
 * Builds a message buffer with a QUESTION to lookup PTR records
 * for an address.  Caller must allocate 'buf' which should
 * probably be at least 512 octets.  The 'szp' initially
 * specifies the size of the buffer, on return it contains
 * the size of the message (i.e. how much to write).
 * Returns the size of the query
 */
ssize_t
rfc1035BuildPTRQuery(const struct in_addr addr, char *buf, size_t sz, unsigned short qid, rfc1035_query * query, ssize_t edns_sz)
{
    static rfc1035_message h;
    size_t offset = 0;
    static char rev[32];
    unsigned int i;
    memset(&h, '\0', sizeof(h));
    i = (unsigned int) ntohl(addr.s_addr);
    snprintf(rev, 32, "%u.%u.%u.%u.in-addr.arpa.",
             i & 255,
             (i >> 8) & 255,
             (i >> 16) & 255,
             (i >> 24) & 255);
    h.id = qid;
    h.qr = 0;
    h.rd = 1;
    h.opcode = 0;       /* QUERY */
    h.qdcount = (unsigned int) 1;
    h.arcount = (edns_sz > 0 ? 1 : 0);
    offset += rfc1035HeaderPack(buf + offset, sz - offset, &h);
    offset += rfc1035QuestionPack(buf + offset,
                                  sz - offset,
                                  rev,
                                  RFC1035_TYPE_PTR,
                                  RFC1035_CLASS_IN);
    if (edns_sz > 0)
        offset += rfc2671RROptPack(buf + offset, sz - offset, edns_sz);
    if (query) {
        query->qtype = RFC1035_TYPE_PTR;
        query->qclass = RFC1035_CLASS_IN;
        xstrncpy(query->name, rev, sizeof(query->name));
    }
    assert(offset <= sz);
    return offset;
}

/*
 * We're going to retry a former query, but we
 * just need a new ID for it.  Lucky for us ID
 * is the first field in the message buffer.
 */
void
rfc1035SetQueryID(char *buf, unsigned short qid)
{
    unsigned short s = htons(qid);
    memcpy(buf, &s, sizeof(s));
}

