root/usr.sbin/nsd/xfr-inspect.c
/* xfr-inspect - list the contents and inspect an zone transfer XFR file
 * By W.C.A. Wijngaards
 * Copyright 2017, NLnet Labs.
 * BSD, see LICENSE.
 */

#include "config.h"
#include "util.h"
#include "buffer.h"
#include "packet.h"
#include "rdata.h"
#include "namedb.h"
#include "difffile.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>

/** verbosity for inspect */
static int v = 0;
/** shorthand for ease */
#ifdef ULL
#undef ULL
#endif
#define ULL (unsigned long long)

/** print usage text */
static void
usage(void)
{
        printf("usage:  xfr-inspect [options] file\n");
        printf(" -h             this help\n");
        printf(" -v             increase verbosity: "
               "with -v(list chunks), -vv(inside chunks)\n");
        printf(" -l             list contents of transfer\n");
}

static int
xi_diff_read_64(FILE *in, uint64_t* result)
{
        if (fread(result, sizeof(*result), 1, in) == 1) {
                return 1;
        } else {
                return 0;
        }
}

static int
xi_diff_read_32(FILE *in, uint32_t* result)
{
        if (fread(result, sizeof(*result), 1, in) == 1) {
                *result = ntohl(*result);
                return 1;
        } else {
                return 0;
        }
}

static int
xi_diff_read_8(FILE *in, uint8_t* result)
{
        if (fread(result, sizeof(*result), 1, in) == 1) {
                return 1;
        } else {
                return 0;
        }
}

static int
xi_diff_read_str(FILE* in, char* buf, size_t len)
{
        uint32_t disklen;
        if(!xi_diff_read_32(in, &disklen))
                return 0;
        if(disklen >= len)
                return 0;
        if(fread(buf, disklen, 1, in) != 1)
                return 0;
        buf[disklen] = 0;
        return 1;
}


/** inspect header of xfr file, return num_parts */
static int
inspect_header(FILE* in)
{
        char zone_buf[3072];
        char patname_buf[2048];

        uint32_t old_serial, new_serial, num_parts, type;
        uint64_t time_end_0, time_start_0;
        uint32_t time_end_1, time_start_1;
        uint8_t committed;

        time_t time_end, time_start;

        if(!xi_diff_read_32(in, &type)) {
                printf("could not read type, file short\n");
                fclose(in);
                exit(1);
        }
        if(type != DIFF_PART_XFRF) {
                printf("type:   %x (BAD FILE TYPE)\n", type);
                fclose(in);
                exit(1);
        }
        if(!xi_diff_read_8(in, &committed) ||
                !xi_diff_read_32(in, &num_parts) ||
                !xi_diff_read_64(in, &time_end_0) ||
                !xi_diff_read_32(in, &time_end_1) ||
                !xi_diff_read_32(in, &old_serial) ||
                !xi_diff_read_32(in, &new_serial) ||
                !xi_diff_read_64(in, &time_start_0) ||
                !xi_diff_read_32(in, &time_start_1) ||
                !xi_diff_read_str(in, zone_buf, sizeof(zone_buf)) ||
                !xi_diff_read_str(in, patname_buf, sizeof(patname_buf))) {
                printf("diff file bad commit part, file too short");
                fclose(in);
                exit(1);
        }
        time_end = (time_t)time_end_0;
        time_start = (time_t)time_start_0;

        /* printf("type:                %x\n", (int)type); */
        printf("committed:      %d (%s)\n", (int)committed,
                committed?"yes":"no");
        printf("num_parts:      %d\n", (int)num_parts);
        printf("time_end:       %d.%6.6d %s", (int)time_end_0,
                (int)time_end_1, ctime(&time_end));
        printf("old_serial:     %u\n", (unsigned)old_serial);
        printf("new_serial:     %u\n", (unsigned)new_serial);
        printf("time_start:     %d.%6.6d %s", (int)time_start_0,
                (int)time_start_1, ctime(&time_start));
        printf("zone:           %s\n", zone_buf);
        printf("patname:        %s\n", patname_buf);

        return num_parts;
}

