/* 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 "udbzone.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;
}