diff options
Diffstat (limited to '')
-rw-r--r-- | src/utils/common/exec.c | 1212 |
1 files changed, 1212 insertions, 0 deletions
diff --git a/src/utils/common/exec.c b/src/utils/common/exec.c new file mode 100644 index 0000000..798ae42 --- /dev/null +++ b/src/utils/common/exec.c @@ -0,0 +1,1212 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <stdlib.h> +#include <time.h> + +#include "libdnssec/random.h" +#include "utils/common/exec.h" +#include "utils/common/msg.h" +#include "utils/common/netio.h" +#include "utils/common/params.h" +#include "libknot/libknot.h" +#include "contrib/ctype.h" +#include "contrib/macros.h" +#include "contrib/sockaddr.h" +#include "contrib/time.h" +#include "contrib/openbsd/strlcat.h" +#include "contrib/ucw/lists.h" +#include "contrib/wire_ctx.h" + +static const char *JSON_INDENT = " "; + +static knot_lookup_t rtypes[] = { + { KNOT_RRTYPE_A, "has IPv4 address" }, + { KNOT_RRTYPE_NS, "nameserver is" }, + { KNOT_RRTYPE_CNAME, "is an alias for" }, + { KNOT_RRTYPE_SOA, "start of authority is" }, + { KNOT_RRTYPE_PTR, "points to" }, + { KNOT_RRTYPE_MX, "mail is handled by" }, + { KNOT_RRTYPE_TXT, "description is" }, + { KNOT_RRTYPE_AAAA, "has IPv6 address" }, + { KNOT_RRTYPE_LOC, "location is" }, + { KNOT_RRTYPE_DS, "delegation signature is" }, + { KNOT_RRTYPE_SSHFP, "SSH fingerprint is" }, + { KNOT_RRTYPE_RRSIG, "RR set signature is" }, + { KNOT_RRTYPE_DNSKEY, "DNSSEC key is" }, + { KNOT_RRTYPE_TLSA, "has TLS certificate" }, + { 0, NULL } +}; + +static void print_header(const knot_pkt_t *packet, const style_t *style) +{ + if (packet->size < KNOT_WIRE_OFFSET_QDCOUNT) { + return; + } + + char flags[64] = ""; + char unknown_rcode[64] = ""; + char unknown_opcode[64] = ""; + + const char *rcode_str = NULL; + const char *opcode_str = NULL; + + uint16_t qdcount = 0, ancount = 0, nscount = 0, arcount = 0; + + uint16_t id = knot_wire_get_id(packet->wire); + + // Get extended RCODE. + const char *code_name = knot_pkt_ext_rcode_name(packet); + if (code_name[0] != '\0') { + rcode_str = code_name; + } else { + uint16_t code = knot_pkt_ext_rcode(packet); + (void)snprintf(unknown_rcode, sizeof(unknown_rcode), "RCODE %d", code); + rcode_str = unknown_rcode; + } + + // Get OPCODE. + uint8_t code = knot_wire_get_opcode(packet->wire); + const knot_lookup_t *opcode = knot_lookup_by_id(knot_opcode_names, code); + if (opcode != NULL) { + opcode_str = opcode->name; + } else { + (void)snprintf(unknown_opcode, sizeof(unknown_opcode), "OPCODE %d", code); + opcode_str = unknown_opcode; + } + + // Get flags. + size_t flags_rest = sizeof(flags); + const size_t flag_len = 4; + if (knot_wire_get_qr(packet->wire) != 0 && flags_rest > flag_len) { + flags_rest -= strlcat(flags, " qr", flags_rest); + } + if (knot_wire_get_aa(packet->wire) != 0 && flags_rest > flag_len) { + flags_rest -= strlcat(flags, " aa", flags_rest); + } + if (knot_wire_get_tc(packet->wire) != 0 && flags_rest > flag_len) { + flags_rest -= strlcat(flags, " tc", flags_rest); + } + if (knot_wire_get_rd(packet->wire) != 0 && flags_rest > flag_len) { + flags_rest -= strlcat(flags, " rd", flags_rest); + } + if (knot_wire_get_ra(packet->wire) != 0 && flags_rest > flag_len) { + flags_rest -= strlcat(flags, " ra", flags_rest); + } + if (knot_wire_get_z(packet->wire) != 0 && flags_rest > flag_len) { + flags_rest -= strlcat(flags, " z", flags_rest); + } + if (knot_wire_get_ad(packet->wire) != 0 && flags_rest > flag_len) { + flags_rest -= strlcat(flags, " ad", flags_rest); + } + if (knot_wire_get_cd(packet->wire) != 0 && flags_rest > flag_len) { + strlcat(flags, " cd", flags_rest); + } + + if (packet->size >= KNOT_WIRE_HEADER_SIZE) { + qdcount = knot_wire_get_qdcount(packet->wire); + ancount = knot_wire_get_ancount(packet->wire); + nscount = knot_wire_get_nscount(packet->wire); + arcount = knot_wire_get_arcount(packet->wire); + + if (knot_pkt_has_tsig(packet)) { + arcount++; + } + } + + // Print formatted info. + switch (style->format) { + case FORMAT_NSUPDATE: + printf(";; ->>HEADER<<- opcode: %s; status: %s; id: %u\n" + ";; Flags:%1s; " + "ZONE: %u; PREREQ: %u; UPDATE: %u; ADDITIONAL: %u\n", + opcode_str, rcode_str, id, flags, qdcount, ancount, + nscount, arcount); + break; + default: + printf(";; ->>HEADER<<- opcode: %s; status: %s; id: %u\n" + ";; Flags:%1s; " + "QUERY: %u; ANSWER: %u; AUTHORITY: %u; ADDITIONAL: %u\n", + opcode_str, rcode_str, id, flags, qdcount, ancount, + nscount, arcount); + break; + } +} + +static void print_footer(const size_t total_len, + const size_t msg_count, + const size_t rr_count, + const net_t *net, + const float elapsed, + time_t exec_time, + const bool incoming) +{ + struct tm tm; + char date[64]; + + // Get current timestamp. + if (exec_time == 0) { + exec_time = time(NULL); + } + + // Create formatted date-time string. + localtime_r(&exec_time, &tm); + strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S %Z", &tm); + + // Print messages statistics. + if (incoming) { + printf(";; Received %zu B", total_len); + } else { + printf(";; Sent %zu B", total_len); + } + + // If multimessage (XFR) print additional statistics. + if (msg_count > 0) { + printf(" (%zu messages, %zu records)\n", msg_count, rr_count); + } else { + printf("\n"); + } + // Print date. + printf(";; Time %s\n", date); + + // Print connection statistics. + if (net != NULL) { + if (incoming) { + printf(";; From %s", net->remote_str); + } else { + printf(";; To %s", net->remote_str); + } + + if (elapsed >= 0) { + printf(" in %.1f ms\n", elapsed); + } else { + printf("\n"); + } + } +} + +static void print_hex(const uint8_t *data, uint16_t len) +{ + for (int i = 0; i < len; i++) { + printf("%02X", data[i]); + } +} + +static void print_nsid(const uint8_t *data, uint16_t len) +{ + if (len == 0) { + return; + } + + print_hex(data, len); + + // Check if printable string. + for (int i = 0; i < len; i++) { + if (!is_print(data[i])) { + return; + } + } + printf(" \"%.*s\"", len, data); +} + +static bool print_text(const uint8_t *data, uint16_t len) +{ + if (len == 0) { + return false; + } + + // Check if printable string. + for (int i = 0; i < len; i++) { + if (!is_print(data[i])) { + return false; + } + } + printf("%.*s", len, data); + return true; +} + +static void print_edns_client_subnet(const uint8_t *data, uint16_t len) +{ + knot_edns_client_subnet_t ecs = { 0 }; + int ret = knot_edns_client_subnet_parse(&ecs, data, len); + if (ret != KNOT_EOK) { + return; + } + + struct sockaddr_storage addr = { 0 }; + ret = knot_edns_client_subnet_get_addr(&addr, &ecs); + assert(ret == KNOT_EOK); + + char addr_str[SOCKADDR_STRLEN] = { 0 }; + sockaddr_tostr(addr_str, sizeof(addr_str), &addr); + + printf("%s/%u/%u", addr_str, ecs.source_len, ecs.scope_len); +} + +static void print_ede(const uint8_t *data, uint16_t len) +{ + if (len < 2) { + printf("(malformed)"); + return; + } + + + uint16_t errcode; + memcpy(&errcode, data, sizeof(errcode)); + errcode = be16toh(errcode); + + const knot_lookup_t *item = knot_lookup_by_id(knot_edns_ede_names, errcode); + const char *strerr = (item != NULL) ? item->name : "Unknown code"; + + if (len > 2) { + printf("%hu (%s): '%.*s'", errcode, strerr, (int)(len - 2), data + 2); + } else { + printf("%hu (%s)", errcode, strerr); + } +} + +static void print_expire(const uint8_t *data, uint16_t len) +{ + if (len == 0) { + printf("(empty)"); + } else if (len != sizeof(uint32_t)) { + printf("(malformed)"); + } else { + char str[80] = ""; + uint32_t timer = knot_wire_read_u32(data); + if (knot_time_print_human(timer, str, sizeof(str), false) > 0) { + printf("%u (%s)", timer, str); + } else { + printf("%u", timer); + } + } +} + +static void print_section_opt(const knot_pkt_t *packet, const style_t *style) +{ + if (style->present_edns) { + size_t buflen = 8192; + char *buf = calloc(buflen, 1); + int ret = knot_rrset_txt_dump_edns(packet->opt_rr, + knot_wire_get_rcode(packet->wire), + buf, buflen, &style->style); + if (ret < 0) { + WARN("can't print OPT record (%s)", knot_strerror(ret)); + } else { + printf(". 0 ANY EDNS\t\t\t%s\n", buf); + } + free(buf); + return; + } + + char unknown_ercode[64] = ""; + const char *ercode_str = NULL; + + uint16_t ercode = knot_edns_get_ext_rcode(packet->opt_rr); + if (ercode > 0) { + ercode = knot_edns_whole_rcode(ercode, + knot_wire_get_rcode(packet->wire)); + } + + const knot_lookup_t *item = knot_lookup_by_id(knot_rcode_names, ercode); + if (item != NULL) { + ercode_str = item->name; + } else { + (void)snprintf(unknown_ercode, sizeof(unknown_ercode), "RCODE %d", ercode); + ercode_str = unknown_ercode; + } + + printf(";; Version: %u; flags: %s; UDP size: %u B; ext-rcode: %s\n", + knot_edns_get_version(packet->opt_rr), + (knot_edns_do(packet->opt_rr) != 0) ? "do" : "", + knot_edns_get_payload(packet->opt_rr), + ercode_str); + + assert(packet->opt_rr->rrs.count > 0); + knot_rdata_t *rdata = packet->opt_rr->rrs.rdata; + wire_ctx_t wire = wire_ctx_init_const(rdata->data, rdata->len); + + while (wire_ctx_available(&wire) >= KNOT_EDNS_OPTION_HDRLEN) { + uint16_t opt_code = wire_ctx_read_u16(&wire); + uint16_t opt_len = wire_ctx_read_u16(&wire); + uint8_t *opt_data = wire.position; + + if (wire.error != KNOT_EOK) { + WARN("invalid OPT record data"); + return; + } + + switch (opt_code) { + case KNOT_EDNS_OPTION_NSID: + printf(";; NSID: "); + print_nsid(opt_data, opt_len); + break; + case KNOT_EDNS_OPTION_CLIENT_SUBNET: + printf(";; CLIENT-SUBNET: "); + print_edns_client_subnet(opt_data, opt_len); + break; + case KNOT_EDNS_OPTION_PADDING: + printf(";; PADDING: %u B", opt_len); + break; + case KNOT_EDNS_OPTION_COOKIE: + printf(";; COOKIE: "); + print_hex(opt_data, opt_len); + break; + case KNOT_EDNS_OPTION_EDE: + printf(";; EDE: "); + print_ede(opt_data, opt_len); + break; + case KNOT_EDNS_OPTION_EXPIRE: + printf(";; EXPIRE: "); + print_expire(opt_data, opt_len); + break; + default: + printf(";; Option (%u): ", opt_code); + if (style->show_edns_opt_text) { + if (!print_text(opt_data, opt_len)) { + print_hex(opt_data, opt_len); + } + } else { + print_hex(opt_data, opt_len); + } + } + printf("\n"); + + wire_ctx_skip(&wire, opt_len); + } + + if (wire_ctx_available(&wire) > 0) { + WARN("invalid OPT record data"); + } +} + +static void print_section_question(const knot_dname_t *owner, + const uint16_t qclass, + const uint16_t qtype, + const style_t *style) +{ + size_t buflen = 8192; + char *buf = calloc(buflen, 1); + + // Don't print zero TTL. + knot_dump_style_t qstyle = style->style; + qstyle.empty_ttl = true; + + knot_rrset_t *question = knot_rrset_new(owner, qtype, qclass, 0, NULL); + + if (knot_rrset_txt_dump_header(question, 0, buf, buflen, &qstyle) < 0) { + WARN("can't print whole question section"); + } + + printf("%s\n", buf); + + knot_rrset_free(question, NULL); + free(buf); +} + +static void print_section_full(const knot_rrset_t *rrsets, + const uint16_t count, + const style_t *style, + const bool no_tsig) +{ + size_t buflen = 8192; + char *buf = calloc(buflen, 1); + + for (size_t i = 0; i < count; i++) { + // Ignore OPT records. + if (rrsets[i].type == KNOT_RRTYPE_OPT) { + continue; + } + + // Exclude TSIG record. + if (no_tsig && rrsets[i].type == KNOT_RRTYPE_TSIG) { + continue; + } + + if (knot_rrset_txt_dump(&rrsets[i], &buf, &buflen, + &(style->style)) < 0) { + WARN("can't print whole section"); + break; + } + printf("%s", buf); + } + + free(buf); +} + +static void print_section_dig(const knot_rrset_t *rrsets, + const uint16_t count, + const style_t *style) +{ + size_t buflen = 8192; + char *buf = calloc(buflen, 1); + + for (size_t i = 0; i < count; i++) { + const knot_rrset_t *rrset = &rrsets[i]; + uint16_t rrset_rdata_count = rrset->rrs.count; + for (uint16_t j = 0; j < rrset_rdata_count; j++) { + while (knot_rrset_txt_dump_data(rrset, j, buf, buflen, + &(style->style)) < 0) { + buflen += 4096; + // Oversize protection. + if (buflen > 100000) { + WARN("can't print whole section"); + break; + } + + char *newbuf = realloc(buf, buflen); + if (newbuf == NULL) { + WARN("can't print whole section"); + break; + } + buf = newbuf; + } + printf("%s\n", buf); + } + } + + free(buf); +} + +static void print_section_host(const knot_rrset_t *rrsets, + const uint16_t count, + const style_t *style) +{ + size_t buflen = 8192; + char *buf = calloc(buflen, 1); + + for (size_t i = 0; i < count; i++) { + const knot_rrset_t *rrset = &rrsets[i]; + const knot_lookup_t *descr; + char type[32] = "NULL"; + char *owner; + + owner = knot_dname_to_str_alloc(rrset->owner); + if (style->style.ascii_to_idn != NULL) { + style->style.ascii_to_idn(&owner); + } + descr = knot_lookup_by_id(rtypes, rrset->type); + + uint16_t rrset_rdata_count = rrset->rrs.count; + for (uint16_t j = 0; j < rrset_rdata_count; j++) { + if (rrset->type == KNOT_RRTYPE_CNAME && + style->hide_cname) { + continue; + } + + while (knot_rrset_txt_dump_data(rrset, j, buf, buflen, + &(style->style)) < 0) { + buflen += 4096; + // Oversize protection. + if (buflen > 100000) { + WARN("can't print whole section"); + break; + } + + char *newbuf = realloc(buf, buflen); + if (newbuf == NULL) { + WARN("can't print whole section"); + break; + } + buf = newbuf; + } + + if (descr != NULL) { + printf("%s %s %s\n", owner, descr->name, buf); + } else { + knot_rrtype_to_string(rrset->type, type, sizeof(type)); + printf("%s has %s record %s\n", owner, type, buf); + } + } + + free(owner); + } + + free(buf); +} + +static void print_error_host(const knot_pkt_t *packet, const style_t *style) +{ + char type[32] = "Unknown"; + const char *rcode_str = "Unknown"; + + knot_rrtype_to_string(knot_pkt_qtype(packet), type, sizeof(type)); + + // Get extended RCODE. + const char *code_name = knot_pkt_ext_rcode_name(packet); + if (code_name[0] != '\0') { + rcode_str = code_name; + } + + // Get record owner. + char *owner = knot_dname_to_str_alloc(knot_pkt_qname(packet)); + if (style->style.ascii_to_idn != NULL) { + style->style.ascii_to_idn(&owner); + } + + if (knot_pkt_ext_rcode(packet) == KNOT_RCODE_NOERROR) { + printf("Host %s has no %s record\n", owner, type); + } else { + printf("Host %s type %s error: %s\n", owner, type, rcode_str); + } + + free(owner); +} + +static void json_dname(jsonw_t *w, const char *key, const knot_dname_t *dname) +{ + knot_dname_txt_storage_t name; + if (knot_dname_to_str(name, dname, sizeof(name)) != NULL) { + jsonw_str(w, key, name); + } +} + +static void json_rdata(jsonw_t *w, const knot_rrset_t *rrset) +{ + char type[16]; + if (knot_rrtype_to_string(rrset->type, type, sizeof(type)) <= 0 || + strncmp(type, "TYPE", 4) == 0) { // Unknown/hex format. + return; + } + + char key[32] = "rdata"; + strlcat(key, type, sizeof(key)); + + char data[16384]; + const knot_dump_style_t *style = &KNOT_DUMP_STYLE_DEFAULT; + if (knot_rrset_txt_dump_data(rrset, 0, data, sizeof(data), style) > 0) { + jsonw_str(w, key, data); + } +} + +static void json_print_section(jsonw_t *w, const char *name, + const knot_pktsection_t *section) +{ + if (section->count == 0 || + (section->count == 1 && knot_pkt_rr(section, 0)->type == KNOT_RRTYPE_OPT)) { + return; + } + + char str[16]; + + jsonw_list(w, name); + + bool first_opt = true; + for (int i = 0; i < section->count; i++) { + const knot_rrset_t *rr = knot_pkt_rr(section, i); + if (rr->type == KNOT_RRTYPE_OPT && first_opt) { + first_opt = false; + continue; + } + jsonw_object(w, NULL); + json_dname(w, "NAME", rr->owner); + jsonw_int(w, "TYPE", rr->type); + if (knot_rrtype_to_string(rr->type, str, sizeof(str)) > 0) { + jsonw_str(w, "TYPEname", str); + } + jsonw_int(w, "CLASS", rr->rclass); + if (rr->type != KNOT_RRTYPE_OPT && // OPT class meaning is different. + knot_rrclass_to_string(rr->rclass, str, sizeof(str)) > 0) { + jsonw_str(w, "CLASSname", str); + } + jsonw_int(w, "TTL", rr->ttl); + if (rr->type != KNOT_RRTYPE_OPT) { // OPT with HEX rdata. + json_rdata(w, rr); + } + jsonw_int(w, "RDLENGTH", rr->rrs.rdata->len); + if (rr->rrs.rdata->len > 0 ) { + jsonw_hex(w, "RDATAHEX", rr->rrs.rdata->data, rr->rrs.rdata->len); + } + jsonw_end(w); + } + + jsonw_end(w); +} + +static void json_print_edns_generic(jsonw_t *w, const knot_rrset_t *rr) +{ + jsonw_object(w, "EDNS"); + json_dname(w, "NAME", rr->owner); + jsonw_int(w, "CLASS", rr->rclass); + jsonw_int(w, "TTL", rr->ttl); + if (rr->rrs.count > 0) { + jsonw_int(w, "RDLENGTH", rr->rrs.rdata->len); + jsonw_hex(w, "RDATAHEX", rr->rrs.rdata->data, rr->rrs.rdata->len); + } + jsonw_end(w); +} + +static void json_edns_unknown(jsonw_t *w, uint8_t *optdata, uint16_t optype, uint16_t optlen) +{ + char name[9] = { 0 }; + (void)snprintf(name, sizeof(name), "OPT%hu", optype); + jsonw_hex(w, name, optdata, optlen); +} + +static bool all_zero(const uint8_t * const str, const size_t len) +{ + for (const uint8_t *p = str; p != str + len; p++) { + if (*p != 0) { + return false; + } + } + return true; +} + +static bool all_print(const uint8_t * const str, const size_t len) +{ + for (const uint8_t *p = str; p != str + len; p++) { + if (!is_print(*p)) { + return false; + } + } + return true; +} + +static void json_edns_ecs(jsonw_t *w, uint8_t *optdata, uint16_t optlen, + char *tmps, size_t tmps_size) +{ + knot_edns_client_subnet_t ecs = { 0 }; + struct sockaddr_storage addr = { 0 }; + + int ret = knot_edns_client_subnet_parse(&ecs, optdata, optlen); + if (ret == KNOT_EOK) { + ret = knot_edns_client_subnet_get_addr(&addr, &ecs); + } + if (ret == KNOT_EOK) { + ret = sockaddr_tostr(tmps, tmps_size, &addr); + assert(ret > 0); + + (void)snprintf(tmps + ret, tmps_size - ret, + "/%d/%d", ecs.source_len, ecs.scope_len); + + jsonw_str(w, "ECS", tmps); + } else { + jsonw_hex(w, "ECS", optdata, optlen); + } +} + +static void json_edns_opt(jsonw_t *w, uint8_t *optdata, uint16_t optype, uint16_t optlen) +{ + char tmps[KNOT_DNAME_TXT_MAXLEN] = { 0 }; + uint32_t tmpu = 0; + uint16_t tmphu = 0; + + switch (optype) { + case KNOT_EDNS_OPTION_NSID: + jsonw_object(w, "NSID"); + jsonw_hex(w, "HEX", optdata, optlen); + if (all_print(optdata, optlen)) { + jsonw_str_len(w, "TEXT", optdata, optlen, true); + } + jsonw_end(w); + break; + case KNOT_EDNS_OPTION_CLIENT_SUBNET: + json_edns_ecs(w, optdata, optlen, tmps, sizeof(tmps)); + break; + case KNOT_EDNS_OPTION_EXPIRE: + if (optlen == 0) { + jsonw_str(w, "EXPIRE", "NONE"); + } else if (optlen == sizeof(tmpu)) { + tmpu = knot_wire_read_u32(optdata); + (void)snprintf(tmps, sizeof(tmps), "%u", tmpu); + jsonw_str(w, "EXPIRE", tmps); + } else { + json_edns_unknown(w, optdata, optype, optlen); + } + break; + case KNOT_EDNS_OPTION_COOKIE: + jsonw_list(w, "COOKIE"); + tmphu = MIN(optlen, KNOT_EDNS_COOKIE_CLNT_SIZE); + jsonw_hex(w, NULL, optdata, tmphu); + if (optlen > tmphu) { + jsonw_hex(w, NULL, optdata + tmphu, optlen - tmphu); + } + jsonw_end(w); + break; + case KNOT_EDNS_OPTION_TCP_KEEPALIVE: + if (optlen == sizeof(tmphu)) { + tmphu = knot_wire_read_u16(optdata); + jsonw_int(w, "KEEPALIVE", tmphu); + } else { + json_edns_unknown(w, optdata, optype, optlen); + } + break; + case KNOT_EDNS_OPTION_PADDING: + jsonw_object(w, "PADDING"); + jsonw_int(w, "LENGTH", optlen); + if (!all_zero(optdata, optlen)) { + jsonw_hex(w, "HEX", optdata, optlen); + } + jsonw_end(w); + break; + case KNOT_EDNS_OPTION_CHAIN: + if (knot_dname_wire_check(optdata, optdata + optlen, NULL) > 0 && + knot_dname_to_str(tmps, optdata, sizeof(tmps)) != NULL) { + jsonw_str(w, "CHAIN", tmps); + } else { + json_edns_unknown(w, optdata, optype, optlen); + } + break; + case KNOT_EDNS_OPTION_EDE: + if (optlen < sizeof(uint16_t)) { + json_edns_unknown(w, optdata, optype, optlen); + } else { + tmphu = knot_wire_read_u16(optdata); + jsonw_object(w, "EDE"); + jsonw_int(w, "CODE", tmphu); + const knot_lookup_t *item = knot_lookup_by_id(knot_edns_ede_names, tmphu); + if (item != NULL) { + jsonw_str(w, "Purpose", item->name); + } + if (optlen > 2) { + jsonw_str_len(w, "TEXT", optdata + 2, optlen - 2, true); + } + jsonw_end(w); + } + break; + default: + json_edns_unknown(w, optdata, optype, optlen); + break; + } +} + +static void json_print_edns(jsonw_t *w, const knot_pkt_t *pkt) +{ + assert(pkt != NULL && pkt->opt_rr != NULL); + + if (pkt->opt_rr->owner[0] != '\0' || pkt->opt_rr->rrs.count != 1) { + json_print_edns_generic(w, pkt->opt_rr); + return; + } + + char tmp[11] = { 0 }; + + jsonw_object(w, "EDNS"); + uint16_t version = (pkt->opt_rr->ttl & 0x00ff0000) >> 16; + uint16_t flags = pkt->opt_rr->ttl & 0xffff, mask = (1 << 15); + jsonw_int(w, "Version", version); + jsonw_list(w, "FLAGS"); + for (int i = 0; i < 16; i++) { + if ((flags & mask)) { + if ((mask & KNOT_EDNS_DO_MASK)) { + jsonw_str(w, NULL, "DO"); + } else { + (void)snprintf(tmp, sizeof(tmp), "BIT%d", i); + jsonw_str(w, NULL, tmp); + } + } + mask >>= 1; + } + jsonw_end(w); + + const knot_lookup_t *item = knot_lookup_by_id(knot_rcode_names, knot_pkt_ext_rcode(pkt)); + (void)snprintf(tmp, sizeof(tmp), "RCODE%hu", knot_pkt_ext_rcode(pkt)); + jsonw_str(w, "RCODE", item == NULL ? tmp : item->name); + jsonw_int(w, "UDPSIZE", knot_edns_get_payload(pkt->opt_rr)); + + assert(pkt->opt_rr->rrs.count == 1); + wire_ctx_t opts = wire_ctx_init(pkt->opt_rr->rrs.rdata->data, pkt->opt_rr->rrs.rdata->len); + while (wire_ctx_available(&opts) > 0 && opts.error == KNOT_EOK) { + uint16_t optype = wire_ctx_read_u16(&opts); + uint16_t optlen = wire_ctx_read_u16(&opts); + if (wire_ctx_can_read(&opts, optlen) == KNOT_EOK) { + json_edns_opt(w, opts.position, optype, optlen); + wire_ctx_skip(&opts, optlen); + } + } + jsonw_end(w); +} + +static void print_packet_json(jsonw_t *w, const knot_pkt_t *pkt, time_t time) +{ + if (pkt == NULL) { + return; + } + + char str[16]; + + struct tm tm; + char date[64]; + localtime_r(&time, &tm); + strftime(date, sizeof(date), "%Y-%m-%dT%H:%M:%S%z", &tm); + jsonw_str(w, "dateString", date); + jsonw_ulong(w, "dateSeconds", time); + + jsonw_int(w, "msgLength", pkt->size); + + if (pkt->parsed >= KNOT_WIRE_HEADER_SIZE) { + jsonw_int(w, "ID", knot_wire_get_id(pkt->wire)); + jsonw_int(w, "QR", (bool)knot_wire_get_qr(pkt->wire)); + jsonw_int(w, "Opcode", knot_wire_get_opcode(pkt->wire)); + jsonw_int(w, "AA", (bool)knot_wire_get_aa(pkt->wire)); + jsonw_int(w, "TC", (bool)knot_wire_get_tc(pkt->wire)); + jsonw_int(w, "RD", (bool)knot_wire_get_rd(pkt->wire)); + jsonw_int(w, "RA", (bool)knot_wire_get_ra(pkt->wire)); + jsonw_int(w, "AD", (bool)knot_wire_get_ad(pkt->wire)); + jsonw_int(w, "CD", (bool)knot_wire_get_cd(pkt->wire)); + jsonw_int(w, "RCODE", knot_wire_get_rcode(pkt->wire)); + jsonw_int(w, "QDCOUNT", knot_wire_get_qdcount(pkt->wire)); + jsonw_int(w, "ANCOUNT", knot_wire_get_ancount(pkt->wire)); + jsonw_int(w, "NSCOUNT", knot_wire_get_nscount(pkt->wire)); + jsonw_int(w, "ARCOUNT", knot_wire_get_arcount(pkt->wire)); + } + if (knot_wire_get_qdcount(pkt->wire) == 1) { + json_dname(w, "QNAME", knot_pkt_qname(pkt)); + jsonw_int(w, "QTYPE", knot_pkt_qtype(pkt)); + if (knot_rrtype_to_string(knot_pkt_qtype(pkt), str, sizeof(str)) > 0) { + jsonw_str(w, "QTYPEname", str); + } + jsonw_int(w, "QCLASS", knot_pkt_qclass(pkt)); + if (knot_rrclass_to_string(knot_pkt_qclass(pkt), str, sizeof(str)) > 0) { + jsonw_str(w, "QCLASSname", str); + } + } + if (pkt->rrset_count) { + json_print_section(w, "answerRRs", knot_pkt_section(pkt, KNOT_ANSWER)); + json_print_section(w, "authorityRRs", knot_pkt_section(pkt, KNOT_AUTHORITY)); + json_print_section(w, "additionalRRs", knot_pkt_section(pkt, KNOT_ADDITIONAL)); + } + if (knot_pkt_has_edns(pkt)) { + json_print_edns(w, pkt); + } + if (pkt->parsed < pkt->size) { + jsonw_hex(w, "messageOctetsHEX", pkt->wire, pkt->size); + } +} + +knot_pkt_t *create_empty_packet(const uint16_t max_size) +{ + // Create packet skeleton. + knot_pkt_t *packet = knot_pkt_new(NULL, max_size, NULL); + if (packet == NULL) { + DBG_NULL; + return NULL; + } + + // Set random sequence id. + knot_wire_set_id(packet->wire, dnssec_random_uint16_t()); + + return packet; +} + +jsonw_t *print_header_xfr_json(const knot_pkt_t *query, + const time_t exec_time, + const style_t *style) +{ + if (style == NULL) { + DBG_NULL; + return NULL; + } + + jsonw_t *w = jsonw_new(stdout, JSON_INDENT); + if (w == NULL) { + return NULL; + } + + if (style->show_query) { + jsonw_object(w, NULL); + jsonw_object(w, "queryMessage"); + print_packet_json(w, query, exec_time); + jsonw_end(w); + jsonw_list(w, "responseMessage"); + } else { + jsonw_list(w, NULL); + } + + return w; +} + +void print_data_xfr_json(jsonw_t *w, + const knot_pkt_t *reply, + const time_t exec_time) +{ + if (w == NULL) { + DBG_NULL; + return; + } + + jsonw_object(w, NULL); + print_packet_json(w, reply, exec_time); + jsonw_end(w); +} + +void print_footer_xfr_json(jsonw_t **w, + const style_t *style) +{ + if (w == NULL || style == NULL) { + DBG_NULL; + return; + } + + jsonw_end(*w); // list (responseMessage) + if (style->show_query) { + jsonw_end(*w); // object + } + + jsonw_free(w); + *w = NULL; +} + +void print_header_xfr(const knot_pkt_t *packet, const style_t *style) +{ + if (style == NULL) { + DBG_NULL; + return; + } + + char xfr[16] = "AXFR"; + + switch (knot_pkt_qtype(packet)) { + case KNOT_RRTYPE_AXFR: + break; + case KNOT_RRTYPE_IXFR: + xfr[0] = 'I'; + break; + default: + return; + } + + if (style->show_header) { + char *owner = knot_dname_to_str_alloc(knot_pkt_qname(packet)); + if (style->style.ascii_to_idn != NULL) { + style->style.ascii_to_idn(&owner); + } + if (owner != NULL) { + printf(";; %s for %s\n", xfr, owner); + free(owner); + } + } +} + +void print_data_xfr(const knot_pkt_t *packet, + const style_t *style) +{ + if (packet == NULL || style == NULL) { + DBG_NULL; + return; + } + + const knot_pktsection_t *answers = knot_pkt_section(packet, KNOT_ANSWER); + + switch (style->format) { + case FORMAT_DIG: + print_section_dig(knot_pkt_rr(answers, 0), answers->count, style); + break; + case FORMAT_HOST: + print_section_host(knot_pkt_rr(answers, 0), answers->count, style); + break; + case FORMAT_FULL: + print_section_full(knot_pkt_rr(answers, 0), answers->count, style, true); + + // Print TSIG record. + if (style->show_tsig && knot_pkt_has_tsig(packet)) { + print_section_full(packet->tsig_rr, 1, style, false); + } + break; + default: + break; + } +} + +void print_footer_xfr(const size_t total_len, + const size_t msg_count, + const size_t rr_count, + const net_t *net, + const float elapsed, + const time_t exec_time, + const style_t *style) +{ + if (style == NULL) { + DBG_NULL; + return; + } + + if (style->show_footer) { + print_footer(total_len, msg_count, rr_count, net, elapsed, + exec_time, true); + } +} + +void print_packets_json(const knot_pkt_t *query, + const knot_pkt_t *reply, + const net_t *net, + const time_t exec_time, + const style_t *style) +{ + if (style == NULL) { + DBG_NULL; + return; + } + + jsonw_t *w = jsonw_new(stdout, JSON_INDENT); + if (w == NULL) { + return; + } + jsonw_object(w, NULL); + + if (style->show_query) { + jsonw_object(w, "queryMessage"); + print_packet_json(w, query, exec_time); + jsonw_end(w); + jsonw_object(w, "responseMessage"); + } + + print_packet_json(w, reply, exec_time); + + if (style->show_query) { + jsonw_end(w); + } + + jsonw_end(w); + jsonw_free(&w); +} + +void print_packet(const knot_pkt_t *packet, + const net_t *net, + const size_t size, + const float elapsed, + const time_t exec_time, + const bool incoming, + const style_t *style) +{ + if (packet == NULL || style == NULL) { + DBG_NULL; + return; + } + + const knot_pktsection_t *answers = knot_pkt_section(packet, + KNOT_ANSWER); + const knot_pktsection_t *authority = knot_pkt_section(packet, + KNOT_AUTHORITY); + const knot_pktsection_t *additional = knot_pkt_section(packet, + KNOT_ADDITIONAL); + + uint16_t qdcount = packet->parsed >= KNOT_WIRE_OFFSET_ANCOUNT ? + knot_wire_get_qdcount(packet->wire) : 0; + uint16_t ancount = packet->sections[KNOT_ANSWER].count; + uint16_t nscount = packet->sections[KNOT_AUTHORITY].count; + uint16_t arcount = packet->sections[KNOT_ADDITIONAL].count; + + // Disable additionals printing if there are no other records. + // OPT record may be placed anywhere within additionals! + if (knot_pkt_has_edns(packet) && arcount == 1) { + arcount = 0; + } + + // Print packet information header. + if (style->show_header) { + if (net != NULL) { +#ifdef ENABLE_QUIC + if (net->quic.params.enable) { + print_quic(&net->quic); + } else +#endif + { + print_tls(&net->tls); +#ifdef LIBNGHTTP2 + print_https(&net->https); +#endif + } + } + print_header(packet, style); + } + + // Print EDNS section. + if (style->show_edns && knot_pkt_has_edns(packet)) { + printf("%s", style->show_section ? "\n;; EDNS PSEUDOSECTION:\n" : ";;"); + print_section_opt(packet, style); + } + + // Print DNS sections. + switch (style->format) { + case FORMAT_DIG: + if (ancount > 0) { + print_section_dig(knot_pkt_rr(answers, 0), ancount, style); + } + break; + case FORMAT_HOST: + if (ancount > 0) { + print_section_host(knot_pkt_rr(answers, 0), ancount, style); + } else { + print_error_host(packet, style); + } + break; + case FORMAT_NSUPDATE: + if (style->show_question && qdcount > 0) { + printf("%s", style->show_section ? "\n;; ZONE SECTION:\n;; " : ";;"); + print_section_question(knot_pkt_qname(packet), + knot_pkt_qclass(packet), + knot_pkt_qtype(packet), + style); + } + + if (style->show_answer && ancount > 0) { + printf("%s", style->show_section ? "\n;; PREREQUISITE SECTION:\n" : ""); + print_section_full(knot_pkt_rr(answers, 0), ancount, style, true); + } + + if (style->show_authority && nscount > 0) { + printf("%s", style->show_section ? "\n;; UPDATE SECTION:\n" : ""); + print_section_full(knot_pkt_rr(authority, 0), nscount, style, true); + } + + if (style->show_additional && arcount > 0) { + printf("%s", style->show_section ? "\n;; ADDITIONAL DATA:\n" : ""); + print_section_full(knot_pkt_rr(additional, 0), arcount, style, true); + } + break; + case FORMAT_FULL: + if (style->show_question && qdcount > 0) { + printf("%s", style->show_section ? "\n;; QUESTION SECTION:\n;; " : ";;"); + print_section_question(knot_pkt_wire_qname(packet), + knot_pkt_qclass(packet), + knot_pkt_qtype(packet), + style); + } + + if (style->show_answer && ancount > 0) { + printf("%s", style->show_section ? "\n;; ANSWER SECTION:\n" : ""); + print_section_full(knot_pkt_rr(answers, 0), ancount, style, true); + } + + if (style->show_authority && nscount > 0) { + printf("%s", style->show_section ? "\n;; AUTHORITY SECTION:\n" : ""); + print_section_full(knot_pkt_rr(authority, 0), nscount, style, true); + } + + if (style->show_additional && arcount > 0) { + printf("%s", style->show_section ? "\n;; ADDITIONAL SECTION:\n" : ""); + print_section_full(knot_pkt_rr(additional, 0), arcount, style, true); + } + break; + default: + break; + } + + // Print TSIG section. + if (style->show_tsig && knot_pkt_has_tsig(packet)) { + printf("%s", style->show_section ? "\n;; TSIG PSEUDOSECTION:\n" : ""); + print_section_full(packet->tsig_rr, 1, style, false); + } + + // Print packet statistics. + if (style->show_footer) { + printf("\n"); + print_footer(size, 0, 0, net, elapsed, exec_time, incoming); + } +} |