/** print records in packet */
static void
print_records(region_type* region, buffer_type* pkt, int num, int qsection)
{
        domain_table_type* table;
        int i;
        rr_type* rr;
        region_type* tmpregion = region_create(xalloc, free);
        buffer_type* tmpbuf;
        if(!tmpregion) {
                printf("out of memory\n");
                return;
        }
        tmpbuf = buffer_create(region, QIOBUFSZ);
        if(!tmpbuf) {
                printf("out of memory\n");
                return;
        }
        table = domain_table_create(tmpregion);
        if(!table) {
                printf("out of memory\n");
                return;
        }

        for(i=0; i<num; ++i) {
                rr = packet_read_rr(region, table, pkt, qsection);
                if(!rr) {
                        printf("; cannot read rr %d\n", i);
                        return;
                }
                if(qsection) {
                        printf("%s", dname_to_string(domain_dname(rr->owner),
                                NULL));
                        printf("\t%s", rrclass_to_string(rr->klass));
                        if(rr->type == TYPE_IXFR)
                                printf("\tIXFR\n");
                        else if(rr->type == TYPE_AXFR)
                                printf("\tAXFR\n");
                        else printf("\t%s\n", rrtype_to_string(rr->type));
                } else {
                        if(!print_rr(stdout, NULL, rr, tmpregion, tmpbuf)) {
                                printf("; cannot print rr %d\n", i);
                        }
                }
        }
        region_destroy(tmpregion);
}

/** inspect packet (IXFR or AXFR) */
static void
inspect_packet(region_type* region, buffer_type* pkt)
{
        printf("\n");
        if(buffer_limit(pkt) < QHEADERSZ) {
                printf("packet too short\n");
                return;
        }
        printf("; id=%4.4x ; flags%s%s%s%s%s%s%s%s ; rcode %s ; opcode %d\n",
                ID(pkt), QR(pkt)?" QR":"", AA(pkt)?" AA":"", TC(pkt)?" TC":"",
                RD(pkt)?" RD":"", RA(pkt)?" RA":"", Z(pkt)?" Z":"",
                AD(pkt)?" AD":"", CD(pkt)?" CD":"", rcode2str(RCODE(pkt)),
                OPCODE(pkt));
        printf("; qdcount %d ; ancount %d ; nscount %d ; arcount %d\n",
                QDCOUNT(pkt), ANCOUNT(pkt), NSCOUNT(pkt), ARCOUNT(pkt));
        buffer_skip(pkt, QHEADERSZ);

        if(QDCOUNT(pkt) != 0) {
                printf("; QUESTION SECTION\n");
                print_records(region, pkt, QDCOUNT(pkt), 1);
        }
        if(ANCOUNT(pkt) != 0) {
                printf("; ANSWER SECTION\n");
                print_records(region, pkt, ANCOUNT(pkt), 0);
        }
        if(NSCOUNT(pkt) != 0) {
                printf("; AUTHORITY SECTION\n");
                print_records(region, pkt, NSCOUNT(pkt), 0);
        }
        if(ARCOUNT(pkt) != 0) {
                printf("; ADDITIONAL SECTION\n");
                print_records(region, pkt, ARCOUNT(pkt), 0);
        }
}

