/* Copyright (c) 2009-2015 Sam Trenholme
 *
 * TERMS
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * This software is provided 'as is' with no guarantees of correctness or
 * fitness for purpose.
 */

/* DwRecurse.c: Used for functions and framework so Deadwood has full
 * recursive DNS support */

#include "DwStr.h"
#include "DwStr_functions.h"
#include "DwDnsStr.h"
#include "DwHash.h"
#include "DwMararc.h"
#include "DwSys.h"
#include "DwRecurse.h"
#include "DwSocket.h"
#include "DwRadioGatun.h"
#include "DwCompress.h"
#include "DwDnsStr.h"
/* Numeric values for dwood3rc parameters */
extern int32_t key_n[];
/* List of pending remote UDP connections */
extern remote_T *rem;
extern SOCKET *b_remote;
/* DNS recursive cache */
extern dw_hash *cache;
/* Timestamp */
extern int64_t the_time;
extern int timeout_seconds;
/* Random number generator */
extern dwr_rg *rng_seed;
/* The list of active in-flight connections */
extern dw_hash *inflight;
/* Number of times to retry; needed for reset_rem() prototype */
extern int num_retries;
#ifdef MINGW
extern u_long dont_block;
#endif /* MINGW */
/* Numeric mararc parameters */
extern int32_t max_ttl;
/* Maximum number of remote connections */
extern int_fast32_t maxprocs;

#ifdef OTHER_STUFF
/* Show a single character on the standard output, escaping the
 * character if it's not printable ASCII */

void dwx_stdout_char(dw_str *in, int32_t offset) {
        int8_t c = 0;

        if(in == 0 || in->str == 0 || offset > in->len) {
                return;
        }
        c = *(in->str + offset);

        if(c < 33 || c == '.' || c == '\\' || c > 126) {
                printf("\\x%02x",c & 0xff);
        } else {
                printf("%c",c);
        }
}

/* Show a human-readable form of the RR type for a record */
void dwx_stdout_rrtype(dw_str *in, int32_t offset) {
        int32_t type;

        type = dw_fetch_u16(in,offset);

        if(type < 0 || type > 65535) {
                printf("-- BAD RR TYPE -- ");
                return;
        } else if(type == RR_A) {
                printf("A ");
        } else if(type == RR_NS) {
                printf("NS ");
        } else if(type == RR_CNAME) {
                printf("CNAME ");
        } else if(type == RR_SOA) {
                printf("SOA ");
        } else if(type == RR_AAAA) {
                printf("AAAA ");
        } else {
                printf("%d ",type);
        }

}

/* Given a string with one or more DNS RRs in it, and a pointer to
 * the beginning of an RR, output on standard output a human-readable
 * form of the record in question
 */

int32_t dwx_stdout_rr(dw_str *in, int32_t offset) {
        int32_t len = 1, counter = 0;

        if(in == 0 || in->str == 0) {
                return -1;
        }

        /* Name */
        for(counter = 0; counter < 300; counter++) {
                len = *(in->str + offset);
                if(len < 0 || len > 63) { /* ERROR */
                        printf("%s"," -- ERROR --\n");
                        return -1;
                }
                offset++;
                if(len == 0) {
                        break;
                }
                if(counter > 0) {
                        printf("%c",'.');
                }
                while(len > 0) {
                        dwx_stdout_char(in,offset);
                        offset++;
                        len--;
                }
        }
        printf("%c ",'.');
        dwx_stdout_rrtype(in,offset);
        offset += 8; /* Past CLASS and TTL on to RDLENGTH */
        len = dw_fetch_u16(in,offset);
        if(len < 0 || len > 65535) {
                printf("%s\n","--ERROR GETTING RDLENGTH--\n");
                return -1;
        }
        offset += 2;
        while(len > 0) {
                dwx_stdout_char(in,offset);
                offset++;
                len--;
        }
        printf("\n");
        if(offset >= in->len) {
                return -1;
        }
        return offset;
}

/* Show an entire DNS reply on the standard output */
void dwx_stdout_dns_packet(dw_str *packet) {
        int32_t offset = 0;

        /* Quick and dirty; buggy and shows stuff after DNS data (the offsets)
         * but good enough for debugging */
        while(offset >= 0) {
                offset = dwx_stdout_rr(packet,offset);
        }
}

#endif /* OTHER_STUFF */

/* See if two strings pointing to dname objects are the
 * same.  p1: pointer to the first string; p2: pointer to the second
 * string; pmax: Maximum possible value for either string
 * 1 if they are, 0 if they are not, -1 on error */

int dwx_dname_issame(uint8_t *p1, uint8_t *p2, uint8_t *pmax1,
                uint8_t *pmax2) {
        int len = 0, counter = 0;
        uint8_t lc1, lc2; /* Lower case */

        for(counter = 0; counter < 260; counter++) {
                if(*p1 != *p2) { /* Both labels must be the same length */
                        return 0;
                }

                len = *p1;
                if(len > 63 || len < 0) {
                        return -1; /* Invalid length */
                }
                if(len == 0) {
                        return 1; /* Same */
                }

                for(;len >= 0 && counter < 260; len--) {
                        p1++;
                        p2++;
                        counter++;
                        /* Make sure we do not go out of bounds */
                        if(p1 > pmax1 || p2 > pmax2) {
                                return -1;
                        }
                        /* lc1/2 used to make label compare case insensitive */
                        lc1 = *p1;
                        lc2 = *p2;
                        if(lc1 >= 'A' && lc1 <= 'Z') { lc1 += 32; }
                        if(lc2 >= 'A' && lc2 <= 'Z') { lc2 += 32; }
                        if(lc1 != lc2) {
                                return 0;
                        }
                }
        }
        return -1;
}

/* See if two domain names embedded in a Deadwood string object are the
 * same.  1 if they are, 0 if they are not, -1 on error */
int dwx_dname_issame_dw(dw_str *in, uint32_t offset1, uint32_t offset2) {
        uint8_t *p1 = 0, *p2 = 0, *pmax = 0;

        if(dw_assert_sanity(in) == -1 ||
                        offset1 > in->len || offset2 > in->len) {
                return -1;
        }

        p1 = in->str + offset1;
        p2 = in->str + offset2;
        pmax = in->str + in->len;

        return dwx_dname_issame(p1,p2,pmax,pmax);

}

/* See if two domain name objects embedded in different Deadwood string
 * objects are the same.  1 if they are, 0 if they are not, -1 on error */
int dwx_dname_issame_2dw(dw_str *in1, uint32_t offset1, dw_str *in2,
                        uint32_t offset2) {
        uint8_t *p1 = 0, *p2 = 0, *pmax1 = 0, *pmax2 = 0;

        if(dw_assert_sanity(in1) == -1 || dw_assert_sanity(in2) == -1 ||
                        offset1 > in1->len || offset2 > in2->len) {
                return -1;
        }

        p1 = in1->str + offset1;
        p2 = in2->str + offset2;
        pmax1 = in1->str + in1->len;
        pmax2 = in2->str + in2->len;

        return dwx_dname_issame(p1,p2,pmax1,pmax2);

}

/* Destroy a dns_details object */
void dwx_zap_dns_details(dns_details *victim) {
        if(victim == 0) {
                return;
        }
        if(victim->an_types != 0) {
                free(victim->an_types);
                victim->an_types = 0;
        }
        if(victim->ns_types != 0) {
                free(victim->ns_types);
                victim->ns_types = 0;
        }
        if(victim->ar_types != 0) {
                free(victim->ar_types);
                victim->ar_types = 0;
        }
        if(victim->look != 0) {
                dwc_zap_dns_str(victim->look);
                victim->look = 0;
        }
        free(victim);
}

/* Convert a DNS rr type in to a {an/ns/ar_type} number */
int8_t dwx_rrtype_number(int32_t rr) {
        if(rr < 1) {
                return -1; /* Error */
        }
        if(rr >= RRX_END_OF_LIST) {
                return RRX_INFINITY;
        }
        /* Assertion sanity tests */
        if(RRX_INFINITY <= RRX_END_OF_LIST) {
                dw_fatal("RR_INFNITY must be greater than RRX_END_OF_LIST\n");
        }
        if((RRX_INFINITY & (RRX_INFINITY + 1)) != 0) {
                dw_fatal("RRX_INFINITY + 1 must be a power of 2\n");
        }
        return rr;
}

/* Create a blank dns_details structure */
dns_details *dwx_init_dns_details(dns_string *look) {
        dns_details *out = 0;

        if(look == 0) {
                return 0;
        }

        out = dw_malloc(sizeof(dns_details) + 1);
        if(out == 0) {
                dw_fatal("Fatal: Can not init dns_details\n");
        }
        out->look = look;
        out->an_types = dw_malloc((sizeof(int8_t) * look->ancount) + 1);
        out->ns_types = dw_malloc((sizeof(int8_t) * look->nscount) + 1);
        out->ar_types = dw_malloc((sizeof(int8_t) * look->arcount) + 1);
        return out;
}

#ifdef XTRA_STUFF
/* Show a dns_details string on the standard output; used for debugging
 * purposes */

void dwx_stdout_dns_details(dns_details *view) {
        int counter = 0;
        if(view == 0) {
                printf("NULL dns_details\n");
                return;
        }
        if(view->look == 0) {
                printf("NULL dns_details look member\n");
                return;
        }
        for(counter = 0; counter < view->look->ancount; counter++) {
                printf("view->an_types[%d] = %d\n",counter,
                                view->an_types[counter]);
        }
        for(counter = 0; counter < view->look->nscount; counter++) {
                printf("view->ns_types[%d] = %d\n",counter,
                                view->ns_types[counter]);
        }
        for(counter = 0; counter < view->look->arcount; counter++) {
                printf("view->ar_types[%d] = %d\n",counter,
                                view->ar_types[counter]);
        }
}
#endif /* XTRA_STUFF */

/* Take a DNS packet and pull out the queries.  We want to know the record
 * type for each response in the packet, and whether a given response has the
 * same name as our original query */

dns_details *dwx_create_dns_details(dw_str *in, dw_str *query) {
        dns_string *look = 0;
        dns_details *out = 0;
        int counter = 0;

        if(in == 0) {
                return 0;
        }

        look = dwc_make_dns_str(in);
        if(look == 0) {
                goto catch_dwx_create_dns_details;
        }
        out = dwx_init_dns_details(look);
        if(out == 0) {
                goto catch_dwx_create_dns_details;
        }

        for(counter = 0; counter < look->ancount * 2; counter+=2) {
                out->an_types[(counter >> 1)] =
                     dwx_rrtype_number(dw_fetch_u16(in,look->an[counter + 1]));
        }
        for(counter = 0; counter < look->nscount * 2; counter+=2) {
                out->ns_types[(counter >> 1)] =
                     dwx_rrtype_number(dw_fetch_u16(in,look->ns[counter + 1]));
        }
        for(counter = 0; counter < look->arcount * 2; counter+=2) {
                out->ar_types[(counter >> 1)] =
                     dwx_rrtype_number(dw_fetch_u16(in,look->ar[counter + 1]));
        }

        return out;

catch_dwx_create_dns_details:
        if(look != 0 && (out == 0 || out->look != look)) {
                dwc_zap_dns_str(look);
        }
        if(out != 0) {
                dwx_zap_dns_details(out);
        }
        return 0;
}

int dwx_check_answer_section_any(dw_str *in, dw_str *query,dns_details *view) {
        int32_t qtype = 0; /* The type of record they want in the query */
        int counter = 0, cname_chain = 0, use_cname = 1;

        qtype = dw_fetch_u16(query,-1);
        if(qtype != RR_ANY) {
                return -1;
        }
        if(view == 0 || view->look == 0 || view->look->an == 0) {
                return -1;
        }

        for(counter = 0; counter < view->look->ancount * 2; counter +=2) {
                if(dwx_dname_issame_2dw(query,0,
                                in,view->look->an[counter]) == 1 &&
                                dw_fetch_u16(in,view->look->an[counter + 1]) !=
                                RR_CNAME) {
                                /* Direct answer for our query */
                        use_cname = 0; /* No CNAMEs after direct answer */
                } else if(cname_chain == 0 &&
                                (counter >> 1) == cname_chain && use_cname == 1
                                && dwx_dname_issame_2dw(query,0,in,
                                view->look->an[counter]) == 1 &&
                                dw_fetch_u16(in,view->look->an[counter + 1]) ==
                                RR_CNAME) { /* First answer a CNAME */
                        cname_chain++;
                } else if(cname_chain > 0 &&
                                (counter >> 1) == cname_chain && use_cname == 1
                                && dwx_dname_issame_dw(in,
                                view->look->an[((cname_chain - 1) * 2) + 1]
                                + 10 /* 10: TYPE, CLASS, TTL, RDLENGTH */,
                                view->look->an[counter]) == 1 &&
                                dw_fetch_u16(in,view->look->an[counter + 1]) ==
                                RR_CNAME) { /* CNAME chain member */
                        cname_chain++;
                } else if(cname_chain > 0 &&
                                dwx_dname_issame_dw(in,
                                view->look->an[((cname_chain - 1) * 2) + 1]
                                + 10 /* 10: TYPE, CLASS, TTL, RDLENGTH */,
                                view->look->an[counter]) == 1 &&
                                dw_fetch_u16(in,view->look->an[counter + 1]) !=
                                RR_CNAME) {
                        /* Answer after CNAME chain */
                        use_cname = 0; /* Answer found; no more CNAMEs */
                } else { /* Not an answer to our query nor a CNAME chain */
                        view->an_types[(counter >> 1)] = RRX_IGNORE;
                }
        }

        return 1;
}

/* Look at the responses in the answer section.  The only allowed responses
 * are responses where:
 *
 * 1) It is a direct reply to our query
 *
 * 2) It is a CNAME reply to our query
 *
 * 3) It is a CNAME or direct reply to where the immediate previous CNAME
 *    pointed to
 *
 * DNS replies in the answer section that do not meet the above
 * criteria in the AN section are marked RRX_IGNORE
 *
 * This function has the side effect of handling the unusual case where the
 * DNS stub resolver asks for a CNAME answer by ignoring everything after the
 * first link in a CNAME chain in the answer.
 *
 * Output: -1 on error, 1 on success
 */

int dwx_check_answer_section(dw_str *in, dw_str *query, dns_details *view) {
        int32_t qtype = 0; /* The type of record they want in the query */
        int counter = 0, cname_chain = 0, use_cname = 1, offset = 0;

        qtype = dw_fetch_u16(query,-1);
        if(qtype == -1) {
                return -1;
        }
        if(qtype == RR_ANY) {
                return dwx_check_answer_section_any(in, query, view);
        }
        if(view == 0 || view->look == 0 || view->look->an == 0) {
                return -1;
        }

        for(counter = 0; counter < view->look->ancount * 2; counter +=2) {
                if(dwx_dname_issame_2dw(query,0,
                                in,view->look->an[counter]) == 1 &&
                                dw_fetch_u16(in,view->look->an[counter + 1]) ==
                                qtype) { /* Direct answer for our query */
                        offset++;
                        use_cname = 0; /* No CNAMEs after direct answer */
                } else if(cname_chain == 0 && qtype != RR_CNAME &&
                                offset == cname_chain &&
                                use_cname == 1 &&
                                dwx_dname_issame_2dw(query,0,in,
                                view->look->an[counter]) == 1 &&
                                dw_fetch_u16(in,view->look->an[counter + 1]) ==
                                RR_CNAME) { /* First answer a CNAME */
                        offset++;
                        cname_chain++;
                } else if(cname_chain > 0 && qtype != RR_CNAME &&
                                offset == cname_chain &&
                                use_cname == 1 &&
                                dwx_dname_issame_dw(in,
                                view->look->an[((cname_chain - 1) * 2) + 1]
                                + 10 /* 10: TYPE, CLASS, TTL, RDLENGTH */,
                                view->look->an[counter]) == 1 &&
                                dw_fetch_u16(in,view->look->an[counter + 1]) ==
                                RR_CNAME) { /* CNAME chain member */
                        offset++;
                        cname_chain++;
                } else if(cname_chain > 0 && qtype != RR_CNAME &&
                                dwx_dname_issame_dw(in,
                                view->look->an[((cname_chain - 1) * 2) + 1]
                                + 10 /* 10: TYPE, CLASS, TTL, RDLENGTH */,
                                view->look->an[counter]) == 1 &&
                                dw_fetch_u16(in,view->look->an[counter + 1]) ==
                                qtype) { /* Answer after CNAME chain */
                        offset++;
                        use_cname = 0; /* Answer found; no more CNAMEs */
                } else { /* Not an answer to our query nor a CNAME chain */
                        view->an_types[(counter >> 1)] = RRX_IGNORE;
                }
        }

        return 1;
}

/* A DNS packet with an answer section has all records in the NS
 * and AR section ignored.  This modifies view and outputs -1 on
 * error, 1 on success. */

int dwx_if_an_then_no_ns_nor_ar(dns_details *view) {
        int counter = 0;
        if(view == 0 || view->look == 0 || view->ns_types == 0 ||
                        view->ar_types == 0) {
                return -1;
        }

        if(view->look->ancount == 0) {
                return 1; /* No answer (AN) section; nothing to do */
        }

        for(counter = 0; counter < view->look->nscount * 2; counter += 2) {
                view->ns_types[(counter >> 1)] = RRX_IGNORE;
        }
        for(counter = 0; counter < view->look->arcount * 2; counter += 2) {
                view->ar_types[(counter >> 1)] = RRX_IGNORE;
        }

        return 1;
}

/* Clean up the NS and AR section.  Only NS and SOA records are used in the NS
 * section; only A and AAAA records are used in the AR (glue) section.
 * -1 on error; 1 on success */

int dwx_cleanup_ns_ar(dns_details *view) {
        int counter = 0;

        if(view == 0 || view->look == 0 || view->ns_types == 0 ||
                        view->ar_types == 0) {
                return -1;
        }

        for(counter = 0; counter < view->look->nscount * 2; counter += 2) {
                if(view->ns_types[(counter >> 1)] != RR_NS &&
                   view->ns_types[(counter >> 1)] != RR_SOA) {
                        view->ns_types[(counter >> 1)] = RRX_IGNORE;
                }
        }
        for(counter = 0; counter < view->look->arcount * 2; counter += 2) {
                if(view->ar_types[(counter >> 1)] != RR_A &&
                   view->ar_types[(counter >> 1)] != RR_AAAA) {
                        view->ar_types[(counter >> 1)] = RRX_IGNORE;
                }
        }
        return 1;
}

#ifdef XTRA_STUFF
/* This is scaffolding during the development cycle: Create a
 * "\0" string (DNS root) to use as a bailiwick */

dw_str *dwx_example_root_bailiwick() {
        dw_str *out;
        out = dw_create(3);
        if(out == 0) {
                return 0;
        }
        out->len = 1;
        *(out->str) = 0;
        return out;
}

/* And, likewise, make a dummy ".org" ("\3org\0") bailiwick */

dw_str *dwx_example_org_bailiwick() {
        dw_str *out;
        out = dw_create(7);
        if(out == 0) {
                return 0;
        }
        out->len = 5;
        *(out->str + 0) = 3;
        *(out->str + 1) = 'o';
        *(out->str + 2) = 'r';
        *(out->str + 3) = 'g';
        *(out->str + 4) = 0;
        return out;
}
#endif /* XTRA_STUFF */

/* Given a string with a domain name we are inspecting, the offset in
 * the string with the beginning of the domain name, the baliwick, and our
 * original query, see if the string is in bailiwick.
 *
 * Output:
 *
 * -1: Error
 *
 * 0: Not in bailiwick
 *
 * 1: In bailiwick
 *
 *
 * In more detail:
 *
 * Part 1:
 *
 * Look at what the NS record points at and the query sent by the client.
 *
 * If they are the same, go to the next part of the bailiwick check.
 *
 * Remove one label from our query (make www.example.com example.com or
 * make example.com simply .com, or make .com the name of the root server)
 *
 * See if they are the same; if they are we pass this part; if not, keep
 * lopping off labels until we match or the query is less than zero-length
 * (fail, exit routine)
 *
 * Part two:
 *
 * Let's get the Bailiwick for this query (if this NS record was told by
 * us upstream that these are records for .org; the bailiwick is .org; if
 * this is a root server, tha bailiwick is any querty; if this NS record was
 * told by us upstream this is for example.com, the bailiwick is example.com,
 * and so on)
 *
 * Let's remove one label from the NS record given to us
 *
 * Let's compare the NS record with the bailiwick this NS record has
 *
 * If they are the same, we're gold and have passed the bailiwick check
 *
 * Otherwise, remove labels from the NS record and compare with the
 * bailiwick until we either get a match or the truncated NS record is
 * shorter than our bailiwick. If we get a match, it's gold, otherwise
 * it's out-of-bailiwick
 */

/* Part one of the bailiwick check: Make sure the NS record is part of
 * our original query; in other words, make sure they're not giving us
 * NS records for paypal.com when we ask for malware.com.  If we ask for
 * makware.com, they're allowed to give us .com and malware.com NS records,
 * but not paypal.com
 *
 * Return 1 if in bailiwick and we can proceed; otherwise stop.
 *
 */
int dwx_string_bailiwick_query(dw_str *in, int32_t offset, dw_str *query) {

        int32_t q_offset = 0; /* Offset in query string */
        int noloop = 0;
        int32_t len = 0;

        if(query == 0 || in == 0) {
                return 0; /* Error */
        }

        if(query->len <= 2) {
                return 0; /* Error */
        }

        if(dwx_dname_issame_2dw(in,offset,query,0) == 1) {
                return 1; /* NS records same as name asked for */
        }

        /* Lop of names from the top of our query until it matches the
         * NS record offered; if no match is found, it is OOB */
        while(noloop < 1024 && *(query->str + q_offset) != 0) {

                /* Lop one label off of the query we asked for */
                len = *(query->str + q_offset);
                if(len < 0 || len > 63) {
                        return 0; /* Error */
                }
                q_offset += len;
                q_offset++;

                if(q_offset < 0 || q_offset >= query->len) {
                        return 0; /* Error */
                }

                if(dwx_dname_issame_2dw(in,offset,query,q_offset) == 1) {
                        return 1; /* NS record same as truncated query */
                }

                noloop++;
        }

        return 0; /* No match found */
}

/* Part two of the bailiwick check:
 *
 * See if the NS record we have is a subname (longer) for the bailiwick
 * we allow this query to have
 *
 */
int dwx_string_bailiwick_top(dw_str *in, int32_t offset, dw_str *bailiwick) {

        int noloop = 0;
        int32_t len = 0;

        if(bailiwick == 0 || in == 0) {
                return 0; /* Error */
        }

        if(offset < 0 || offset >= in->len) {
                return 0;
        }

        while(noloop < 1024 && *(in->str + offset) != 0) {

                len = *(in->str + offset);
                if(len < 0 || len > 63) {
                        return 0; /* Error */
                }

                offset += len;
                offset++;

                if(offset < 0 || offset >= in->len) {
                        return 0;
                }

                if(dwx_dname_issame_2dw(in,offset,bailiwick,0) == 1) {
                        return 1; /* In bailiwick */
                }

                noloop++;
        }

        return 0; /* No match found */
}

int dwx_string_in_bailiwick(dw_str *in, int32_t offset, dw_str *bailiwick,
                dw_str *query) {

        if(dwx_string_bailiwick_query(in,offset,query) != 1) {
                return 0;
        }

        if(dwx_string_bailiwick_top(in,offset,bailiwick) != 1) {
                return 0;
        }

        return 1;
}

/* Verify the bailiwick of the NS records.  A NS record is marked
 * out-of-bailiwick if it does not have at least one domain label more
 * than the current bailick (NS referrals must always bring us closer to
 * the answer we seek) or if its suffix is not the same as the name we
 * are actually seeking (we don't allow NS referrals to paypal.com when
 * looking up hackersite.example.com) */

int dwx_check_bailiwick_ns_section(dns_details *view, dw_str *query,
                dw_str *bailiwick) {
        int ns = 0;
        dw_str *in = 0;
        dns_string *look = 0;
        int max = 0;

        if(view == 0 || view->look == 0 || view->ns_types == 0) {
                return -1;
        }

        look = view->look;

        if(look->nscount <= 0) {
                return 1; /* No NS records */
        }
        if(look->nscount > GLUE_RANGE) {
                max = GLUE_RANGE;
        } else {
                max = look->nscount;
        }

        in = look->packet;
        if(in == 0) {
                return -1;
        }
        for(ns = 0; ns < max * 2; ns += 2) { /* For each NS record */
                /* Check its bailiwick */
                if(dwx_string_in_bailiwick(in,look->ns[ns],bailiwick,
                                query) != 1) {
                        view->ns_types[(ns >> 1)] = RRX_OOB_NS;
                }
        }
        return 1;
}

/* Make a single link between a NS record in the NS section and a glue
 * record in the AR section */
void dwx_make_link(dns_details *view, int ns, int ar) {
        if(view == 0 || view->look == 0) {
                return;
        }
        if((ns >> 1) > GLUE_RANGE) {
                return;
        }

        /* The NS record us marked as having glue */
        if(view->ar_types[(ar >> 1)] == RR_A ||
           (view->ar_types[(ar >> 1)] >= RRX_FIRST_A_GLUE &&
            view->ar_types[(ar >> 1)] <= RRX_LAST_A_GLUE)) {
                view->ns_types[(ns >> 1)] |= RRX_A_GLUE_NS;
        } else if(view->ar_types[(ar >> 1)] == RR_AAAA ||
           (view->ar_types[(ar >> 1)] >= RRX_FIRST_AAAA_GLUE &&
            view->ar_types[(ar >> 1)] <= RRX_LAST_AAAA_GLUE)) {
                view->ns_types[(ns >> 1)] |= RRX_AAAA_GLUE_NS;
        }

        /* The AR record has the glue record marked */
        if(view->ar_types[(ar >> 1)] == RR_A) {
                view->ar_types[(ar >> 1)] = RRX_FIRST_A_GLUE + (ns >> 1);
        } else if(view->ar_types[(ar >> 1)] == RR_AAAA) {
                view->ar_types[(ar >> 1)] = RRX_FIRST_AAAA_GLUE + (ns >> 1);
        }

}

/* Link NS records in the NS section of a DNS packet to their corresponding
 * A and AAAA glue records in the AR section.   Only do this *after*
 * confirming the NS records have acceptable bailiwick */

int dwx_link_ns_records(dns_details *view) {
        int ns = 0; /* The NS record we are looking at */
        int ar = 0; /* The AR (glue) record we are looking at */
        int max = 0;
        dw_str *in = 0;
        dns_string *look = 0;

        if(view == 0 || view->look == 0) {
                return -1;
        }

        look = view->look;

        if(look->nscount > GLUE_RANGE) { /* Cap on NS records */
                max = GLUE_RANGE;
        } else {
                max = look->nscount;
        }

        in = look->packet;

        for(ns = 0; ns < max * 2; ns += 2) { /* For each NS record */
                if(view->ns_types[(ns >> 1)] == RR_NS) {
                        view->ns_types[(ns >> 1)] = RRX_GLUELESS_NS;
                        for(ar = 0; ar < view->look->arcount * 2; ar += 2) {
                                if(dwx_dname_issame_dw(in,
                                   look->ns[ns + 1] + 10,
                                   look->ar[ar]) == 1) {
                                        dwx_make_link(view,ns,ar);
                                }
                        }
                }
        }
        return 1;
}