/** inspect part of xfr file */
static void
inspect_part(FILE* in, int partnum)
{
        uint32_t pkttype, msglen, msglen2;
        region_type* region;
        buffer_type* packet;
        region = region_create(xalloc, free);
        if(!region) {
                printf("out of memory\n");
                fclose(in);
                exit(1);
        }
        packet = buffer_create(region, QIOBUFSZ);
        if(!xi_diff_read_32(in, &pkttype)) {
                printf("cannot read part %d\n", partnum);
                fclose(in);
                exit(1);
        }
        if(pkttype != DIFF_PART_XXFR) {
                printf("bad part %d: not type XXFR\n", partnum);
                fclose(in);
                exit(1);
        }
        if(!xi_diff_read_32(in, &msglen)) {
                printf("bad part %d: not msglen, file too short\n", partnum);
                fclose(in);
                exit(1);
        }
        if(msglen < QHEADERSZ || msglen > QIOBUFSZ) {
                printf("bad part %d: msglen %u (too short or too long)\n",
                        partnum, (unsigned)msglen);
                fclose(in);
                exit(1);
        }
        if(fread(buffer_begin(packet), msglen, 1, in) != 1) {
                printf("bad part %d: short packet, file too short, %s\n",
                        partnum, strerror(errno));
                fclose(in);
                exit(1);
        }
        if(!xi_diff_read_32(in, &msglen2)) {
                printf("bad part %d: cannot read msglen2, file too short\n", partnum);
                fclose(in);
                exit(1);
        }
        if(v==0) {
                region_destroy(region);
                return;
        }

        printf("\n");
        /* printf("type : %x\n", pkttype); */
        printf("part    : %d\n", partnum);
        printf("msglen  : %u\n", (unsigned)msglen);
        printf("msglen2 : %u (%s)\n", (unsigned)msglen2,
                (msglen==msglen2)?"ok":"wrong");

        if(v>=2) {
                buffer_set_limit(packet, msglen);
                inspect_packet(region, packet);
        }

        region_destroy(region);
}

/** inspect parts of xfr file */
static void
inspect_parts(FILE* in, int num)
{
        int i;
        for(i=0; i<num; i++) {
                inspect_part(in, i);
        }
}

/** inspect trail of xfr file */
static void
inspect_trail(FILE* in)
{
        char log_buf[5120];
        if(!xi_diff_read_str(in, log_buf, sizeof(log_buf))) {
                printf("bad trail: cannot read log string\n");
                fclose(in);
                exit(1);
        }
        printf("\n");
        printf("log:    %s\n", log_buf);
}

/** inspect contents of xfr file */
static void
inspect_file(char* fname)
{
        FILE* in;
        int num;
        log_init("udb-inspect");
        if(!(in=fopen(fname, "r"))) {
                printf("cannot open %s: %s\n", fname, strerror(errno));
                exit(1);
        }
        printf("file:   %s\n", fname);
        num = inspect_header(in);
        inspect_parts(in, num);
        inspect_trail(in);
        fclose(in);
}

/** list header of xfr file, return num_parts */
static int
list_header(FILE* in)
{
        char zone_buf[3072];
        char patname_buf[2048];

        uint32_t old_serial, new_serial, num_parts, type;
        uint64_t time_end_0, time_start_0;
        uint32_t time_end_1, time_start_1;
        uint8_t committed;

        time_t time_end, time_start;

        if(!xi_diff_read_32(in, &type)) {
                printf("could not read type, file short\n");
                fclose(in);
                exit(1);
        }
        if(type != DIFF_PART_XFRF) {
                printf("type:   %x (BAD FILE TYPE)\n", type);
                fclose(in);
                exit(1);
        }
        if(!xi_diff_read_8(in, &committed) ||
                !xi_diff_read_32(in, &num_parts) ||
                !xi_diff_read_64(in, &time_end_0) ||
                !xi_diff_read_32(in, &time_end_1) ||
                !xi_diff_read_32(in, &old_serial) ||
                !xi_diff_read_32(in, &new_serial) ||
                !xi_diff_read_64(in, &time_start_0) ||
                !xi_diff_read_32(in, &time_start_1) ||
                !xi_diff_read_str(in, zone_buf, sizeof(zone_buf)) ||
                !xi_diff_read_str(in, patname_buf, sizeof(patname_buf))) {
                printf("diff file bad commit part, file too short");
                fclose(in);
                exit(1);
        }
        time_end = (time_t)time_end_0;
        time_start = (time_t)time_start_0;

        /* printf("; type:              %x\n", (int)type); */
        printf("; committed:    %d (%s)\n", (int)committed,
                committed?"yes":"no");
        printf("; num_parts:    %d\n", (int)num_parts);
        printf("; time_end:     %d.%6.6d %s", (int)time_end_0,
                (int)time_end_1, ctime(&time_end));
        printf("; old_serial:   %u\n", (unsigned)old_serial);
        printf("; new_serial:   %u\n", (unsigned)new_serial);
        printf("; time_start:   %d.%6.6d %s", (int)time_start_0,
                (int)time_start_1, ctime(&time_start));
        printf("; zone:         %s\n", zone_buf);
        printf("; patname:      %s\n", patname_buf);

        return num_parts;
}