/* Given a dw_str object with one or more DNS packets, take
 * a single DNS packet and extract a single DNS packet starting
 * at the specified offset, returning a newly created dw_str object
 * with the single DNS packet (Or 0/NULL on any error)
 */

dw_str *dwx_get_1_dns_rr(dw_str *in, int32_t offset) {
        int32_t end = 0, rdlength = 0;
        dw_str *out = 0;

        if(in == 0 || offset < 0 || offset > in->len) {
                goto catch_dwx_get_1_dns_rr;
        }

        end = dw_get_dn_end(in, offset); /* End of name for packet */
        if(end == -1 || end < offset) {
                goto catch_dwx_get_1_dns_rr;
        }
        if(dw_fetch_u16(in,end + 2) != 1) { /* CLASS must be 1 */
                goto catch_dwx_get_1_dns_rr;
        }
        rdlength = dw_fetch_u16(in, end + 8);
        if(rdlength < 0) {
                goto catch_dwx_get_1_dns_rr;
        }

        end += 10 + rdlength; /* End of packet */
        if(end <= offset) {
                goto catch_dwx_get_1_dns_rr;
        }

        out = dw_substr(in,offset,end - offset,1);
        if(out == 0) {
                goto catch_dwx_get_1_dns_rr;
        }
        return out;

catch_dwx_get_1_dns_rr:
        if(out != 0) {
                dw_destroy(out);
        }
        return 0;
}

/* Grab a single DNS name from a string.
 *
 * Input: String (dw_str) and offset where the DNS name begins in the
 *        string.
 *
 * Output: Newly created dw_str object with the DNS name in question
 */

dw_str *dwx_get_dns_string(dw_str *in, int32_t offset) {
        int32_t end = 0;

        if(in == 0 || offset < 0 || offset > in->len) {
                return 0;
        }

        end = dw_get_dn_end(in,offset);
        if(end < offset) {
                return 0;
        }

        return dw_substr(in,offset,end-offset,1);
}

/* Given a fully processed dns_details object which is for an incomplete
 * CNAME chain referral, create a special "incomplete CNAME chain" string
 * to store in the cache
 *
 * A CNAME referral is stored as a list of DNAME records in the
 * following format:
 *
 * {length}{DNAME}
 *
 * {length} is an unsigned 16-bit integer. {DNAME} is a raw DNS name
 * (samiam.org will be \x06samiam\x03org\x00). After this list, we
 * have the following three bytes
 *
 * {final offset}\0x11
 *
 * The final offset is an unsigned 16-bit integer with a pointer to
 * the beginning of the final DNAME entry.
 */

dw_str *dwx_make_cname_refer(dns_details *view) {
        int32_t offset = 0, final_offset = 0;
        dw_str *out = 0, *tmp = 0;
        int counter = 0;

        if(view == 0 || view->look == 0 || view->an_types == 0) {
                goto catch_dwx_make_cname_refer;
        }

        out = dw_create(257 * view->look->ancount);
        if(out == 0) {
                goto catch_dwx_make_cname_refer;
        }

        for(counter = 0; counter < view->look->ancount * 2; counter += 2) {
                if(view->an_types[(counter >> 1)] == RRX_IGNORE) {
                        continue;
                }
                if(view->an_types[(counter >> 1)] != RR_CNAME) {
                        goto catch_dwx_make_cname_refer;
                }
                offset = view->look->an[counter + 1];
                offset += 10;
                tmp = dwx_get_dns_string(view->look->packet,offset);
                if(tmp == 0) {
                        goto catch_dwx_make_cname_refer;
                }
                if(tmp->len > 256) {
                        goto catch_dwx_make_cname_refer;
                }
                final_offset = out->len;
                if(dw_push_u16(tmp->len,out) == -1) {
                        goto catch_dwx_make_cname_refer;
                }
                if(dw_append(tmp,out) == -1) {
                        goto catch_dwx_make_cname_refer;
                }
                dw_destroy(tmp);
                tmp = 0;
        }
        if(dw_push_u16(final_offset,out) == -1 ||
           dw_addchar(TYPE_CNAME_REFER,out) == -1) {
                goto catch_dwx_make_cname_refer;
        }
        return out;

catch_dwx_make_cname_refer:
        if(out != 0) {
                dw_destroy(out);
        }
        if(tmp != 0) {
                dw_destroy(tmp);
        }
        return 0;
}

/* Make a single glued NS record to add to the list of NS referrals */
dw_str *dwx_make_nsglue(dw_str *in, int32_t toffset, int type) {
        int otype = 0, len = 0;
        dw_str *out = 0;

        if(in == 0) {
                return 0;
        }

        if(type >= RRX_FIRST_A_GLUE && type <= RRX_LAST_A_GLUE) {
                otype = RR_A;
                len = 4;
        } else if(type >= RRX_FIRST_AAAA_GLUE && type <= RRX_LAST_AAAA_GLUE) {
                otype = RR_AAAA;
                len = 16;
        } else {
                return 0;
        }

        out = dw_create(len + 2);
        if(out == 0) {
                return 0;
        }

        if(dw_addchar(otype,out) == -1) {
                dw_destroy(out);
                return 0;
        }

        if(dw_substr_append(in,toffset,len,out) == -1) {
                dw_destroy(out);
                return 0;
        }

        return out;
}

/* Make a single glueless NS record to add to the list of NS referrals */
dw_str *dwx_make_nsglueless(dw_str *in, int32_t offset) {
        dw_str *out = 0, *tmp = 0;
        int32_t rdlength = 0;

        if(in == 0) {
                return 0;
        }

        rdlength = dw_fetch_u16(in,offset);
        offset += 2;
        tmp = dwx_get_dns_string(in,offset);
        if(tmp == 0) {
                return 0;
        }
        if(tmp->len != rdlength) {
                dw_destroy(tmp);
                return 0;
        }

        out = dw_create(rdlength + 5);
        if(out == 0) {
                dw_destroy(tmp);
                return 0;
        }

        if(dw_addchar(RR_NS,out) == -1 ||
           dw_push_u16(rdlength,out) == -1 ||
           dw_append(tmp,out) == -1) {
                dw_destroy(out);
                dw_destroy(tmp);
                return 0;
        }

        dw_destroy(tmp);
        return out;
}

/* Add all of the glued records in a NS referral */
int dwx_make_ns_refer_glued(dns_details *view, dw_str *out, uint16_t *offset,
                int *onumber) {
        dw_str *tmp = 0;
        int counter = 0;
        int32_t toffset = 0;

        for(counter = 0; counter < view->look->arcount * 2; counter += 2) {
                toffset = view->look->ar[counter + 1];
                toffset += 10;
                if((view->ar_types[(counter >> 1)] >= RRX_FIRST_A_GLUE &&
                    view->ar_types[(counter >> 1)] <= RRX_LAST_A_GLUE) ||
                   (view->ar_types[(counter >> 1)] >= RRX_FIRST_AAAA_GLUE &&
                    view->ar_types[(counter >> 1)] <= RRX_LAST_AAAA_GLUE)) {
                        offset[*onumber] = out->len;
                        (*onumber)++;
                        tmp = dwx_make_nsglue(view->look->packet,toffset,
                                view->ar_types[(counter >> 1)]);
                        if(dw_append(tmp,out) == -1 ||
                           *onumber > (GLUE_RANGE * 2) + 1) {
                                dw_destroy(tmp);
                                return -1;
                        }
                        dw_destroy(tmp);
                        tmp = 0;
                }
        }

        return 1;
}

/* Create a NS referral string with the name this NS referral points to
 * as the first part of the packet.  The name the NS referral points to
 * is the longest in-bailiwick NS referral in the packet we received.
 */

dw_str *dwx_find_ns_referral(dns_details *view, int32_t out_len) {
        int counter = 0, max = 0, max_num = 0;
        int32_t len = 0;
        dw_str *out = 0;

        if(view == 0 || view->look == 0 || view->ns_types == 0 ||
                        view->look->ns == 0) {
                return 0;
        }

        /* Find the longest in-bailiwick NS name (the DNS space the NS
         * record covers) */
        for(counter = 0; counter < view->look->nscount; counter++) {
                if(view->ns_types[counter] != RRX_IGNORE &&
                   view->ns_types[counter] != RRX_OOB_NS) {
                        len = dw_get_dn_end(view->look->packet,
                                view->look->ns[(counter * 2)]) -
                                view->look->ns[(counter * 2)];
                        if(len > max) {
                                max = len;
                                max_num = counter;
                        }
                }
        }

        out = dw_substr(view->look->packet,view->look->ns[max_num * 2],
                        max,out_len + 3);
        return out;
}

/* Once dwx_make_ns_refer() verifies the sanity of "view" and gives us
 * the length of the ns referral string, do the process of making the
 * actual ns referral string */

dw_str *dwx_make_ns_refer_proc(dns_details *view, int32_t out_len) {
        uint16_t offset[(GLUE_RANGE * 2) + 3];
        int counter = 0, onumber = 0;
        int32_t toffset = 0;
        dw_str *out = 0, *tmp = 0;

        out = dwx_find_ns_referral(view,out_len);
        if(out == 0) {
                return 0;
        }

        if(dwx_make_ns_refer_glued(view,out,offset,&onumber) == -1) {
                goto catch_dwx_make_ns_refer_proc;
        }

        /* Now glueless NS referrals */
        for(counter = 0; counter < view->look->nscount * 2; counter += 2) {
                if(view->ns_types[(counter >> 1)] == RRX_GLUELESS_NS) {
                        offset[onumber] = out->len;
                        onumber++;
                        toffset = view->look->ns[counter + 1];
                        toffset += 8;
                        tmp = dwx_make_nsglueless(view->look->packet,toffset);
                        if(dw_append(tmp,out) == -1 ||
                           onumber > (GLUE_RANGE * 2) + 1) {
                                goto catch_dwx_make_ns_refer_proc;
                        }
                        dw_destroy(tmp);
                        tmp = 0;
                }
        }

        for(counter = 0; counter < onumber; counter++) { /* Offsets */
                if(dw_push_u16(offset[counter],out) == -1) {
                        goto catch_dwx_make_ns_refer_proc;
                }
        }
        if(dw_addchar(onumber,out) == -1 || /* Number of NS records */
           dw_addchar(TYPE_NS_REFER,out) == -1) { /* NS referral type */
                goto catch_dwx_make_ns_refer_proc;
        }

        return out;

catch_dwx_make_ns_refer_proc:
        if(out != 0) {
                dw_destroy(out);
        }
        if(tmp != 0) {
                dw_destroy(tmp);
        }
        return 0;
}

/* Given a fully processed dns_details object which is for an incomplete
 * NS referral, create a special "NS referral" string to store in the cache
 *
 * A NS referral starts off with a dlabel describing the DNS space this
 * particular NS record covers.  This is followed by a list of the following:
 *
 * {type (A, AAAA, or name)}{data}
 *
 * Type in an eight-bit number which can be either A (1), AAAA (28),
 * or name (2). The type determines the data; an "A" NS referral is a 4-byte
 * IPv4 IP, an "AAAA" NS referral is a 16-byte IPv6 IP, and a "name" type
 * will be a DNAME with the glueless NS referral. (Stored as {length}{name},
 * with {length} being a 16-bit integer).
 *
 * After all of the NS referrals, we have a list of unsigned 16-bit
 * offsets pointing to the NS referrals in the string, followed by a signed
 * 8-bit number with the number of NS referrals (Deadwood ignores NS records
 * after the first 16 records), followed by the \x10 (16) byte indicating that
 * this record is a NS referral.
 */

dw_str *dwx_make_ns_refer(dns_details *view) {
        int32_t offset = 0, rdlength = 0;
        int counter;
        int32_t out_len = 0;

        if(view == 0 || view->look == 0 || view->ar_types == 0 ||
                        view->ns_types == 0 || view->look->ar == 0 ||
                        view->look->ns == 0) {
                return 0;
        }

        /* Find out how long our string will be */
        /* Glued answers */
        for(counter = 0; counter < view->look->arcount * 2; counter += 2) {
                if(view->ar_types[(counter >> 1)] >= RRX_FIRST_A_GLUE &&
                   view->ar_types[(counter >> 1)] <= RRX_LAST_A_GLUE) {
                        out_len += 8; /* 4: IP; 1: Type; 2: Offset; 1: Pad */
                }
                if(view->ar_types[(counter >> 1)] >= RRX_FIRST_AAAA_GLUE &&
                   view->ar_types[(counter >> 1)] <= RRX_LAST_AAAA_GLUE) {
                        out_len += 20; /* 16: IP; 1: Type; 2: Offset; 1: Pad */
                }
        }
        /* Glueless answers */
        for(counter = 0; counter < view->look->nscount * 2; counter += 2) {
                if(view->ns_types[(counter >> 1)] == RRX_GLUELESS_NS) {
                        offset = view->look->ns[counter + 1];
                        offset += 8;
                        rdlength = dw_fetch_u16(view->look->packet,offset);
                        if(rdlength <= 0) {
                                return 0;
                        }
                        out_len += rdlength + 6;
                }
        }

        return dwx_make_ns_refer_proc(view, out_len);

}

/* Copy over all of the records in a given section of a DNS packet (AN,
 * NS, or AR).  This is used by dwx_remake_complete_reply() to copy
 * over the records in to the string out which are usable (which aren't
 * RRX_IGNORE and RRX_OOB_NS records)
 */

int dwx_copy_over_section(dw_str *out, dw_str *packet, int8_t *types,
                uint16_t *section, int32_t count, uint16_t *offsets,
                int32_t *out_count, int *this_offset, int max_offset) {
        int counter = 0;
        dw_str *tmp = 0;

        if(out == 0 || types == 0 || section == 0 || offsets == 0) {
                goto catch_dwx_copy_over_section;
        }

        for(counter = 0; counter < count; counter++) {
                if(types[counter] == RRX_IGNORE ||
                   types[counter] == RRX_OOB_NS) {
                        continue;
                }
                tmp = dwx_get_1_dns_rr(packet,section[counter * 2]);
                if(tmp == 0 || *this_offset + 1 > max_offset) {
                        goto catch_dwx_copy_over_section;
                }
                offsets[*this_offset] = out->len;
                offsets[(*this_offset) + 1] = out->len +
                        (section[(counter * 2) + 1] - section[counter * 2]);
                (*this_offset) += 2;
                if(dw_append(tmp,out) == -1) {
                        goto catch_dwx_copy_over_section;
                }
                (*out_count)++;
                dw_destroy(tmp);
                tmp = 0;
        }

        return 1;

catch_dwx_copy_over_section:
        if(tmp != 0) {
                dw_destroy(tmp);
        }
        return -1;
}

/* Make the footer (offsets, an/ns/ar count, type) to put at the end of
 * a recreated complete DNS reply. */

int dwx_remake_footer(dw_str *out, uint16_t *offsets, uint16_t an,
                uint16_t ns, uint16_t ar, int type) {
        int counter = 0;

        for(counter = 0; counter < (an + ns + ar) * 2;
                        counter++) { /* Add offsets */
                if(dw_push_u16(offsets[counter],out) == -1) {
                        return -1;
                }
        }

        /* Add the ancount, nscount, arcount, and 1-byte type */
        if(dw_push_u16(an,out) == -1 ||
           dw_push_u16(ns,out) == -1 ||
           dw_push_u16(ar,out) == -1 ||
           dw_addchar(type,out) == -1) {
                return -1;
        }

        return 1;
}

/* Given a fully processed dns_details object, if the string is an answer
 * (either positive or negative), make a string in the same format as
 * a decompressed string with only answers we consider "useful" in the
 * string
 *
 * The format is as follows:
 *
 * 1. First, we have a list of each DNS RR in uncompressed form
 *
 * 2. Then we have a list of 16-bit offsets; each DNS packet gets two offsets:
 *
 *      a. The beginning of the DNS label with the name of the RR in
 *         question.
 *
 *      b. The first byte after the end of same label (where the type is
 *         in the DNS RR)
 *
 * 3. Then we have the 16-bit ancount, followed by the nscount, followed
 *    by the arcount
 *
 * 4. We finish it off with a single TYPE byte of either TYPE_ANSWER,
 *    TYPE_NXDOMAIN, or TYPE_NOT_THERE
 *
 */

dw_str *dwx_remake_complete_reply(dns_details *view, int type) {
        uint16_t *offsets = 0;
        int this_offset = 0;
        int32_t total_count = 0, an = 0, ns = 0, ar = 0;
        dw_str *out = 0;

        if(view == 0 || view->look == 0 || view->look->packet == 0) {
                goto catch_dwx_make_complete_reply;
        }

        total_count = (view->look->ancount + view->look->nscount +
                      view->look->arcount) * 2;

        offsets = dw_malloc(((sizeof(uint16_t)) * total_count) + 1);
        if(offsets == 0) {
                goto catch_dwx_make_complete_reply;
        }

        out = dw_create(view->look->packet->len + 1);
        if(dwx_copy_over_section(out,view->look->packet,view->an_types,
                    view->look->an,view->look->ancount,offsets,&an,
                    &this_offset,total_count) == -1 ||
           dwx_copy_over_section(out,view->look->packet,view->ns_types,
                    view->look->ns,view->look->nscount,offsets,&ns,
                    &this_offset,total_count) == -1 ||
           dwx_copy_over_section(out,view->look->packet,view->ar_types,
                    view->look->ar,view->look->arcount,offsets,&ar,
                    &this_offset,total_count) == -1) {
                goto catch_dwx_make_complete_reply;
        }

        if(dwx_remake_footer(out,offsets,an,ns,ar,type) == -1) {
                goto catch_dwx_make_complete_reply;
        }

        free(offsets);
        return out;

catch_dwx_make_complete_reply:
        if(offsets != 0) {
                free(offsets);
        }
        if(out != 0) {
                dw_destroy(out);
        }
        return 0;
}

/* Given a fully processed dns_details object, and the original query they
 * sent us, determine what type of reply it is (direct answer, full CNAME
 * chain referral, incomplete CNAME chain referral, or NS referral) */

int dwx_determine_answer_type(dns_details *view, dw_str *query, dw_str *in) {
        int32_t query_type = 0;
        int counter = 0;
        int max = 32;
        int index = -1;

        if(query == 0 || view == 0 || view->look == 0) {
                return TYPE_ERROR;
        }
        query_type = dw_fetch_u16(query,-1);
        if(query_type == -1) {
                return TYPE_ERROR;
        }

        if(view->look->ancount > 0) { /* Is it an answer */
                if(view->an_types == 0) {
                        return TYPE_ERROR;
                }
                for(index = 0; index < 7; index++ ) {
                        if(view->an_types[index] ==
                                        dwx_rrtype_number(query_type) ||
                                (query_type == RR_ANY &&
                                        view->an_types[index] != RR_CNAME)) {
                                return TYPE_ANSWER;
                        }
                        if(view->an_types[index] == RR_CNAME) { /* CNAMEs */
                                if(view->look->ancount < max) {
                                        max = view->look->ancount;
                                }
                                for(counter = index + 1 ; counter < max ;
                                                counter++) {
                                        if(view->an_types[counter] ==
                                           dwx_rrtype_number(query_type) ||
                                           (query_type == RR_ANY &&
                                           view->an_types[counter] != RR_CNAME)
                                           ) {
                                                return TYPE_ANSWER;
                                        }
                                        if(view->an_types[counter] != RR_CNAME
                                           && view->an_types[counter] !=
                                           RRX_IGNORE) {
                                                return TYPE_NO_USEFUL_DATA;
                                        }
                                }
                        return TYPE_CNAME_REFER;
                        }
                        if(view->an_types[index] != RRX_IGNORE) {
                                return TYPE_NO_USEFUL_DATA;
                        }
                }
        }
        if(view->ns_types == 0) {
                return TYPE_ERROR;
        }
        if(view->ns_types[0] == RR_SOA) {
                return TYPE_NOT_THERE; /* May be NXDOMAIN; look at header */
        }
        if(view->ns_types[0] == RR_NS) {
                /* oncetv-ipn.net fix; also speeds up Deadwood */
                for(index = 0; index < view->look->arcount * 2; index +=2) {
                        if(dwx_dname_issame_2dw(query,0,in,
                                        view->look->ar[index]) == 1 &&
                           dw_fetch_u16(in,view->look->ar[index + 1]) ==
                                        query_type) {
                                view->ar_types[(index >> 1)]=RRX_ANSWER_IN_AR;
                                return TYPE_ANSWER_IN_AR;
                        }
                }
                return TYPE_NS_REFER;
        }
        return TYPE_NO_USEFUL_DATA;
}

/* Given a fully processed dns_details object, create a string that
 * we can store in the cache.
 *
 * Input: The processed dns_details object.
 *
 * Output: A newly-created string, 0 (NULL) on error
 */

dw_str *dwx_make_cache_string(dns_details *view, int type) {
        dw_str *out = 0;
        uint8_t in_type = 0;

        if(view == 0 || view->look == 0 || view->look->packet == 0
           || view->look->packet->len == 0 || view->look->packet->str == 0) {
                return 0;
        }

        in_type = *(view->look->packet->str + view->look->packet->len - 1);

        if(in_type == TYPE_NXDOMAIN && type == TYPE_NOT_THERE) {
                type = TYPE_NXDOMAIN;
        }

        if(in_type != TYPE_ANSWER && in_type != TYPE_NXDOMAIN) {
                return 0;
        }

        /* If the NXDOMAIN bit is set in the header, the answer must look like
         * a NXDOMAIN */
        /* Disabled: bookride.com screws this up, so there are out there in
         * the wild answers marked NXDOMAIN that actually aren't */
        /*if(in_type == TYPE_NXDOMAIN && type != TYPE_NXDOMAIN) {
                return 0;
        }*/

        if(type == TYPE_CNAME_REFER) {
                out = dwx_make_cname_refer(view);
        } else if(type == TYPE_NS_REFER) {
                out = dwx_make_ns_refer(view);
        } else if(type == TYPE_ANSWER || type == TYPE_NXDOMAIN ||
                        type == TYPE_NOT_THERE) {
                out = dwx_remake_complete_reply(view,type);
        } else {
                return 0;
        }

        return out;
}

/* Create a synthetic "not there" reply; this is used for empty replies
 * because a lot of broken DNS servers on the internet will give out a
 * reply with 0 DNS records when they don't have an answer for our query
 */

dw_str *dwx_synth_notthere(dw_str *query) {
        dw_str *hack, *out;
        int l = 20;

        hack = dw_malloc(sizeof(dw_str));
        if(hack == 0) {
                return 0;
        }
        hack->str = (uint8_t *)make_synth_not_there_answer(0,&l,1);
        hack->len = 40;
        hack->sane = 114;
        hack->max = 59;
        dw_put_u16(hack,0,-1);/*ancount*/
        dw_put_u16(hack,1,-1);/*nscount*/
        dw_put_u16(hack,0,-1);/*arcount*/
        dw_put_u8(hack,TYPE_NOT_THERE,-1);/* type */
        out = dwc_decompress(query,hack);
        dw_destroy(hack);

        return out;
}

/* oncetv-ipn.net fix: If the answer is in the AR section, we return
 * a packet where that answer (singular for the simple reason coding for
 * multiple is more difficult and no one is paying me to write this code)
 * is moved to the AN section */
dw_str *dwx_answer_in_ar(dns_details *view, dw_str *in) {
        dw_str *tmp = 0, *out = 0;
        int32_t dname_len = 0;
        int index = 0;

        if(view == 0 || in == 0) {
                return 0; /* Sanity test */
        }

        for(index = 0; index < view->look->arcount; index++) {
                if(view->ar_types[index] == RRX_ANSWER_IN_AR && tmp == 0) {
                        tmp = dwx_get_1_dns_rr(in,view->look->ar[index * 2]);
                        dname_len = view->look->ar[(index * 2) + 1] -
                                        view->look->ar[index * 2];
                }
        }

        if(tmp == 0) {
                return 0;
        }
        out = dw_create(tmp->len + 14);
        if(out == 0) {
                dw_destroy(tmp);
                return 0;
        }
        if(dw_append(tmp,out) == -1 ||
           dw_push_u16(0,out) == -1 || /* Offset 1: Top of string */
           dw_push_u16(dname_len,out) == -1 || /* Offset 2: End of dname */
           dw_push_u16(1,out) == -1 || /* One answer */
           dw_push_u16(0,out) == -1 || /* No NS records */
           dw_push_u16(0,out) == -1 || /* No AR records (moved to AN) */
           dw_addchar(TYPE_ANSWER,out) == -1) {
                dw_destroy(tmp);
                dw_destroy(out);
                return 0;
        }

        dw_destroy(tmp);
        return out;
}

/* Look at a DNS reply and determine how to proceed
 *
 * Input: The reply we got ("in"), and the query we sent ("query")
 *
 * Output: A dw_str object telling us how to proceed.
 */

dw_str *dwx_dissect_packet(dw_str *in, dw_str *query, dw_str *bailiwick) {
        dns_details *view = 0;
        dw_str *out = 0;
        int type = 0;
        int_fast32_t qtype = 0;

        if(in == 0 || query == 0 || bailiwick == 0) {
                return 0;
        }

        qtype = dw_fetch_u16(query,-1);

        if(qtype == -1) {
                return 0;
        }

        if(in->len == 7 && qtype == RR_AAAA) { /* Blank packet */
                return dwx_synth_notthere(query);
        }

        view = dwx_create_dns_details(in,query);

        if(view == 0 ||
           dwx_check_answer_section(in,query,view) == -1 ||
           dwx_if_an_then_no_ns_nor_ar(view) == -1 ||
           dwx_cleanup_ns_ar(view) == -1) {
                goto catch_dwx_dissect_packet;
        }
        type = dwx_determine_answer_type(view,query,in);
        if(type == TYPE_ANSWER_IN_AR) {
                out = dwx_answer_in_ar(view, in);
                if(view != 0) {
                        dwx_zap_dns_details(view);
                }
                return out;
        }
        if(type == TYPE_NS_REFER && (
           dwx_check_bailiwick_ns_section(view,query,bailiwick) == -1 ||
           dwx_link_ns_records(view) == -1 )) {
                goto catch_dwx_dissect_packet;
        }

        out = dwx_make_cache_string(view,type);
        dwx_zap_dns_details(view);
        return out;

catch_dwx_dissect_packet:
        if(view != 0) {
                dwx_zap_dns_details(view);
        }
        if(out != 0) {
                dw_destroy(out);
        }
        return 0;
}

/* When handling a NS referral, make the actual connection to the
 * upstream server */
void dwx_handle_ns_refer_connect(int connection_number, dw_str *packet,
                dw_str *query) {

        rem[connection_number].die = get_time() +
                ((int64_t)timeout_seconds << 8);

        /* Send said query upstream */
        if(rem[connection_number].socket != INVALID_SOCKET) {
                /* make_remote_connection() needs to have the socket be an
                 * invalid socket.
                 */
                closesocket(rem[connection_number].socket);
                rem[connection_number].socket = INVALID_SOCKET;
                b_remote[connection_number] = INVALID_SOCKET;
        }
        make_remote_connection(connection_number,(unsigned char *)packet->str,
                packet->len,query,rem[connection_number].socket);

}