/** list packet (IXFR or AXFR) */
static void
list_packet(region_type* region, buffer_type* pkt, int partnum)
{
        if(buffer_limit(pkt) < QHEADERSZ) {
                printf("packet too short\n");
                return;
        }
        buffer_skip(pkt, QHEADERSZ);

        if(partnum == 0 && QDCOUNT(pkt) == 1) {
                /* print query AXFR or IXFR */
                printf("; ");
                print_records(region, pkt, QDCOUNT(pkt), 1);
        }
        if(ANCOUNT(pkt) != 0) {
                print_records(region, pkt, ANCOUNT(pkt), 0);
        }
}

/** list part of xfr file */
static void
list_part(FILE* in, int partnum)
{
        uint32_t pkttype, msglen, msglen2;
        region_type* region;
        buffer_type* packet;
        region = region_create(xalloc, free);
        if(!region) {
                printf("out of memory\n");
                fclose(in);
                exit(1);
        }
        packet = buffer_create(region, QIOBUFSZ);
        if(!xi_diff_read_32(in, &pkttype)) {
                printf("cannot read part %d\n", partnum);
                fclose(in);
                exit(1);
        }
        if(pkttype != DIFF_PART_XXFR) {
                printf("bad part %d: not type XXFR\n", partnum);
                fclose(in);
                exit(1);
        }
        if(!xi_diff_read_32(in, &msglen)) {
                printf("bad part %d: not msglen, file too short\n", partnum);
                fclose(in);
                exit(1);
        }
        if(msglen < QHEADERSZ || msglen > QIOBUFSZ) {
                printf("bad part %d: msglen %u (too short or too long)\n",
                        partnum, (unsigned)msglen);
                fclose(in);
                exit(1);
        }
        if(fread(buffer_begin(packet), msglen, 1, in) != 1) {
                printf("bad part %d: short packet, file too short, %s\n",
                        partnum, strerror(errno));
                fclose(in);
                exit(1);
        }
        if(!xi_diff_read_32(in, &msglen2)) {
                printf("bad part %d: cannot read msglen2, file too short\n", partnum);
                fclose(in);
                exit(1);
        }

        buffer_set_limit(packet, msglen);
        list_packet(region, packet, partnum);
        region_destroy(region);
}

/** list parts of xfr file */
static void
list_parts(FILE* in, int num)
{
        int i;
        for(i=0; i<num; i++) {
                list_part(in, i);
        }
}

/** list contents of xfr file */
static void
list_file(char* fname)
{
        FILE* in;
        int num;
        log_init("udb-inspect");
        if(!(in=fopen(fname, "r"))) {
                printf("cannot open %s: %s\n", fname, strerror(errno));
                exit(1);
        }
        num = list_header(in);
        list_parts(in, num);

        fclose(in);
}

/** getopt global, in case header files fail to declare it. */
extern int optind;
/** getopt global, in case header files fail to declare it. */
extern char* optarg;

/**
 * main program. Set options given commandline arguments.
 * @param argc: number of commandline arguments.
 * @param argv: array of commandline arguments.
 * @return: exit status of the program.
 */
int
main(int argc, char* argv[])
{
        int c, list=0;
        while( (c=getopt(argc, argv, "hlv")) != -1) {
                switch(c) {
                case 'l':
                        list=1;
                        break;
                case 'v':
                        v++;
                        break;
                default:
                case 'h':
                        usage();
                        return 1;
                }
        }
        argc -= optind;
        argv += optind;
        if(argc != 1) {
                usage();
                return 1;
        }
        if(list) list_file(argv[0]);
        else    inspect_file(argv[0]);

        return 0;
}