/* Handle the case when upstream gives us a NS referral
 * Input: connection_number we are handling, NS referral we received
 * Output: void() (rem[connection_number] changed; as well as the
 *      cache)
 */
void dwx_handle_ns_refer(int connection_number, dw_str *action,
                dw_str *query, int32_t ttl) {
        dw_str *place = 0, *packet = 0;

        if(rem[connection_number].ns == 0 || action == 0
                        || rem[connection_number].is_upstream == 1) {
                goto catch_dwx_handle_ns_refer;
        }

        if(dw_fetch_u8(action,-1) != TYPE_NS_REFER) {
                goto catch_dwx_handle_ns_refer;
        }

        /* Replace NS referral for this query with the NS referral we got */
        dw_destroy(rem[connection_number].ns);
        rem[connection_number].ns = 0;
        rem[connection_number].ns = dw_copy(action);

        /* Add this NS referral to the cache */
        place = dw_get_dname(action->str, 0, 260);
        if(place == 0) {
                goto catch_dwx_handle_ns_refer;
        }
        if(ttl < 3600) { /* We store NS referrals in the cache for at least
                          * an hour for security reasons */
                ttl = 3600;
        }
        if(ttl > max_ttl) {
                ttl = max_ttl;
        }
        dw_put_u16(place, 65395, -1); /* Add "NS refer" private RR type */
        dwh_add(cache,place,action,ttl,1);

        /* Create a DNS query packet to send upstream */
        packet = make_dns_header(rem[connection_number].remote_id,
                0x0000,0,0,0);
        if(dw_append(query,packet) == -1 || dw_put_u16(packet,1,-1) == -1) {
                goto catch_dwx_handle_ns_refer;
        }

        dwx_handle_ns_refer_connect(connection_number, packet, query);

catch_dwx_handle_ns_refer:
        if(place != 0) {
                dw_destroy(place);
        }
        if(packet != 0) {
                dw_destroy(packet);
        }
        return;
}

/* Create a single CNAME RR (Name, type (CNAME), class, TTL, RDLENGTH,
 * RDDATA of cname pointer).
 * Input:
 * 1) String with question name
 * 2) Offset in question string
 * 3) String with answer
 * 4) Offset in answer string
 * 5) TTL of CNAME answer
 * 6) Maximum size of output string
 *
 * Output:
 * Newly create dw_str with full CNAME answer
 */
dw_str *dwx_make_one_cname_rr(dw_str *question, int32_t question_offset,
                dw_str *answer, int32_t answer_offset, int32_t ttl, int *len,
                int size) {
        dw_str *out = 0, *temp = 0;

        if(question == 0 || question_offset < 0 || answer == 0 ||
                        answer_offset < 0 || len == 0) {
                goto catch_dwx_make_one_cname_rr;
        }

        temp = dw_get_dname(question->str, question_offset, size);
        if(temp == 0) {
                goto catch_dwx_make_one_cname_rr;
        }
        out = dw_copy(temp);
        if(out == 0) {
                goto catch_dwx_make_one_cname_rr;
        }
        *len = out->len; /* Length of first dlabel */
        if(dw_put_u16(out,RR_CNAME,-1) == -1 ||
                        dw_put_u16(out,1,-1) == -1 /* CLASS */ ||
                        dw_put_u16(out,(ttl >> 16) & 0xffff, -1) == -1 ||
                        dw_put_u16(out,(ttl & 0xffff), -1) == -1) {
                goto catch_dwx_make_one_cname_rr;
        }
        dw_destroy(temp);
        temp = dw_get_dname(answer->str, answer_offset, 260);
        if(temp == 0 || dw_put_u16(out, temp->len, -1) == -1 ||
                        dw_append(temp,out) == -1) {
                goto catch_dwx_make_one_cname_rr;
        }
        dw_destroy(temp);
        temp = 0;

        return out;

catch_dwx_make_one_cname_rr:
        if(out != 0) {
                dw_destroy(out);
        }
        if(temp != 0) {
                dw_destroy(temp);
        }
        return 0;
}

/* Create the final answer, as well as the footer of the reply to give the
 * user */
void dwx_add_final_answer(dw_str *query, dw_str *answer, dw_str *value,
                uint16_t *offsets, int this_offset) {
        int32_t start = 0, end = 0;
        dw_str *temp = 0;
        int first_offset = 0, place = 0, rdlength = 0;
        int last_place = 0;

        if(value == 0) {
                return;
        }
        /* Extract DNS packets with answers to our query;
         * append said answers to value string */

        /* Set "start" and "end" to be the start and end of the answer */
        dwc_rr_rotate(answer, &start, &end);

        if(start < 0 || end < 0) {
                return;
        }

        temp = dw_substr(answer, start, end - start, 1);
        if(temp == 0) {
                return;
        }

        first_offset = value->len;
        while(this_offset < 15) {

                last_place = place;

                offsets[this_offset * 2] = place + first_offset;
                place = dw_get_dn_end(temp, place);
                if(place < 0) {
                        dw_destroy(temp);
                        return;
                }
                offsets[(this_offset * 2) + 1] = place + first_offset;
                place += 8; /* type, class, TTL (4) */
                rdlength = dw_fetch_u16(temp, place);
                if(rdlength < 0) {
                        dw_destroy(temp);
                        return;
                }
                place += rdlength + 2;

                /* Make sure string will fit in "value" */
                if(place + first_offset > value->max - 8 - (this_offset * 4)) {
                        if(last_place < temp->len) {
                                temp->len = last_place;
                                place = last_place;
                        }
                        break;
                }

                this_offset++;
                if(place >= temp->len) {
                        break;
                }
        }

        /* RRs we didn't account for above are discarded */
        if(place < temp->len) {
                temp->len = place;
        }
        /* Make sure string will fit in "value" */
        if(place + first_offset > value->max - 8 - (this_offset * 4)) {
                if(last_place < temp->len) {
                        temp->len = last_place;
                        this_offset--;
                }
        }

        dw_append(temp,value);
        dw_destroy(temp);

        /* Put offsets and an/ns/ar count at end of string */
        for(rdlength=0;rdlength<this_offset*2;rdlength++) {
                dw_put_u16(value, offsets[rdlength], -1);
        }
        dw_put_u16(value,this_offset,-1);/*ancount*/
        dw_put_u16(value,0,-1);/*nscount*/
        dw_put_u16(value,0,-1);/*arcount*/
        dw_put_u8(value,TYPE_ANSWER,-1);/* Answer to question */

}

/* If the answer at the end of a CNAME chain is not a full answer, handle it
 * thusly; this is used so we can give the correct reply when a CNAME points
 * to a SOA "not there" reply. */
dw_str *dwx_alt_cname_reply(dw_str *query, dw_str *action, dw_str *answer,
                int32_t ttl) {
        int len = 0, place = 0;
        dw_str *value = 0;

        /* We will just give the stub resolver our first CNAME link */
        value = dwx_make_one_cname_rr(query, 0, action, 2, ttl, &len, 260);
        place = dw_get_dn_end(value, 0);
        dw_put_u16(value,0,-1); /* First offset */
        dw_put_u16(value,place,-1); /* Second offset */
        dw_put_u16(value,1,-1); /* ANcount */
        dw_put_u16(value,0,-1); /* NScount */
        dw_put_u16(value,0,-1); /* ARcount */
        dw_put_u8(value,0,-1); /* Type */
        return value;
}

/* Create the actual reply to give the user */
dw_str *dwx_create_cname_reply(dw_str *query, dw_str *action, dw_str *answer,
                int32_t ttl) {
        dw_str *value = 0, *temp = 0;
        int32_t length = 0, final_offset = 0, current_offset = 0;
        int this_offset = 0, len = 0;
        uint16_t offsets[32];

        final_offset = dw_fetch_u16(action, -2);
        if(query == 0 || action == 0 || final_offset == -1 ||
                        dw_fetch_u8(action, -1) != TYPE_CNAME_REFER) {
                goto catch_dwx_create_cname_reply;
        }
        if(dw_fetch_u8(answer,-1) != TYPE_ANSWER) {
                return dwx_alt_cname_reply(query, action, answer, ttl);
        }

        /* First CNAME pointer... */
        value = dwx_make_one_cname_rr(query, 0, action, 2, ttl, &len, 1024);
        if(value == 0 || value->len > 1020) {
                dw_log_string("dwx_make_one_cname_rr problem",100);
                goto catch_dwx_create_cname_reply;
        }
        offsets[0] = 0;
        offsets[1] = len;
        this_offset = 1;

        /* Subsequent CNAME pointers... */
        while(current_offset < final_offset && this_offset < 12) {
                length = dw_fetch_u16(action, current_offset);
                if(length < 1) {
                        goto catch_dwx_create_cname_reply;
                }
                temp = dwx_make_one_cname_rr(action, current_offset + 2,
                                action, current_offset + length + 4, ttl,
                                &len, 260);
                if(temp == 0) {
                        goto catch_dwx_create_cname_reply;
                }
                offsets[this_offset * 2] = value->len;
                offsets[(this_offset * 2) + 1] = value->len + len;
                if(dw_append(temp, value) == -1 || value->len > 1020) {
                        dw_log_string("Append error making cname reply",100);
                        goto catch_dwx_create_cname_reply;
                }
                dw_destroy(temp);
                current_offset += length + 2;
                this_offset++;
        }

        /* Final answer after CNAME pointers... */
        dwx_add_final_answer(query, answer, value, offsets, this_offset);

        if(value->len > 1020) {
                dw_log_string("Final append error making cname reply",100);
                goto catch_dwx_create_cname_reply;
        }

        /* Make that a full answer to give the calling function */
        return value;

catch_dwx_create_cname_reply:
        if(value != 0) {
                dw_destroy(value);
        }
        if(temp != 0) {
                dw_destroy(temp);
        }
        return 0;
}

/* Gluless CNAME finished; send upstream.
 *
 * This is called from dwx_make_cname_reply() and is a private method for
 * that calling function.
 *
 * Input:
 *
 * conn_num: The connection number for the connection which just finished
 *      a glueless cname referral upstream
 *
 * c: The particular "local" connection number pointing to the upstream
 *      cname referral which needs a glueless CNAME finished
 *
 * depth: The recursion depth for dwx_make_cname_reply(); we have this to
 *      stop infinite loops
 *
 * uncomp: A pointer to a string with the uncompressed reply; the format
 *      for this reply is:
 *
 *              * raw DNS packets with answers combined together
 *
 *              * offsets for beginning of DNS packets; each DNS packet
 *                has two offsets (the beginning of the name followed by
 *                the end of the name/beginning of the type).  These are
 *                big-endian 16-bit numbers
 *
 *              * ancount, nscount, arcount.  Again big-endian 16-bit numbers
 *
 *              * a type of "0" (TYPE_ANSWER) to indicate this is an answer
 *                (as opposed to a NS referral or incomplete CNAME reply,
 *                 which have their own format)
 *
 * Variables used:
 *
 * * cname_cache: This is a pointer to a record in the cache with the
 *      CNAME referral which the upstream query got when trying to solve
 *      the query in question, such as binary data stating
 *      "The CNAME pointer for www.example.com".  The query type is 65394,
 *      which is a Deadwood internal query (we reserve queries 65392-65407
 *      for Deadwood's internal use, rejecting outside queries sent with
 *      these RRs) for a CNAME pointer (for security reasons, I'm not using
 *      the CNAME RR for this purpose)
 *
 * * child_action: This is the element we got from the cache which tells us
 *      the action done by the parent query which we finally solved.  It is
 *      a string with binary data which tells us something like
 *      "www.example.com is a CNAME for akamai.example.net" (the format of
 *      the data is generated by dwx_make_cname_refer() and the binary format
 *      is described there)
 *
 * * upstream: The connection number for the parent query which needed to
 *      solve an incomplete multi-link CNAME reply
 *
 */
void dwx_send_glueless_cname_upstream(int conn_num, int c, int depth,
                dw_str *uncomp) {
        dw_str *cname_cache = 0, *child_action = 0;
        int upstream = -1;

        upstream = rem[conn_num].local[c]->glueless_conn;
        cname_cache = dw_copy(rem[upstream].query);
        if(cname_cache == 0 || cname_cache->len < 3 ||
                dw_put_u16(cname_cache, 65394, -3) == -1) {
                        goto catch_dwx_send_glueless_cname_upstream;
        }
        child_action = dwh_get(cache, cname_cache, 0, 1);
        dwx_make_cname_reply(upstream, rem[upstream].query,
                        child_action, uncomp, depth + 1);

catch_dwx_send_glueless_cname_upstream:
        if(cname_cache != 0) {
                dw_destroy(cname_cache);
        }
        if(child_action != 0) {
                dw_destroy(child_action);
        }
}

/* This is used for completing incomplete CNAME referrals.
 * What this does is create a reply to give to the user, then
 * send that reply out.
 */
int dwx_make_cname_reply(int conn_num, dw_str *query,
                dw_str *action, dw_str *answer, int depth) {
        dw_str *uncomp = 0, *reply = 0, *comp = 0;
        int ret = -1, c = 0; /* c is for counter */
        int_fast32_t ttl = 3600;
        int ancount = 0;

        if(depth > 32) {
                 goto catch_dwx_make_cname_reply;
        }

        if(query == 0 || action == 0 || answer == 0) {
                goto catch_dwx_make_cname_reply;
        }

        ancount = dw_fetch_u16(answer,-6);
        if(ancount < 1) {
                goto catch_dwx_make_cname_reply;
        }
        ttl = dw_get_a_dnsttl(answer,0,31536000,ancount);
        if(ttl < 0) {
                dw_log_dwstr("Invalid TTL in answer ",answer,100);
                ttl = 3600;
        }
        if(ttl < 60) {
                ttl = 60;
        }
        if(ttl > max_ttl) {
                ttl = max_ttl;
        }
        /*ttl = 30; // DEBUG*/
        uncomp = dwx_create_cname_reply(query, action, answer, ttl);
        comp = dwc_compress(query, uncomp);

        /* Send a reply out for each "live" local connection */
        if(uncomp == 0 || comp == 0 || rem[conn_num].local == 0 ||
           rem[conn_num].num_locals < 1) {
                goto catch_dwx_make_cname_reply;
        }

        /* Put answer in cache as full answer */
        dwh_add(cache, query, uncomp, ttl, 1);

        /* Send answer from cache */
        for(c = 0; c < rem[conn_num].num_locals; c++) {
                if(rem[conn_num].local[c] == 0) {
                        goto catch_dwx_make_cname_reply;
                }
                if(rem[conn_num].local[c]->glueless_type == 2) {
                        dwx_send_glueless_cname_upstream(conn_num, c, depth,
                                        uncomp);
                        continue;
                }
                if(rem[conn_num].local[c]->glueless_type == 1) {
                        /* To do: Glueless NS */
                        continue;
                }
                if(rem[conn_num].local[c]->glueless_type == 3) {
                        dwx_cached_cname_done(query,conn_num,c,depth);
                        continue;
                }
                reply = make_dns_packet(query, comp,
                                rem[conn_num].local[c]->local_id);
                if(reply == 0) {
                        goto catch_dwx_make_cname_reply;
                }
                send_reply_from_cache(reply->str, reply->len, conn_num, c);
                dw_destroy(reply);
        }

        reset_rem(conn_num); /* Close finished query */
        closesocket(b_remote[conn_num]);
        b_remote[conn_num] = INVALID_SOCKET; /* Make remote available */
        reply = 0;

        /* Clean up */
catch_dwx_make_cname_reply:
        if(reply != 0) {
                dw_destroy(reply);
        }
        if(uncomp != 0) {
                dw_destroy(uncomp);
        }
        if(comp != 0) {
                dw_destroy(comp);
        }
        return ret;
}

/* Handle the case for when we get an incomplete CNAME referral */
int dwx_handle_cname_refer(int connection_number, dw_str *action,
                dw_str *query, int32_t ttl) {
        int32_t qtype = 0, offset = 0;
        dw_str *real_query = 0, *cname_cache = 0, *answer = 0;
        int ret = -1;

        /* Cache this CNAME referral */
        if(ttl < key_n[DWM_N_min_ttl_incomplete_cname]) {
                ttl = key_n[DWM_N_min_ttl_incomplete_cname];
        }
        if(ttl < 30) {
                ttl = 30;
        }
        if(ttl > max_ttl) {
                ttl = max_ttl;
        }
        cname_cache=dw_copy(query);
        if(cname_cache == 0 || cname_cache->len < 3 ||
                        dw_put_u16(cname_cache, 65394, -3) == -1) {
                goto catch_dwx_handle_cname_refer;
        }
        dwh_add(cache, cname_cache, action, ttl, 1);

        /* Determine what query the CNAME points to */
        if(action == 0 || query == 0 ||
                        dw_fetch_u8(action,-1) != TYPE_CNAME_REFER) {
                goto catch_dwx_handle_cname_refer;
        }
        offset = dw_fetch_u16(action, -2);
        offset += 2; /* Go past two-byte length */
        real_query = dw_get_dname(action->str, offset, 260);
        dwc_lower_case(real_query);
        if(real_query == 0) {
                goto catch_dwx_handle_cname_refer;
        }
        qtype = dw_fetch_u16(query,-1);
        if(qtype == -1 || dw_put_u16(real_query, qtype, -1) == -1) {
                goto catch_dwx_handle_cname_refer;
        }

        /* See if we have the data already in the cache */
        answer = dwh_get(cache,real_query,0,1);
        if(answer != 0) { /* In cache */
                ret = dwx_make_cname_reply(connection_number, query,
                                action, answer,0);
                goto catch_dwx_handle_cname_refer;
        } else { /* Not in cache */
                ret = dwx_do_cname_glueless(real_query, connection_number);
        }

catch_dwx_handle_cname_refer:
        if(real_query != 0) {
                dw_destroy(real_query);
        }
        if(answer != 0) {
                dw_destroy(answer);
        }
        if(cname_cache != 0) {
                dw_destroy(cname_cache);
        }
        return ret;
}

/* Called from cache_dns_reply this determines whether the answer is
 * complete or not; if it's complete, we put it in the cache as-is; if
 * not, we put in the cache an incomplete DNS reply (NS referral),
 * and continue processing
 *
 * Input: Pointer to the cache, the query used, the reply upstream, the
 *      desired TTL for this reply
 * Output: The "type" of reply we got upstream, as follows:
 *      -1: Error
 *      Type  0: Positive answer
 *      Type  1: NXDOMAIN negative reply
 *      Type  2: Non-NXDOMAIN negative reply
 *      Type 16: NS referral
 *      Type 17: CNAME referral
 */
int dwx_cache_reply(dw_hash *cache, dw_str *query, dw_str *in, int32_t ttl,
                int connection_number) {
        dw_str *action = 0;
        dw_str *bailiwick = 0;
        int type;
        int ret = 0;

        if(query == 0 || in == 0) {
                ret = -1;
                goto catch_dwx_cache_reply;
        }

        if(rem[connection_number].ns == 0) {
                ret = -1;
                goto catch_dwx_cache_reply;
        }

        if(ttl < 30) {
                ttl = 30;
        }
        if(ttl > max_ttl) {
                ttl = max_ttl;
        }

        bailiwick = dw_get_dname(rem[connection_number].ns->str, 0, 260);
        if(bailiwick == 0 || bailiwick->len > 256) {
                ret = -1;
                goto catch_dwx_cache_reply;
        }

        action = dwx_dissect_packet(in,query,bailiwick);
        if(action == 0) {
                ret = -1;
                goto catch_dwx_cache_reply;
        }
        type = dw_fetch_u8(action,-1);

        if(type == TYPE_ANSWER || type == TYPE_NXDOMAIN ||
                        type == TYPE_NOT_THERE) {
                dw_log_dwstr("Caching direct answer at ",query,100);
                dwh_add(cache,query,action,ttl,1);
                ret = 1;
                goto catch_dwx_cache_reply;
        }

        if(type == TYPE_NS_REFER && rem[connection_number].is_upstream == 0) {
                ret = TYPE_NS_REFER;
                dw_log_dwstr("Processing NS refer for ",query,100);
                dwx_handle_ns_refer(connection_number,action,query,ttl);
                goto catch_dwx_cache_reply;
        }

        if(type == TYPE_CNAME_REFER &&
                        rem[connection_number].is_upstream == 0) {
                ret = type;
                dw_log_dwstr("Processing incomplete CNAME for ",query,100);
                dwx_handle_cname_refer(connection_number,action,query,ttl);
                goto catch_dwx_cache_reply;
        }

        /* Anything else is an error */
        ret = -1;

catch_dwx_cache_reply:
        if(bailiwick != 0) {
                dw_destroy(bailiwick);
        }
        if(action != 0) {
                dw_destroy(action);
                action = 0;
        }
        return ret;
}

/* Convert a single IP from human-readable ("8.8.4.4",
 * "fd4d:6172:6144:4e53::3") to raw binary format, returning the length of
 * the resulting IP
 */
int dwx_ns_convert_1ip(char *ip_human, uint8_t *ip_raw) {
        int ip_type = 0;

        if(inet_pton(AF_INET, ip_human, (uint8_t *)(ip_raw)) > 0) {
                ip_type = 4;
#ifdef IPV6
        } else if(inet_pton(AF_INET6, ip_human, (uint8_t *)(ip_raw)) > 0) {
                ip_type = 16;
#endif /* IPV6 */
        }

        return ip_type;
}

/* Convert a length of a given IP (ip_type) in to a number in a NS
 * referral; 4 -> 1; 16 -> 28; otherwise error */
int dwx_ns_convert_number(int in) {
        if(in == 4) {
                return 1;
        } else if(in == 16) {
                return 28;
        }
        return -1;
}

/* Add a single record (IP) to our converted NS */
int dwx_ns_add_1record(dw_str *out, int ip_type, uint8_t *ip_raw) {
        int num = 0, counter = 0;

        num = dwx_ns_convert_number(ip_type);
        if(num == -1 || dw_put_u8(out,num,-1) == -1) {
                return -1;
        }
        for(counter = 0; counter < ip_type; counter++) {
                if(dw_put_u8(out,ip_raw[counter],-1) == -1) {
                        return -1;
                }
        }
        return 1;
}

/* Create a new NS referral with the bailiwick at the top of the string */
dw_str *dwx_ns_convert_init(dw_str *bailiwick) {
        dw_str *out = 0, *dname = 0;

        if(bailiwick == 0) {
                out = dw_create(256);
                if(out == 0) {
                        return 0;
                }
                if(dw_put_u8(out,0,-1) == -1) /* One byte "dot" RR */ {
                        dw_destroy(out);
                        return 0;
                }
        } else {
                dname = dw_get_dname(bailiwick->str, 0, 260);
                if(dname == 0) {
                        return 0;
                }
                if(dname->len > 256) {
                        dw_destroy(dname);
                        return 0;
                }
                out = dw_substr(dname,0,-1,256);
                dw_destroy(dname);
                if(out == 0) {
                        return 0;
                }
        }

        return out;
}

/* Remove trailing whitespace from a null-terminated string */
void dwx_zap_whitespace(char *in) {
        int counter = 0;

        if(in == 0) {
                return;
        }

        for(counter = 0; counter < 512; counter++) {
                if(*in == 0) {
                        return;
                }
                if(*in == ' ' || *in == '\t') {
                        *in = 0;
                        return;
                }
                in++;
        }
}

/* This is the engine for dwx_ns_convert; the "mangle" string is, well,
 * mangled */
dw_str *dwx_ns_convert_mangle(dw_str *mangle, int is_upstream, dw_str *b) {
        int counter = 0, ip_type = 0, a = 0;
        char *ip_human = 0;
        uint8_t ip_raw[24];
        uint16_t offsets[32];
        dw_str *out = 0;

        /* Initialize output string */
        out = dwx_ns_convert_init(b);
        if(out == 0) {
                goto clean_dwx_ns_convert_mangle;
        }

        /* Add IPs to output string */
        for(counter = 0; counter < 30; counter++) {
                ip_human = pop_last_item(mangle);
                dwx_zap_whitespace(ip_human);
                ip_type = dwx_ns_convert_1ip(ip_human,ip_raw);
                free(ip_human);
                if(ip_type == 0) {
                        break;
                }
                offsets[counter] = out->len;
                if(dwx_ns_add_1record(out, ip_type, ip_raw) == -1) {
                        goto clean_dwx_ns_convert_mangle;
                }
        }

        /* List of offsets */
        for(a = 0; a < counter && a < 31; a++) {
                if(dw_put_u16(out, offsets[a], -1) == -1) {
                        goto clean_dwx_ns_convert_mangle;
                }
        }

        /* Footer */
        if(dw_put_u8(out,counter,-1) == -1) { /* Number of offsets */
                goto clean_dwx_ns_convert_mangle;
        }
        if(is_upstream == 1 && dw_put_u8(out,TYPE_UPSTREAM_REFER,-1) == -1) {
                goto clean_dwx_ns_convert_mangle;
        } else if(is_upstream != 1 && dw_put_u8(out,TYPE_NS_REFER,-1) == -1) {
                goto clean_dwx_ns_convert_mangle;
        }

        return out;

clean_dwx_ns_convert_mangle:
        if(out != 0) {
                dw_destroy(out);
        }
        return 0;
}

/* Given a line in the dwood3rc file with IPs, convert the line in to a NS
 * referral in the same format as created by dwx_make_ns_refer (see
 * that function for full description of format).
 *
 * Input: List of IPs as it exists in the mararc (ok dwood3rc) file;
 *        Whether this is a root (0) or upstream (1) NS record (whether
 *        to make the file byte TYPE_NS_REFER or TYPE_UPSTREAM_REFER)
 *
 * Output: The NS referral as described in the comments for
 *         dwx_make_ns_refer
 */
dw_str *dwx_ns_convert(dw_str *in, int is_upstream, dw_str *bailiwick) {
        /* This function is a wrapper for dwx_ns_convert_mangle; we create
         * a temporary "mangle" string because dwx_ns_convert_mangle modifies
         * the "mangle" string */
        dw_str *mangle = 0, *out = 0;

        mangle = dw_copy(in);
        if(mangle == 0) {
                goto clean_dwx_ns_convert;
        }

        out = dwx_ns_convert_mangle(mangle, is_upstream, bailiwick);
        if(out == 0) {
                goto clean_dwx_ns_convert;
        }

        if(mangle != 0) {
                dw_destroy(mangle);
        }
        return out;

clean_dwx_ns_convert:
        if(mangle != 0) {
                dw_destroy(mangle);
        }
        if(out != 0) {
                dw_destroy(out);
        }
        return 0;
}

/* Given a NS referral string and an offset, get the IPv4 address from that
 * string and return it
 */
ip_addr_T dwx_ns_getip_ipv4(dw_str *list, int offset) {
        ip_addr_T addr = {0, {0,0}, 0, 0};
        int counter = 0, temp = 0;

        if(list == 0 || dw_fetch_u8(list,offset) != RR_A) { /* Sanity check */
                return addr; /* Error */
        }

        for(counter = 0; counter < 4; counter++) {
                temp = dw_fetch_u8(list,offset + 1 + counter);
                if(temp < 0) {
                        return addr; /* Error */
                }
                addr.ip[counter] = temp;
        }

        addr.len = 4; /* IPv4 */

        return addr;
}

#ifdef IPV6
/* Given a NS referral string and an offset, get the IPv6 address from that
 * string and return it
 */
ip_addr_T dwx_ns_getip_ipv6(dw_str *list, int offset) {
        ip_addr_T addr = {0, {0,0}, 0, 0};
        int counter = 0, temp = 0;

        if(list == 0 || dw_fetch_u8(list,offset) != RR_AAAA) { /* Sanity */
                return addr; /* Error */
        }

        for(counter = 0; counter < 16; counter++) {
                temp = dw_fetch_u8(list,offset + 1 + counter);
                if(temp < 0) {
                        return addr; /* Error */
                }
                addr.ip[counter] = temp;
        }

        addr.len = 16; /* IPv6 */

        return addr;
}
#endif /* IPV6 */

/* Given an answer as it looks in the cache, choose a random A (or AAAA if
 * IPv6 support is compiled in) record from that answer and return it as
 * an IP; note that "answer" is mangled.
 */

ip_addr_T dwx_get_rr_from_answer(dw_str *answer) {
        ip_addr_T addr = {0, {0, 0}, 0, 0};
        int counter = 0, ancount = 0, stack[20], type = 0, place = 0, z = 0;

        if(answer == 0) {
                goto catch_dwx_get_rr_from_answer;
        }

        if(dw_fetch_u8(answer,-1) != TYPE_ANSWER) {
                goto catch_dwx_get_rr_from_answer;
        }

        answer->len--;

        if(dw_pop_u16(answer) == -1 || dw_pop_u16(answer) == -1) {
                goto catch_dwx_get_rr_from_answer;
        }
        ancount = dw_pop_u16(answer);

        for(counter = 0; counter < ancount && counter < 16; counter++) {
                z = dw_pop_u16(answer);
                if(z == -1 || dw_pop_u16(answer) == -1) {
                        goto catch_dwx_get_rr_from_answer;
                }
                /* See if it's an A/AAAA; if it is, add the
                 * offset to a stack and finally choose a random offset
                 * from the stack */
                type = dw_fetch_u16(answer,z);
                if(type == RR_A
#ifdef IPV6
                        || type == RR_AAAA
#endif /* IPV6 */
                ) {
                        stack[place] = z;
                        place++;
                }
        }

        if(place == 0) {
                goto catch_dwx_get_rr_from_answer;
        }
        place = dwr_rng(rng_seed) % place;
        type = dw_fetch_u16(answer,stack[place]);
        if(type == RR_A) {
                addr.len = 4;
                addr.ip[0] = *(answer->str + stack[place] + 10);
                addr.ip[1] = *(answer->str + stack[place] + 11);
                addr.ip[2] = *(answer->str + stack[place] + 12);
                addr.ip[3] = *(answer->str + stack[place] + 13);
        }
        /* We really should add IPv6 support...this will be done later */

catch_dwx_get_rr_from_answer:
        return addr;
}

/* Given a NS referral string and an offset, get the glueless NS referral
 * from the string and return it
 */
ip_addr_T dwx_ns_getip_glueless(dw_str *list, int offset) {
        ip_addr_T addr = {0, {0,0}, 0, 0};
        dw_str *query = 0, *answer = 0;
        int type = 1;

        if(list == 0 || *(list->str + offset) != 2) {
                goto catch_dwx_ns_getip_glueless;
        }
        type = key_n[DWM_N_ns_glueless_type];
        if(type != RR_A && type != RR_AAAA && type != RR_ANY) {
                type = RR_A; /* Sanity check */
        }

        /* See if it is in the cache */
        query = dw_get_dname(list->str + 3, offset, 256);
        dwc_lower_case(query);
        if(query == 0 || dw_push_u16(type,query) == -1) {
                goto catch_dwx_ns_getip_glueless;
        }
        answer = dwh_get(cache,query,0,1);
        if(answer != 0) {
                addr = dwx_get_rr_from_answer(answer);
                if(addr.len != 0) { /* Return from cache if success */
                        goto catch_dwx_ns_getip_glueless;
                }
        }

        addr.glueless = dw_get_dname(list->str + 3, offset, 260);
        dw_put_u16(addr.glueless, key_n[DWM_N_ns_glueless_type], -1);
        if(addr.glueless == 0) {
                goto catch_dwx_ns_getip_glueless;
        }
        addr.len = 127; /* Tell them this is a glueless NS referral */

catch_dwx_ns_getip_glueless:
        if(query != 0) {
                dw_destroy(query);
        }
        if(answer != 0) {
                dw_destroy(answer);
        }
        return addr;
}

/* Given a NS referral list and the NS record we want to look at, tell us
 * what type of record it is */
int dwx_nsref_type(int a, int *offset, dw_str *list) {
        if(list == 0 || offset == 0) {
                return -1;
        }

        *offset = -1 * (2 + (a * 2));
        *offset = *offset - 1;
        *offset = dw_fetch_u16(list, *offset);
        if(*offset < 0 || *offset > list->len) {
                return -1;
        }

        return dw_fetch_u8(list,*offset);
}

/* Choose which upstream NS server we will contact.  "b" is the connection
 * number; "count" is the number of upstream NS servers; we also point
 * to an active random number generator */
int dwx_choose_ns(int b, int count, dwr_rg *rng, dw_str *list) {
        int out = 0, offset = 0, a = 0, type = 0;

        if(count <= 1) {
                return 0;
        }

        /* Choose the first one randomly; otherwise cycle them using a
         * largeish prime number chosen to have good "jumps".  A jump is
         * how many name servers we jump past for a given number
         * of name servers.  I like 173 because it has the following jumps:
         * Number of name servers:  2  3  4  5  6  7  8  9 10 11 12 13 14
         * Jump for this number:    1 -1  1 -2 -1 -2 -3  2  3 -3  5  4  5
         *
         * The jump is not 1 or -1 for all small numbers (16 or less) where
         * it's possible to have a non 1/-1 relatively prime sequence; this
         * is the smallest prime with this condition (others are 283, 317,
         * 653, 787, and 907).  This bit of research was done with a small
         * awk script: 'BEGIN{for(prime=131;prime<1024;prime+=2){y=0;for(z
         * =3;z*z<=prime;z++){if(prime % z == 0){y=1}}if(y==0){score=0;for
         * (a=1;a<17;a++){b = prime % a; c = b - a; c = -c; if(b>1 && c>1)
         * {score++}; print a " " b }print score ":" prime}}}'
         */
        if(rem[b].current_ns < 0) {
                for(a = 0; a < 5; a++) { /* Favor glued records */
                        out = dwr_rng(rng) % count;
                        type = dwx_nsref_type(out,&offset,list);
                        if(type == RR_A
#ifdef IPV6
                        || type == RR_AAAA
#endif /* IPV6 */
                        ) {
                                break;
                        }
                }
        } else {
                out = rem[b].current_ns;
                out = (out + 173) % count;
        }
        rem[b].current_ns = out;

        return out;
}

/* Given a list of NS referrals (in dwx_make_ns_refer format), a pointer
 * to our random number generator, and the connection number we are on (b),
 * return either the IP or glueless hostname of a randomly chosen NS record
 */

ip_addr_T dwx_ns_getip(dw_str *list, dwr_rg *rng, int b) {
        ip_addr_T addr = {0, {0,0}, 0, 0};
        int type = 0, count = 0, offset = 0, a = 0, rr = 0;

        if(list == 0 || rng == 0) {
                goto catch_dwx_ns_getip;
        }

        /* See what kind of record this is and how many RRs we have */
        type = dw_fetch_u8(list,-1);
        if(type != TYPE_NS_REFER && type != TYPE_UPSTREAM_REFER) {
                goto catch_dwx_ns_getip;
        }
        count = dw_fetch_u8(list,-2);
        if(count <= 0 || count > 127) {
                goto catch_dwx_ns_getip;
        }

        /* Choose a RR at random */
        a = dwx_choose_ns(b, count, rng, list);
        rr = dwx_nsref_type(a, &offset, list);

        if(rr == RR_A) {
                addr = dwx_ns_getip_ipv4(list,offset);
#ifdef IPV6
        } else if(rr == RR_AAAA) {
                addr = dwx_ns_getip_ipv6(list,offset);
#endif /* IPV6 */
        } else if(rr == RR_NS) {
                addr = dwx_ns_getip_glueless(list,offset);
        } else {
                goto catch_dwx_ns_getip;
        }

        if(type == TYPE_UPSTREAM_REFER) {
                addr.flags = 1;
        }
        return addr;

catch_dwx_ns_getip:
        addr.len = 0;
        return addr;
}

/* Look for a given query, to see if it is already "in flight".  If it is,
 * we return the remote UDP connection with the inflight query; if we
 * do not find an inflight query, return -1. */
int dwx_find_inflight(dw_str *query) {
        dw_str *answer = 0;
        int ret = -1;

        answer = dwh_get(inflight, query, 0, 1);
        if(answer == 0) {
                return -1;
        }

        ret = dw_fetch_u16(answer,0);
        dw_destroy(answer);

        if(rem[ret].socket == INVALID_SOCKET) { /* Sanity check */
                return -1;
        }

        if(dw_issame(query,rem[ret].query) != 1) { /* Actually not same */
                dwh_zap(inflight,query, 0, 1); /* Remove corrupt data */
                return -1;
        }

        return ret;

}

/* Initialize all of the "local" elements of an already existing connection */
void dwx_init_conn_local(int32_t conn_number, int local_number) {
        if(rem[conn_number].local == 0 ||  /* Sanity check */
                        rem[conn_number].local[local_number] == 0) {
                return;
        }
        rem[conn_number].local[local_number]->from_socket = INVALID_SOCKET;
        rem[conn_number].local[local_number]->port = 0;
        rem[conn_number].local[local_number]->tcp_num = -1;
        rem[conn_number].local[local_number]->action = 0;
}

#ifdef XTRA_STUFF
/* Given an "address" with a NS referral (addr), a connection which needs to
 * process the glueless referral (conn_number), and an already existing
 * connection which we will attach to (already), connect our query to the
 * already existing connection
 */
int dwx_do_glueless_inflight(int32_t conn_number, int already, int type) {
        int max = 0, num_alloc = 0;

        if(already == conn_number || rem[conn_number].recurse_depth >= 83 ||
           rem[already].recurse_depth >= 83) {
                return -1;
        }

        rem[conn_number].recurse_depth++;
        rem[already].recurse_depth++;

        num_alloc = key_n[DWM_N_max_inflights];
        if(num_alloc < 1) {
                num_alloc = 1;
        } else if(num_alloc > 32000) {
                num_alloc = 32000;
        }
        num_alloc++; /* Stop off-by-one attacks */

        max = rem[conn_number].recurse_depth;
        if(rem[already].recurse_depth > max) {
                max = rem[already].recurse_depth;
        }
        rem[conn_number].recurse_depth = max + 1;
        rem[already].recurse_depth = max + 1;

        /* Add this glueless NS request to the already existing query */
        if(rem[already].num_locals >= num_alloc - 2) {
                return -2; /* No more inflights for this query; make new one */
        }
        rem[already].num_locals++;
        rem[already].local[rem[already].num_locals-1] =
                        dw_malloc(sizeof(local_T));
        if(rem[already].local[rem[already].num_locals-1] != 0) {
                rem[already].local[rem[already].num_locals-1]->orig_query = 0;
                rem[already].local[rem[already].num_locals-1]->action = 0;
        }
        dwx_init_conn_local(already,rem[already].num_locals - 1);
        if(rem[already].socket == INVALID_SOCKET ||
           rem[already].local[rem[already].num_locals - 1] == 0) {
                reset_rem(already);
                closesocket(b_remote[already]);
                b_remote[already] = INVALID_SOCKET;
                return -1;
        }
        rem[already].local[rem[already].num_locals - 1]->glueless_type = type;
        rem[already].local[rem[already].num_locals - 1]->glueless_conn =
                        conn_number;
        return 1; /* Success */

}
#endif /* XTRA_STUFF */

/* Create a new outgoing query to process a glueless request (NS or CNAME);
 * similiar to forward_local_udp_packet() and make_new_udp_connect()
 */
void dwx_do_glueless_new(dw_str *query, int32_t conn_number, int type) {
        int32_t new_conn_num = 0;
        int num_alloc = 0;
        int depth = 0;
        dw_str *packet = 0;

        num_alloc = key_n[DWM_N_max_inflights];
        if(num_alloc < 1) {
                num_alloc = 1;
        } else if(num_alloc > 32000) {
                num_alloc = 32000;
        }
        num_alloc++; /* Stop off-by-one attacks */
        if(rem[conn_number].recurse_depth >= 83) {
                return;
        }
        rem[conn_number].recurse_depth++;

        /* Make sure we "bubble up" the fact we have made a new query */
        new_conn_num = conn_number;
        depth = 0;
        while(rem[conn_number].num_locals > 0 &&
              rem[conn_number].local != 0 &&
              depth < 120) {
                if(rem[conn_number].local[0] != 0) {
                        conn_number = rem[conn_number].local[0]->glueless_conn;
                }
                if(conn_number == -1) {
                        break;
                }
                if(conn_number < 0 || conn_number > maxprocs) {
                        return;
                }
                if(rem[conn_number].recurse_depth > 83) {
                        return;
                }
                rem[conn_number].recurse_depth++;
                depth++;
        }
        conn_number = new_conn_num;


        new_conn_num = find_free_remote();
        if(new_conn_num == -1) { /* No more remote pending connections */
                return;
        }

        reset_rem(new_conn_num);
        closesocket(b_remote[new_conn_num]);
        b_remote[new_conn_num] = INVALID_SOCKET;
        rem[new_conn_num].query = dw_copy(query);
        rem[new_conn_num].recurse_depth = rem[conn_number].recurse_depth + 1;
        rem[new_conn_num].socket = INVALID_SOCKET;
        rem[new_conn_num].remote_id = dwr_rng(rng_seed);
        /* Note that RD is always set to 0, even though the upstream server
         * may be an upstream, and not root, server.  This is a bug. */
        packet = make_dns_header(rem[new_conn_num].remote_id,0x0000,0,0,0);
        if(packet == 0 || dw_append(rem[new_conn_num].query,packet) == -1 ||
                        dw_put_u16(packet,1,-1) == -1 /* QCLASS: 1 */) {
                reset_rem(new_conn_num);
                closesocket(b_remote[new_conn_num]);
                b_remote[new_conn_num] = INVALID_SOCKET;
                goto catch_dwx_do_ns_glueless_new;
        }
        /* Connect to remote server... */
        rem[new_conn_num].local = dw_malloc(num_alloc * sizeof(local_T *));
        if(rem[new_conn_num].local == 0) {
                reset_rem(new_conn_num);
                closesocket(b_remote[new_conn_num]);
                b_remote[new_conn_num] = INVALID_SOCKET;
                goto catch_dwx_do_ns_glueless_new;
        }
        make_remote_connection(new_conn_num,(unsigned char *)packet->str,
                        packet->len,rem[new_conn_num].query,INVALID_SOCKET);
        rem[conn_number].die = get_time() + ((int64_t)timeout_seconds << 12);
        rem[conn_number].child_id = new_conn_num;
        /* Set new connection parameters */
        rem[new_conn_num].die = get_time() + ((int64_t)timeout_seconds << 8);
        rem[new_conn_num].num_locals = 1;
        rem[new_conn_num].local[0] = dw_malloc(sizeof(local_T));
        if(rem[new_conn_num].local[0] != 0) {
                rem[new_conn_num].local[0]->orig_query = 0;
                rem[new_conn_num].local[0]->action = 0;
        }
        dwx_init_conn_local(new_conn_num,0);
        if(rem[new_conn_num].socket == INVALID_SOCKET ||
           rem[new_conn_num].local[0] == 0) {
                reset_rem(new_conn_num);
                closesocket(b_remote[new_conn_num]);
                b_remote[new_conn_num] = INVALID_SOCKET;
                goto catch_dwx_do_ns_glueless_new;
        }
        rem[new_conn_num].local[0]->glueless_type = type;
        rem[new_conn_num].local[0]->glueless_conn = conn_number;

catch_dwx_do_ns_glueless_new:
        if(packet != 0) {
                dw_destroy(packet);
        }

}

/* Given an "address" with a NS referral (addr), and a connection which needs
 * to process the glueless referral (conn_number), create a new remote
 * connection to process the "child" glueless request while the parent
 * request waits (and has its timeout occasionally updated).
 *
 * Note that this function is responsible for freeing the allocated string
 * addr.glueless
 */

void dwx_do_ns_glueless(ip_addr_T addr, int32_t conn_number) {
        /* int already = 0; */

        if(addr.len != 127 || addr.glueless == 0) {
                goto clean_dwx_do_ns_glueless;
        }

        /* Do we connect to an already existing query? */
        /* Disabled because this code has not been tested */
        /*
        already = dwx_find_inflight(addr.glueless);
        rem[conn_number].child_id = already;
        if(already != -1) {
                if(dwx_do_glueless_inflight(conn_number, already, 1)
                                != -2) {
                        goto clean_dwx_do_ns_glueless;
                }
        }
        */

        /* If not, then we make a new query */
        dwc_lower_case(addr.glueless);
        dwx_do_glueless_new(addr.glueless, conn_number,1);

clean_dwx_do_ns_glueless:
        dw_destroy(addr.glueless);
}

/* Once a glueless part of a query is finished, we have to make sure the
 * query that had to spawn the glueless query gets the NS record we looked
 * for and goes on processing the query */

void dwx_glueless_done(dw_str *query, int32_t conn_num) {
        ip_addr_T addr = {0, {0,0}, 0, 0};
        dw_str *answer = 0, *packet = 0;
        sockaddr_all_T server;
        SOCKET s = INVALID_SOCKET;
        socklen_t inet_len = sizeof(struct sockaddr_in);

        if(rem[conn_num].recurse_depth > 83) {
                return;
        }
        rem[conn_num].recurse_depth++;

        /* Get answer from cache */
        answer = dwh_get(cache,query,0,1);
        if(answer == 0) {
                goto catch_dwx_glueless_done;
        }
        addr = dwx_get_rr_from_answer(answer);
        if(addr.len != 4
#ifdef IPV6
                && addr.len != 16
#endif /* IPV6 */
        ) {
                goto catch_dwx_glueless_done;
        }
        s = setup_server(&server,&addr);
        if(s == INVALID_SOCKET) {
                goto catch_dwx_glueless_done;
        }
#ifdef IPV6
        if (server.Family == AF_INET6) {
                inet_len = sizeof(struct sockaddr_in6);
        }
#endif /* IPV6 */
        rem[conn_num].remote_id = dwr_rng(rng_seed);
        /* Make sure the following does not leak */
        /* Yes, RD is 0.  Yes, this may very well be a bug */
        packet = make_dns_header(rem[conn_num].remote_id,0x0000,0,0,0);
        if(packet == 0 || dw_append(rem[conn_num].query,packet) == -1 ||
                        dw_put_u16(packet,1,-1) == -1 /* QCLASS: 1 */) {
                goto catch_dwx_glueless_done;
        }
        make_socket_nonblock(s); /* Linux kernel bug */
        if ((do_random_bind(s,addr.len) == -1) ||
            (connect(s, (struct sockaddr *)&server, inet_len) == -1) ||
            (send(s,packet->str,packet->len,0) < 0)) {
                closesocket(s);
                goto catch_dwx_glueless_done;
        }

        b_remote[conn_num] = s;
        if(rem[conn_num].socket != INVALID_SOCKET) {
                closesocket(rem[conn_num].socket);
        }
        rem[conn_num].socket = s;
        /* We should have now finished up updating this connection */

catch_dwx_glueless_done:
        if(answer != 0) {
                dw_destroy(answer);
        }
        if(packet != 0) {
                dw_destroy(packet);
        }
}

/* Create a child query to solve an incomplete CNAME referral */
int dwx_do_cname_glueless(dw_str *query, int conn_num) {
        /* int already = 0; */

        if(query == 0) {
                return -1;
        }

        /* Do we connect to an already existing query? */
        /* Disabled because this code has not been tested */
        /*
        already = dwx_find_inflight(query);
        rem[conn_number].child_id = already;
        if(already != -1) {
                if(dwx_do_glueless_inflight(conn_num, already, 2) != -2) {
                        return 1;
                }
        }
        */

        dwx_do_glueless_new(query, conn_num, 2);

        return 1;
}

/* Handle having an incomplete CNAME query finished upstream */
void dwx_incomplete_cname_done(dw_str *query, int child, int l) {
        int parent = 0;
        dw_str *cname_cache = 0, *action = 0, *answer = 0;

        /* Arguments: conn_num, query (conn_num->query),
         * action (from cache, make qtype 65394 via
         * dw_put_u16(cname_cache, 65394, -3)), answer (from cache, via
         * parent query)
         */
        if(rem[child].local == 0 || rem[child].local[l] == 0) {
                goto catch_dwx_incomplete_cname_done;
        }

        parent = rem[child].local[l]->glueless_conn;
        if(rem[parent].child_id != child) {
                goto catch_dwx_incomplete_cname_done;
        }
        cname_cache = dw_copy(rem[parent].query);
        if(cname_cache == 0 || cname_cache->len < 3 ||
                        dw_put_u16(cname_cache, 65394, -3) == -1) {
                goto catch_dwx_incomplete_cname_done;
        }
        action = dwh_get(cache, cname_cache, 0, 1);
        answer = dwh_get(cache, query, 0, 1);
        if(action == 0 || answer == 0) {
                goto catch_dwx_incomplete_cname_done;
        }

        dwx_make_cname_reply(parent, rem[parent].query, action, answer, 0);

catch_dwx_incomplete_cname_done:
        if(cname_cache != 0) {
                dw_destroy(cname_cache);
        }
        if(action != 0) {
                dw_destroy(action);
        }
        if(answer != 0) {
                dw_destroy(answer);
        }
}

/* See if a CNAME referral is already cached; if so, chase the CNAME
 * Input:
 * orig_query: case-preserved version of query from client
 * query: lower-case version of query from client
 * client: Structure for sending info to client
 * from_ip: IP of client
 * local_id: Query ID of DNS query from client
 * sock: Socket ID of local client
 */
int dwx_cname_in_cache(dw_str *orig_query, dw_str *query,
                sockaddr_all_T *client, ip_addr_T *from_ip, int32_t local_id,
                SOCKET sock, uint16_t from_port) {
        dw_str *cname_cache = 0, *action = 0, *real_query = 0, *packet = 0;
        int ret = 0;
        int_fast32_t b = 0, offset = 0, num_alloc = 1, qtype = 0;

        num_alloc = key_n[DWM_N_max_inflights];
        if(num_alloc < 1) {
                num_alloc = 1;
        }
        if(num_alloc > 32000) {
                num_alloc = 32000;
        }

        if(orig_query == 0 || query == 0 || client == 0 || from_ip == 0) {
                return 0; /* Failed */
        }
        /* Look for CNAME of query */
        cname_cache=dw_copy(query);
        if(cname_cache == 0 || cname_cache->len < 3 ||
                        dw_put_u16(cname_cache, 65394, -3) == -1) {
                goto catch_dwx_cname_in_cache;
        }
        action = dwh_get(cache, cname_cache, 0, 1);
        if(action == 0) {
                goto catch_dwx_cname_in_cache;
        }
        dw_log_dwstr("Cached CNAME refer found for ",query,100);

        /* Create new remote for solving incomplete CNAME */
        offset = dw_fetch_u16(action, -2);
        offset += 2; /* Go past two-byte length */
        real_query = dw_get_dname(action->str, offset, 260);
        dwc_lower_case(real_query);
        if(real_query == 0) {
                goto catch_dwx_cname_in_cache;
        }
        qtype = dw_fetch_u16(query,-1);
        if(qtype == -1 || dw_put_u16(real_query, qtype, -1) == -1) {
                goto catch_dwx_cname_in_cache;
        }

        b = find_free_remote();
        if(b == -1) { /* We're overloaded */
                goto catch_dwx_cname_in_cache;
        }
        reset_rem(b);
        rem[b].query = dw_copy(real_query);
        rem[b].remote_id = dwr_rng(rng_seed);
        rem[b].local = dw_malloc(num_alloc * sizeof(local_T *));
        if(rem[b].local == 0) {
                goto catch_dwx_cname_in_cache;
        }
        rem[b].local[0] = dw_malloc(sizeof(local_T));
        rem[b].num_locals = 0;
        if(rem[b].local[0] == 0) {
                reset_rem(b);
                goto catch_dwx_cname_in_cache;
        }
        rem[b].num_locals = 1;
        rem[b].die = get_time() + ((int64_t)timeout_seconds << 12);
        rem[b].local[0]->orig_query = dw_copy(orig_query);
        rem[b].local[0]->action = dw_copy(action);
        rem[b].local[0]->from_socket = sock;
        rem[b].local[0]->tcp_num = INVALID_SOCKET;
        rem[b].local[0]->port = from_port;
        rem[b].local[0]->local_id = local_id;
        rem[b].local[0]->glueless_type = 3;
        rem[b].local[0]->glueless_conn = -1;
        rem[b].local[0]->ip = *from_ip;

        /* Make packet to send upstream */
        packet = make_dns_header(rem[b].remote_id,0x0000,0,0,0);
        if(packet == 0 || dw_append(rem[b].query,packet) == -1 ||
                        dw_put_u16(packet,1,-1) == -1 /* QCLASS: 1 */) {
                reset_rem(b);
                goto catch_dwx_cname_in_cache;
        }
        /* Send packet upstream */
        make_remote_connection(b,(unsigned char *)packet->str,
                        packet->len,rem[b].query,INVALID_SOCKET);

        ret = 1;

catch_dwx_cname_in_cache:
        if(cname_cache != 0) {
                dw_destroy(cname_cache);
        }
        if(action != 0) {
                dw_destroy(action);
        }
        if(real_query != 0) {
                dw_destroy(real_query);
        }
        if(packet != 0) {
                dw_destroy(packet);
        }
        return ret;
}

void dwx_cached_cname_done(dw_str *query, int b, int l, int depth) {

        dw_str *answer = 0, *oquery = 0, *action = 0;

        if(rem[b].local == 0 || rem[b].local[l] == 0 || depth > 32) {
                return;
        }

        oquery = dw_copy(rem[b].local[l]->orig_query);
        dwc_lower_case(oquery);
        action = dw_copy(rem[b].local[l]->action);
        answer = dwh_get(cache, query, 0, 1);

        rem[b].local[l]->glueless_type = 0; /* For dwx_make_cname_reply */

        if(answer == 0 || oquery == 0 || action == 0) {
                goto catch_dwx_cached_cname_done;
        }

        dwx_make_cname_reply(b,oquery,action,answer,depth + 1);

catch_dwx_cached_cname_done:
        dw_destroy(answer);
        dw_destroy(oquery);
        dw_destroy(action);
}

