summaryrefslogtreecommitdiffstats
path: root/src/utils/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils/common')
-rw-r--r--src/utils/common/cert.c61
-rw-r--r--src/utils/common/cert.h36
-rw-r--r--src/utils/common/exec.c736
-rw-r--r--src/utils/common/exec.h87
-rw-r--r--src/utils/common/hex.c82
-rw-r--r--src/utils/common/hex.h31
-rw-r--r--src/utils/common/https.c572
-rw-r--r--src/utils/common/https.h149
-rw-r--r--src/utils/common/lookup.c279
-rw-r--r--src/utils/common/lookup.h112
-rw-r--r--src/utils/common/msg.c40
-rw-r--r--src/utils/common/msg.h38
-rw-r--r--src/utils/common/netio.c673
-rw-r--r--src/utils/common/netio.h226
-rw-r--r--src/utils/common/params.c347
-rw-r--r--src/utils/common/params.h165
-rw-r--r--src/utils/common/resolv.c208
-rw-r--r--src/utils/common/resolv.h24
-rw-r--r--src/utils/common/sign.c109
-rw-r--r--src/utils/common/sign.h63
-rw-r--r--src/utils/common/tls.c681
-rw-r--r--src/utils/common/tls.h70
-rw-r--r--src/utils/common/token.c115
-rw-r--r--src/utils/common/token.h65
24 files changed, 4969 insertions, 0 deletions
diff --git a/src/utils/common/cert.c b/src/utils/common/cert.c
new file mode 100644
index 0000000..1b76b23
--- /dev/null
+++ b/src/utils/common/cert.c
@@ -0,0 +1,61 @@
+/* Copyright (C) 2016 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 <gnutls/abstract.h>
+#include <gnutls/crypto.h>
+
+#include "utils/common/cert.h"
+#include "libknot/error.h"
+
+static int spki_hash(gnutls_x509_crt_t cert, gnutls_digest_algorithm_t alg,
+ uint8_t *hash, size_t size)
+{
+ if (!cert || !hash || gnutls_hash_get_len(alg) != size) {
+ return KNOT_EINVAL;
+ }
+
+ gnutls_pubkey_t key = { 0 };
+ if (gnutls_pubkey_init(&key) != GNUTLS_E_SUCCESS) {
+ return KNOT_ENOMEM;
+ }
+
+ if (gnutls_pubkey_import_x509(key, cert, 0) != GNUTLS_E_SUCCESS) {
+ gnutls_pubkey_deinit(key);
+ return KNOT_ERROR;
+ }
+
+ gnutls_datum_t der = { 0 };
+ if (gnutls_pubkey_export2(key, GNUTLS_X509_FMT_DER, &der) != GNUTLS_E_SUCCESS) {
+ gnutls_pubkey_deinit(key);
+ return KNOT_ERROR;
+ }
+
+ int ret = gnutls_hash_fast(alg, der.data, der.size, hash);
+
+ gnutls_free(der.data);
+ gnutls_pubkey_deinit(key);
+
+ if (ret != GNUTLS_E_SUCCESS) {
+ return KNOT_ERROR;
+ }
+
+ return KNOT_EOK;
+}
+
+int cert_get_pin(gnutls_x509_crt_t cert, uint8_t *pin, size_t size)
+{
+ return spki_hash(cert, GNUTLS_DIG_SHA256, pin, size);
+}
diff --git a/src/utils/common/cert.h b/src/utils/common/cert.h
new file mode 100644
index 0000000..51e3d53
--- /dev/null
+++ b/src/utils/common/cert.h
@@ -0,0 +1,36 @@
+/* Copyright (C) 2016 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/>.
+ */
+
+#pragma once
+
+#include <gnutls/x509.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#define CERT_PIN_LEN 32
+
+/*!
+ * \brief Get certificate pin value.
+ *
+ * The pin is a SHA-256 hash of the X.509 SubjectPublicKeyInfo.
+ *
+ * \param[in] crt Certificate.
+ * \param[out] pin Pin.
+ * \param[in] size Length of the pin, must be CERT_PIN_LEN.
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int cert_get_pin(gnutls_x509_crt_t crt, uint8_t *pin, size_t size);
diff --git a/src/utils/common/exec.c b/src/utils/common/exec.c
new file mode 100644
index 0000000..0791614
--- /dev/null
+++ b/src/utils/common/exec.c
@@ -0,0 +1,736 @@
+/* Copyright (C) 2020 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/sockaddr.h"
+#include "contrib/openbsd/strlcat.h"
+#include "contrib/ucw/lists.h"
+#include "contrib/wire_ctx.h"
+
+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)
+{
+ char flags[64] = "";
+ char unknown_rcode[64] = "";
+ char unknown_opcode[64] = "";
+
+ const char *rcode_str = NULL;
+ const char *opcode_str = NULL;
+
+ // 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);
+ }
+
+ uint16_t id = knot_wire_get_id(packet->wire);
+ uint16_t qdcount = knot_wire_get_qdcount(packet->wire);
+ uint16_t ancount = knot_wire_get_ancount(packet->wire);
+ uint16_t nscount = knot_wire_get_nscount(packet->wire);
+ uint16_t 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 = be16toh(*(uint16_t *)data);
+ const char *strerr = knot_edns_ede_strerr(errcode);
+ if (len > 2) {
+ printf("%hu (%s): '%.*s'", errcode, strerr, (int)(len - 2), data + 2);
+ } else {
+ printf("%hu (%s)", errcode, strerr);
+ }
+}
+
+static void print_section_opt(const knot_pkt_t *packet, const style_t *style)
+{
+ 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\n");
+ 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;
+ 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\n");
+ }
+}
+
+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\n");
+ }
+
+ 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\n");
+ 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\n");
+ break;
+ }
+
+ char *newbuf = realloc(buf, buflen);
+ if (newbuf == NULL) {
+ WARN("can't print whole section\n");
+ 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\n");
+ break;
+ }
+
+ char *newbuf = realloc(buf, buflen);
+ if (newbuf == NULL) {
+ WARN("can't print whole section\n");
+ 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);
+}
+
+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;
+}
+
+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_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 = knot_wire_get_qdcount(packet->wire);
+ uint16_t ancount = knot_wire_get_ancount(packet->wire);
+ uint16_t nscount = knot_wire_get_nscount(packet->wire);
+ uint16_t arcount = knot_wire_get_arcount(packet->wire);
+
+ // 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) {
+ 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_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);
+ }
+}
diff --git a/src/utils/common/exec.h b/src/utils/common/exec.h
new file mode 100644
index 0000000..2940be4
--- /dev/null
+++ b/src/utils/common/exec.h
@@ -0,0 +1,87 @@
+/* Copyright (C) 2018 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/>.
+ */
+
+#pragma once
+
+#include <time.h>
+
+#include "utils/common/netio.h"
+#include "utils/common/params.h"
+#include "libknot/libknot.h"
+
+/*!
+ * \brief Allocates empty packet and sets packet size and random id.
+ *
+ * \param max_size Maximal packet size.
+ *
+ * \retval packet if success.
+ * \retval NULL if error.
+ */
+knot_pkt_t *create_empty_packet(const uint16_t max_size);
+
+/*!
+ * \brief Prints information header for transfer.
+ *
+ * \param packet Parsed packet.
+ * \param style Style of the output.
+ */
+void print_header_xfr(const knot_pkt_t *packet, const style_t *style);
+
+/*!
+ * \brief Prints answer section for 1 transfer message.
+ *
+ * \param packet Response packet.
+ * \param style Style of the output.
+ */
+void print_data_xfr(const knot_pkt_t *packet, const style_t *style);
+
+/*!
+ * \brief Prints trailing statistics for transfer.
+ *
+ * \param total_len Total reply size (all messages).
+ * \param msg_count Number of messages.
+ * \param rr_count Total number of answer records.
+ * \param net Connection information.
+ * \param elapsed Total elapsed time.
+ * \param exec_time Time of the packet creation.
+ * \param style Style of the otput.
+ */
+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);
+
+/*!
+ * \brief Prints one response packet.
+ *
+ * \param packet Response packet.
+ * \param net Connection information.
+ * \param size Original packet wire size.
+ * \param elapsed Total elapsed time.
+ * \param exec_time Time of the packet creation.
+ * \param incoming Indicates if the packet is input.
+ * \param style Style of the otput.
+ */
+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);
diff --git a/src/utils/common/hex.c b/src/utils/common/hex.c
new file mode 100644
index 0000000..9683446
--- /dev/null
+++ b/src/utils/common/hex.c
@@ -0,0 +1,82 @@
+/* Copyright (C) 2017 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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libknot/libknot.h"
+#include "contrib/ctype.h"
+#include "contrib/tolower.h"
+
+/*!
+ * \brief Convert HEX char to byte.
+ * \note Expects valid lowercase letters.
+ */
+static uint8_t hex_to_num(int c)
+{
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else {
+ return c - 'a' + 10;
+ }
+}
+
+/*!
+ * \brief Convert string encoded in hex to bytes.
+ */
+int hex_decode(const char *input, uint8_t **output, size_t *output_size)
+{
+ if (!input || input[0] == '\0' || !output || !output_size) {
+ return KNOT_EINVAL;
+ }
+
+ // input validation (length and content)
+
+ size_t input_size = strlen(input);
+ if (input_size % 2 != 0) {
+ return KNOT_EMALF;
+ }
+
+ for (size_t i = 0; i < input_size; i++) {
+ if (!is_xdigit(input[i])) {
+ return KNOT_EMALF;
+ }
+ }
+
+ // output allocation
+
+ size_t result_size = input_size / 2;
+ assert(result_size > 0);
+ uint8_t *result = malloc(result_size);
+ if (!result) {
+ return KNOT_ENOMEM;
+ }
+
+ // conversion
+
+ for (size_t i = 0; i < result_size; i++) {
+ int high_nib = knot_tolower(input[2 * i]);
+ int low_nib = knot_tolower(input[2 * i + 1]);
+
+ result[i] = hex_to_num(high_nib) << 4 | hex_to_num(low_nib);
+ }
+
+ *output = result;
+ *output_size = result_size;
+
+ return KNOT_EOK;
+}
diff --git a/src/utils/common/hex.h b/src/utils/common/hex.h
new file mode 100644
index 0000000..efe81be
--- /dev/null
+++ b/src/utils/common/hex.h
@@ -0,0 +1,31 @@
+/* Copyright (C) 2018 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/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+
+/*!
+ * \brief Convert string encoded in hex to bytes.
+ *
+ * \param input Hex encoded input string.
+ * \param output Decoded bytes.
+ * \param output_size Size of the output.
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int hex_decode(const char *input, uint8_t **output, size_t *output_size);
diff --git a/src/utils/common/https.c b/src/utils/common/https.c
new file mode 100644
index 0000000..f5d7bd0
--- /dev/null
+++ b/src/utils/common/https.c
@@ -0,0 +1,572 @@
+/* Copyright (C) 2021 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 <assert.h>
+#include <arpa/inet.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "contrib/base64url.h"
+#include "contrib/url-parser/url_parser.h"
+#include "libknot/errcode.h"
+#include "utils/common/https.h"
+#include "utils/common/msg.h"
+
+int https_params_copy(https_params_t *dst, const https_params_t *src)
+{
+ if (dst == NULL || src == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ dst->enable = src->enable;
+ dst->method = src->method;
+ if (src->path != NULL) {
+ dst->path = strdup(src->path);
+ if (dst->path == NULL) {
+ return KNOT_ENOMEM;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+void https_params_clean(https_params_t *params)
+{
+ if (params == NULL) {
+ return;
+ }
+
+ params->enable = false;
+ params->method = GET;
+ free(params->path);
+ params->path = NULL;
+}
+
+#ifdef LIBNGHTTP2
+
+#define HTTP_STATUS_SUCCESS 200
+#define HTTPS_MAX_STREAMS 16
+#define HTTPS_AUTHORITY_LEN (INET6_ADDRSTRLEN + 2)
+
+#define MAKE_NV(K, KS, V, VS) \
+ { (uint8_t *)K, (uint8_t *)V, KS, VS, NGHTTP2_NV_FLAG_NONE }
+
+#define MAKE_STATIC_NV(K, V) \
+ MAKE_NV(K, sizeof(K) - 1, V, sizeof(V) - 1)
+
+static const char default_path[] = "/dns-query";
+static const char default_query[] = "?dns=";
+
+static const gnutls_datum_t https_protocols[] = {
+ { (unsigned char *)"h2", 2 }
+};
+
+static const nghttp2_settings_entry settings[] = {
+ { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTPS_MAX_STREAMS }
+};
+
+static bool https_status_is_redirect(unsigned long status)
+{
+ switch (status) {
+ case 301UL:
+ case 302UL:
+ case 307UL:
+ case 308UL:
+ return true;
+ }
+ return false;
+}
+
+static ssize_t https_send_callback(nghttp2_session *session, const uint8_t *data,
+ size_t length, int flags, void *user_data)
+{
+ assert(user_data);
+
+ gnutls_session_t tls_session = ((https_ctx_t *)user_data)->tls->session;
+ ssize_t len = 0;
+
+ gnutls_record_cork(tls_session);
+ if ((len = gnutls_record_send(tls_session, data, length)) <= 0) {
+ WARN("TLS, failed to send\n");
+ return KNOT_NET_ESEND;
+ }
+ return len;
+}
+
+static int https_on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data)
+{
+ assert(user_data);
+
+ gnutls_session_t tls_session = ((https_ctx_t *)user_data)->tls->session;
+ while (gnutls_record_check_corked(tls_session) > 0) {
+ int ret = gnutls_record_uncork(tls_session, 0);
+ if (ret < 0 && gnutls_error_is_fatal(ret) != 0) {
+ WARN("TLS, failed to send (%s)\n", gnutls_strerror(ret));
+ return KNOT_NET_ESEND;
+ }
+ }
+ return KNOT_EOK;
+}
+
+static ssize_t https_recv_callback(nghttp2_session *session, uint8_t *data, size_t length,
+ int flags, void *user_data)
+{
+ assert(user_data);
+
+ https_ctx_t *ctx = (https_ctx_t *)user_data;
+ struct pollfd pfd = {
+ .fd = ctx->tls->sockfd,
+ .events = POLLIN,
+ .revents = 0,
+ };
+
+ ssize_t ret = 0;
+ while ((ret = gnutls_record_recv(ctx->tls->session, data, length)) <= 0) {
+ if (!ctx->read) { //Unblock `nghttp2_session_recv(nghttp2_session)`
+ ctx->read = true;
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+ if (ret == 0) {
+ WARN("TLS, peer has closed the connection\n");
+ return KNOT_NET_ERECV;
+ } else if (gnutls_error_is_fatal(ret)) {
+ WARN("TLS, failed to receive reply (%s)\n",
+ gnutls_strerror(ret));
+ return KNOT_NET_ERECV;
+ } else if (poll(&pfd, 1, 1000 * ctx->tls->wait) != 1) {
+ WARN("TLS, peer took too long to respond\n");
+ return KNOT_ETIMEOUT;
+ }
+ }
+
+ return ret;
+}
+
+static int https_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id,
+ const uint8_t *data, size_t len, void *user_data)
+{
+ assert(user_data);
+
+ https_ctx_t *ctx = (https_ctx_t *)user_data;
+ if (ctx->stream == stream_id) {
+ memcpy(ctx->recv_buf, data, len);
+ ctx->recv_buflen = len;
+ ctx->read = false;
+ ctx->stream = -1;
+ }
+ return KNOT_EOK;
+}
+
+static int https_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen,
+ uint8_t flags, void *user_data)
+{
+ assert(user_data);
+ https_ctx_t *ctx = (https_ctx_t *)user_data;
+
+ if (!strncasecmp(":status", (const char *)name, namelen)) {
+ char *end;
+ long status;
+ status = strtoul((const char *)value, &end, 10);
+ if (value != (const uint8_t *)end) {
+ ctx->status = status;
+ }
+ }
+ else if (!strncasecmp("location", (const char *)name, namelen) &&
+ https_status_is_redirect(ctx->status)) {
+ struct http_parser_url redirect_url;
+ http_parser_parse_url((const char *)value, valuelen, 0, &redirect_url);
+
+ bool r_auth = redirect_url.field_set & (1 << UF_HOST);
+ bool r_path = redirect_url.field_set & (1 << UF_PATH);
+ char *old_auth = ctx->authority, *old_path = ctx->path;
+
+ if (r_auth) {
+ ctx->authority = strndup((const char *)(value + redirect_url.field_data[UF_HOST].off),
+ redirect_url.field_data[UF_HOST].len);
+ }
+ if (r_path) {
+ ctx->path = strndup((const char *)(value + redirect_url.field_data[UF_PATH].off),
+ redirect_url.field_data[UF_PATH].len);
+ }
+ WARN("HTTP redirect (%s%s)->(%s%s)\n", old_auth, old_path, ctx->authority, ctx->path);
+ if (r_auth) {
+ free(old_auth);
+ }
+ if (r_path) {
+ free(old_path);
+ }
+ return https_send_dns_query(ctx, ctx->send_buf, ctx->send_buflen);
+ }
+ return KNOT_EOK;
+}
+
+int https_ctx_init(https_ctx_t *ctx, tls_ctx_t *tls_ctx, const https_params_t *params)
+{
+ if (ctx == NULL || tls_ctx == NULL || params == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (ctx->session != NULL) { // Already initialized before
+ return KNOT_EINVAL;
+ }
+ if (!params->enable) {
+ return KNOT_EINVAL;
+ }
+
+ nghttp2_session_callbacks *callbacks;
+ nghttp2_session_callbacks_new(&callbacks);
+ nghttp2_session_callbacks_set_send_callback(callbacks, https_send_callback);
+ nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, https_on_frame_send_callback);
+ nghttp2_session_callbacks_set_recv_callback(callbacks, https_recv_callback);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, https_on_data_chunk_recv_callback);
+ nghttp2_session_callbacks_set_on_header_callback(callbacks, https_on_header_callback);
+
+ int ret = nghttp2_session_client_new(&(ctx->session), callbacks, ctx);
+ if (ret != 0) {
+ return KNOT_EINVAL;
+ }
+
+ nghttp2_session_callbacks_del(callbacks);
+
+ if (pthread_mutex_init(&ctx->recv_mx, NULL) != 0) {
+ return KNOT_EINVAL;
+ }
+
+ ctx->tls = tls_ctx;
+ ctx->params = *params;
+ ctx->authority = (tls_ctx->params->hostname) ? strdup(tls_ctx->params->hostname) : NULL;
+ ctx->path = strdup((ctx->params.path) ? ctx->params.path : (char *)default_path);
+ ctx->read = true;
+
+ return KNOT_EOK;
+}
+
+static int sockaddr_to_authority(char *buf, const size_t buf_len, const struct sockaddr_storage *ss)
+{
+ if (buf == NULL || ss == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ const char *out = NULL;
+
+ /* Convert IPv6 network address string. */
+ if (ss->ss_family == AF_INET6) {
+ if (buf_len < HTTPS_AUTHORITY_LEN) {
+ return KNOT_EINVAL;
+ }
+
+ const struct sockaddr_in6 *s = (const struct sockaddr_in6 *)ss;
+ buf[0] = '[';
+
+ out = inet_ntop(ss->ss_family, &s->sin6_addr, buf + 1, buf_len - 1);
+ if (out == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ buf += strlen(buf);
+ buf[0] = ']';
+ buf[1] = '\0';
+ /* Convert IPv4 network address string. */
+ } else if (ss->ss_family == AF_INET) {
+ if (buf_len < INET_ADDRSTRLEN) {
+ return KNOT_EINVAL;
+ }
+
+ const struct sockaddr_in *s = (const struct sockaddr_in *)ss;
+
+ out = inet_ntop(ss->ss_family, &s->sin_addr, buf, buf_len);
+ if (out == NULL) {
+ return KNOT_EINVAL;
+ }
+ /* Unknown network address family. */
+ } else {
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+int https_ctx_connect(https_ctx_t *ctx, const int sockfd, struct sockaddr_storage *address,
+ const char *remote)
+{
+ if (ctx == NULL || address == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Create TLS connection
+ int ret = gnutls_init(&ctx->tls->session, GNUTLS_CLIENT | GNUTLS_NONBLOCK);
+ if (ret != GNUTLS_E_SUCCESS) {
+ return KNOT_NET_ECONNECT;
+ }
+
+ ret = gnutls_set_default_priority(ctx->tls->session);
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_deinit(ctx->tls->session);
+ return KNOT_NET_ECONNECT;
+ }
+
+ ret = gnutls_credentials_set(ctx->tls->session, GNUTLS_CRD_CERTIFICATE,
+ ctx->tls->credentials);
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_deinit(ctx->tls->session);
+ return KNOT_NET_ECONNECT;
+ }
+
+ if (remote != NULL) {
+ ret = gnutls_server_name_set(ctx->tls->session, GNUTLS_NAME_DNS, remote,
+ strlen(remote));
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_deinit(ctx->tls->session);
+ return KNOT_NET_ECONNECT;
+ }
+ }
+
+ gnutls_session_set_ptr(ctx->tls->session, ctx->tls);
+ gnutls_transport_set_int(ctx->tls->session, sockfd);
+ gnutls_handshake_set_timeout(ctx->tls->session, 1000 * ctx->tls->wait);
+
+ ret = gnutls_alpn_set_protocols(ctx->tls->session, https_protocols,
+ sizeof(https_protocols) / sizeof(*https_protocols), 0);
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_deinit(ctx->tls->session);
+ return KNOT_NET_ECONNECT;
+ }
+
+ // Initialize poll descriptor structure.
+ struct pollfd pfd = {
+ .fd = sockfd,
+ .events = POLLIN,
+ .revents = 0,
+ };
+
+ // Perform the TLS handshake
+ do {
+ ret = gnutls_handshake(ctx->tls->session);
+ if (ret != GNUTLS_E_SUCCESS && gnutls_error_is_fatal(ret) == 0) {
+ if (poll(&pfd, 1, 1000 * ctx->tls->wait) != 1) {
+ WARN("TLS, peer took too long to respond\n");
+ gnutls_deinit(ctx->tls->session);
+ return KNOT_NET_ETIMEOUT;
+ }
+ }
+ } while (ret != GNUTLS_E_SUCCESS && gnutls_error_is_fatal(ret) == 0);
+ if (ret != GNUTLS_E_SUCCESS) {
+ WARN("TLS, handshake failed (%s)\n", gnutls_strerror(ret));
+ tls_ctx_close(ctx->tls);
+ return KNOT_NET_ESOCKET;
+ }
+
+ // Save the socket descriptor.
+ ctx->tls->sockfd = sockfd;
+
+ // Perform HTTP handshake
+ ret = nghttp2_submit_settings(ctx->session, NGHTTP2_FLAG_NONE, settings,
+ sizeof(settings) / sizeof(*settings));
+ if (ret != 0) {
+ return KNOT_NET_ESOCKET;
+ }
+ ret = nghttp2_session_send(ctx->session);
+ if (ret != 0) {
+ return KNOT_NET_ESOCKET;
+ }
+
+ // Save authority server
+ if (ctx->authority == NULL) {
+ if (remote != NULL) {
+ ctx->authority = strdup(remote);
+ } else {
+ ctx->authority = (char*)calloc(HTTPS_AUTHORITY_LEN, sizeof(char));
+ ret = sockaddr_to_authority(ctx->authority, HTTPS_AUTHORITY_LEN, address);
+ if (ret != KNOT_EOK) {
+ free(ctx->authority);
+ ctx->authority = NULL;
+ return KNOT_EINVAL;
+ }
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int https_send_dns_query_get(https_ctx_t *ctx)
+{
+ const size_t dns_query_len = strlen(ctx->path) +
+ sizeof(default_query) +
+ (ctx->send_buflen * 4) / 3 + 3;
+ char dns_query[dns_query_len];
+ strncpy(dns_query, ctx->path, dns_query_len);
+ strncat(dns_query, default_query, dns_query_len);
+
+ size_t tmp_strlen = strlen(dns_query);
+ int32_t ret = knot_base64url_encode(ctx->send_buf, ctx->send_buflen,
+ (uint8_t *)(dns_query + tmp_strlen), dns_query_len - tmp_strlen - 1);
+ if (ret < 0) {
+ return KNOT_EINVAL;
+ }
+
+ nghttp2_nv hdrs[] = {
+ MAKE_STATIC_NV(":method", "GET"),
+ MAKE_STATIC_NV(":scheme", "https"),
+ MAKE_NV(":authority", 10, ctx->authority, strlen(ctx->authority)),
+ MAKE_NV(":path", 5, dns_query, tmp_strlen + ret),
+ MAKE_STATIC_NV("accept", "application/dns-message"),
+ };
+
+ ctx->stream = nghttp2_submit_request(ctx->session, NULL, hdrs,
+ sizeof(hdrs) / sizeof(*hdrs),
+ NULL, NULL);
+ if (ctx->stream < 0) {
+ return KNOT_NET_ESEND;
+ }
+ ret = nghttp2_session_send(ctx->session);
+ if (ret != 0) {
+ return KNOT_NET_ESEND;
+ }
+
+ return KNOT_EOK;
+}
+
+static ssize_t https_send_data_callback(nghttp2_session *session, int32_t stream_id,
+ uint8_t *buf, size_t length, uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data)
+{
+ https_data_provider_t *buffer = source->ptr;
+ ssize_t sent = (length < buffer->buf_len) ? length : buffer->buf_len;
+
+ memcpy(buf, buffer->buf, sent);
+ buffer->buf += sent;
+ buffer->buf_len -= sent;
+ if (!buffer->buf_len) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+
+ return sent;
+}
+
+static int https_send_dns_query_post(https_ctx_t *ctx)
+{
+ // size of number in text form (base 10)
+ char content_length[sizeof(size_t) * 3 + 1]; // limit for x->inf: log10(2^(8*sizeof(x))-1)/sizeof(x) = 2,408239965 -> 3
+ int content_length_len = sprintf(content_length, "%zu", ctx->send_buflen);
+
+ nghttp2_nv hdrs[] = {
+ MAKE_STATIC_NV(":method", "POST"),
+ MAKE_STATIC_NV(":scheme", "https"),
+ MAKE_NV(":authority", 10, ctx->authority, strlen(ctx->authority)),
+ MAKE_NV(":path", 5, ctx->path, strlen(ctx->path)),
+ MAKE_STATIC_NV("accept", "application/dns-message"),
+ MAKE_STATIC_NV("content-type", "application/dns-message"),
+ MAKE_NV("content-length", 14, content_length, content_length_len)
+ };
+
+ https_data_provider_t data = {
+ .buf = ctx->send_buf,
+ .buf_len = ctx->send_buflen
+ };
+
+ nghttp2_data_provider data_provider = {
+ .source.ptr = &data,
+ .read_callback = https_send_data_callback
+ };
+
+ ctx->stream = nghttp2_submit_request(ctx->session, NULL, hdrs,
+ sizeof(hdrs) / sizeof(nghttp2_nv),
+ &data_provider, NULL);
+ if (ctx->stream < 0) {
+ return KNOT_NET_ESEND;
+ }
+ int ret = nghttp2_session_send(ctx->session);
+ if (ret != 0) {
+ return KNOT_NET_ESEND;
+ }
+
+ return KNOT_EOK;
+}
+
+int https_send_dns_query(https_ctx_t *ctx, const uint8_t *buf, const size_t buf_len)
+{
+ if (ctx == NULL || buf == NULL || buf_len == 0) {
+ return KNOT_EINVAL;
+ }
+
+ ctx->send_buf = buf;
+ ctx->send_buflen = buf_len;
+
+ assert(ctx->params.method == POST || ctx->params.method == GET);
+
+ if (ctx->params.method == POST) {
+ return https_send_dns_query_post(ctx);
+ } else {
+ return https_send_dns_query_get(ctx);
+ }
+}
+
+int https_recv_dns_response(https_ctx_t *ctx, uint8_t *buf, const size_t buf_len)
+{
+ if (ctx == NULL || buf == NULL || buf_len == 0) {
+ return KNOT_EINVAL;
+ }
+
+ pthread_mutex_lock(&ctx->recv_mx);
+ ctx->recv_buf = buf;
+ ctx->recv_buflen = buf_len;
+
+ int ret = nghttp2_session_recv(ctx->session);
+ if (ret != 0) {
+ pthread_mutex_unlock(&ctx->recv_mx);
+ return KNOT_NET_ERECV;
+ }
+
+ ctx->recv_buf = NULL;
+
+ pthread_mutex_unlock(&ctx->recv_mx);
+
+ if (ctx->status != HTTP_STATUS_SUCCESS) {
+ print_https(ctx);
+ return KNOT_NET_ERECV;
+ }
+
+ return ctx->recv_buflen;
+}
+
+void https_ctx_deinit(https_ctx_t *ctx)
+{
+ if (ctx == NULL) {
+ return;
+ }
+
+ nghttp2_session_del(ctx->session);
+ pthread_mutex_destroy(&ctx->recv_mx);
+ free(ctx->path);
+ ctx->path = NULL;
+ free(ctx->authority);
+ ctx->authority = NULL;
+}
+
+void print_https(const https_ctx_t *ctx)
+{
+ if (!ctx || !ctx->authority || !ctx->path) {
+ return;
+ }
+ printf(";; HTTP session (HTTP/2-%s)-(%s%s)-(status: %lu)\n",
+ ctx->params.method == POST ? "POST" : "GET", ctx->authority,
+ ctx->path, ctx->status);
+}
+
+#endif //LIBNGHTTP2
diff --git a/src/utils/common/https.h b/src/utils/common/https.h
new file mode 100644
index 0000000..d39b527
--- /dev/null
+++ b/src/utils/common/https.h
@@ -0,0 +1,149 @@
+/* Copyright (C) 2020 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/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+/*! \brief HTTP method to transfer query. */
+typedef enum {
+ POST,
+ GET
+} https_method_t;
+
+/*! \brief HTTPS parameters. */
+typedef struct {
+ /*! Use HTTPS indicator. */
+ bool enable;
+ /*! HTTP method to transfer query. */
+ https_method_t method;
+ /*! Path */
+ char *path;
+} https_params_t;
+
+int https_params_copy(https_params_t *dst, const https_params_t *src);
+void https_params_clean(https_params_t *params);
+
+#ifdef LIBNGHTTP2
+
+#include <netinet/in.h>
+#include <pthread.h>
+#include <sys/socket.h>
+#include <nghttp2/nghttp2.h>
+
+#include "utils/common/tls.h"
+
+/*! \brief Structure that stores data source for DATA frames. */
+typedef struct {
+ const uint8_t *buf;
+ size_t buf_len;
+} https_data_provider_t;
+
+/*! \brief HTTPS context. */
+typedef struct {
+ // Parameters
+ https_params_t params;
+
+ // Contexts
+ nghttp2_session *session;
+ tls_ctx_t *tls;
+ char *authority;
+ char *path;
+
+ // Send destination
+ const uint8_t *send_buf;
+ size_t send_buflen;
+
+ // Recv destination
+ uint8_t *recv_buf;
+ size_t recv_buflen;
+ unsigned long status;
+
+ // Recv locks
+ pthread_mutex_t recv_mx;
+ bool read;
+ int32_t stream;
+} https_ctx_t;
+
+/*!
+ * \brief Initialize HTTPS context.
+ *
+ * \param ctx HTTPS context.
+ * \param tls_ctx TLS context.
+ * \param params Parameter table.
+ *
+ * \retval KNOT_EOK When initialized.
+ * \retval KNOT_EINVAL When parameters are invalid.
+ */
+int https_ctx_init(https_ctx_t *ctx, tls_ctx_t *tls_ctx, const https_params_t *params);
+
+/*!
+ * \brief Create TLS connection and perform HTTPS handshake.
+ *
+ * \param ctx HTTPS context.
+ * \param sockfd TLS context.
+ * \param address Socket address storage with address to server side.
+ * \param remote [optional] Remote name.
+ *
+ * \retval KNOT_EOK When successfully connected.
+ * \retval KNOT_EINVAL When parameters are invalid.
+ * \retval KNOT_NET_ESOCKET When socket is no accessible.
+ * \retval KNOT_NET_ETIMEOUT When server respond takes too long.
+ * \retval KNOT_NET_ECONNECT When unnable to connect to the server.
+ */
+int https_ctx_connect(https_ctx_t *ctx, const int sockfd, struct sockaddr_storage *address,
+ const char *remote);
+
+/*!
+ * \brief Send buffer as DNS message over HTTPS.
+ *
+ * \param ctx HTTPS context.
+ * \param buf Buffer with DNS message in wire format.
+ * \param buf_len Length of buffer.
+ *
+ * \retval KNOT_EOK When successfully sent.
+ * \retval KNOT_EINVAL When parameters are invalid.
+ * \retval KNOT_NET_ESEND When error occurs while sending a data.
+ */
+int https_send_dns_query(https_ctx_t *ctx, const uint8_t *buf, const size_t buf_len);
+
+/*!
+ * \brief Receive DATA frame as HTTPS packet, and store it into buffer.
+ *
+ * \param ctx HTTPS context.
+ * \param buf Buffer where will be DNS response stored.
+ * \param buf_len Length of buffer.
+ *
+ * \retval >=0 Number of bytes received in DATA frame.
+ * \retval KNOT_NET_ERECV When error while receive.
+ */
+int https_recv_dns_response(https_ctx_t *ctx, uint8_t *buf, const size_t buf_len);
+
+/*!
+ * \brief Deinitialize HTTPS context.
+ *
+ * \param ctx HTTPS context.
+ */
+void https_ctx_deinit(https_ctx_t *ctx);
+
+/*!
+ * \brief Prints information about HTTPS context.
+ *
+ * \param ctx HTTPS context.
+ */
+void print_https(const https_ctx_t *ctx);
+
+#endif //LIBNGHTTP2
diff --git a/src/utils/common/lookup.c b/src/utils/common/lookup.c
new file mode 100644
index 0000000..bb64252
--- /dev/null
+++ b/src/utils/common/lookup.c
@@ -0,0 +1,279 @@
+/* Copyright (C) 2019 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 <string.h>
+
+#include "utils/common/lookup.h"
+#include "contrib/mempattern.h"
+#include "contrib/ucw/mempool.h"
+#include "libknot/error.h"
+
+int lookup_init(lookup_t *lookup)
+{
+ if (lookup == NULL) {
+ return KNOT_EINVAL;
+ }
+ memset(lookup, 0, sizeof(*lookup));
+
+ mm_ctx_mempool(&lookup->mm, MM_DEFAULT_BLKSIZE);
+ lookup->trie = trie_create(&lookup->mm);
+ if (lookup->trie == NULL) {
+ mp_delete(lookup->mm.ctx);
+ return KNOT_ENOMEM;
+ }
+
+ return KNOT_EOK;
+}
+
+static void reset_output(lookup_t *lookup)
+{
+ if (lookup == NULL) {
+ return;
+ }
+
+ mm_free(&lookup->mm, lookup->found.key);
+ lookup->found.key = NULL;
+ lookup->found.data = NULL;
+
+ lookup->iter.count = 0;
+
+ mm_free(&lookup->mm, lookup->iter.first_key);
+ lookup->iter.first_key = NULL;
+
+ trie_it_free(lookup->iter.it);
+ lookup->iter.it = NULL;
+}
+
+void lookup_deinit(lookup_t *lookup)
+{
+ if (lookup == NULL) {
+ return;
+ }
+
+ reset_output(lookup);
+
+ trie_free(lookup->trie);
+ mp_delete(lookup->mm.ctx);
+}
+
+int lookup_insert(lookup_t *lookup, const char *str, void *data)
+{
+ if (lookup == NULL || str == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ size_t str_len = strlen(str);
+ if (str_len == 0) {
+ return KNOT_EINVAL;
+ }
+
+ trie_val_t *val = trie_get_ins(lookup->trie, (const trie_key_t *)str, str_len);
+ if (val == NULL) {
+ return KNOT_ENOMEM;
+ }
+ *val = data;
+
+ return KNOT_EOK;
+}
+
+static int set_key(lookup_t *lookup, char **dst, const char *key, size_t key_len)
+{
+ if (*dst != NULL) {
+ mm_free(&lookup->mm, *dst);
+ }
+ *dst = mm_alloc(&lookup->mm, key_len + 1);
+ if (*dst == NULL) {
+ return KNOT_ENOMEM;
+ }
+ memcpy(*dst, key, key_len);
+ (*dst)[key_len] = '\0';
+
+ return KNOT_EOK;
+}
+
+int lookup_search(lookup_t *lookup, const char *str, size_t str_len)
+{
+ if (lookup == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Change NULL string to the empty one.
+ if (str == NULL) {
+ str = "";
+ }
+
+ reset_output(lookup);
+
+ size_t new_len = 0;
+ trie_it_t *it = trie_it_begin(lookup->trie);
+ for (; !trie_it_finished(it); trie_it_next(it)) {
+ size_t len;
+ const char *key = (const char *)trie_it_key(it, &len);
+
+ // Compare with a shorter key.
+ if (len < str_len) {
+ int ret = memcmp(str, key, len);
+ if (ret >= 0) {
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ // Compare with an equal length or longer key.
+ int ret = memcmp(str, key, str_len);
+ if (ret == 0) {
+ lookup->iter.count++;
+
+ // First candidate.
+ if (lookup->iter.count == 1) {
+ ret = set_key(lookup, &lookup->found.key, key, len);
+ if (ret != KNOT_EOK) {
+ break;
+ }
+ lookup->found.data = *trie_it_val(it);
+ new_len = len;
+ // Another candidate.
+ } else if (new_len > str_len) {
+ if (new_len > len) {
+ new_len = len;
+ }
+ while (memcmp(lookup->found.key, key, new_len) != 0) {
+ new_len--;
+ }
+ }
+ // Stop if greater than the key, and also than all the following keys.
+ } else if (ret < 0) {
+ break;
+ }
+ }
+ trie_it_free(it);
+
+ switch (lookup->iter.count) {
+ case 0:
+ return KNOT_ENOENT;
+ case 1:
+ return KNOT_EOK;
+ default:
+ // Store full name of the first candidate.
+ if (set_key(lookup, &lookup->iter.first_key, lookup->found.key,
+ strlen(lookup->found.key)) != KNOT_EOK) {
+ return KNOT_ENOMEM;
+ }
+ lookup->found.key[new_len] = '\0';
+ lookup->found.data = NULL;
+
+ return KNOT_EFEWDATA;
+ }
+}
+
+void lookup_list(lookup_t *lookup)
+{
+ if (lookup == NULL || lookup->iter.first_key == NULL) {
+ return;
+ }
+
+ if (lookup->iter.it != NULL) {
+ if (trie_it_finished(lookup->iter.it)) {
+ trie_it_free(lookup->iter.it);
+ lookup->iter.it = NULL;
+ return;
+ }
+
+ trie_it_next(lookup->iter.it);
+
+ size_t len;
+ const char *key = (const char *)trie_it_key(lookup->iter.it, &len);
+
+ int ret = set_key(lookup, &lookup->found.key, key, len);
+ if (ret == KNOT_EOK) {
+ lookup->found.data = *trie_it_val(lookup->iter.it);
+ }
+ return;
+ }
+
+ lookup->iter.it = trie_it_begin(lookup->trie);
+ while (!trie_it_finished(lookup->iter.it)) {
+ size_t len;
+ const char *key = (const char *)trie_it_key(lookup->iter.it, &len);
+
+ if (strncmp(key, lookup->iter.first_key, len) == 0) {
+ int ret = set_key(lookup, &lookup->found.key, key, len);
+ if (ret == KNOT_EOK) {
+ lookup->found.data = *trie_it_val(lookup->iter.it);
+ }
+ break;
+ }
+ trie_it_next(lookup->iter.it);
+ }
+}
+
+static void print_options(lookup_t *lookup, EditLine *el)
+{
+ // Get terminal lines.
+ unsigned lines = 0;
+ if (el_get(el, EL_GETTC, "li", &lines) != 0 || lines < 3) {
+ return;
+ }
+
+ for (size_t i = 1; i <= lookup->iter.count; i++) {
+ lookup_list(lookup);
+ printf("\n%s", lookup->found.key);
+
+ if (i > 1 && i % (lines - 1) == 0 && i < lookup->iter.count) {
+ printf("\n Display next from %zu possibilities? (y or n)",
+ lookup->iter.count);
+ char next;
+ el_getc(el, &next);
+ if (next != 'y') {
+ break;
+ }
+ }
+ }
+
+ printf("\n");
+ fflush(stdout);
+}
+
+void lookup_complete(lookup_t *lookup, const char *str, size_t str_len,
+ EditLine *el, bool add_space)
+{
+ if (lookup == NULL || el == NULL) {
+ return;
+ }
+
+ // Try to complete the command name.
+ int ret = lookup_search(lookup, str, str_len);
+ switch (ret) {
+ case KNOT_EOK:
+ el_deletestr(el, str_len);
+ el_insertstr(el, lookup->found.key);
+ if (add_space) {
+ el_insertstr(el, " ");
+ }
+ break;
+ case KNOT_EFEWDATA:
+ if (strlen(lookup->found.key) > str_len) {
+ el_deletestr(el, str_len);
+ el_insertstr(el, lookup->found.key);
+ } else {
+ print_options(lookup, el);
+ }
+ break;
+ default:
+ break;
+ }
+}
diff --git a/src/utils/common/lookup.h b/src/utils/common/lookup.h
new file mode 100644
index 0000000..3f43ab6
--- /dev/null
+++ b/src/utils/common/lookup.h
@@ -0,0 +1,112 @@
+/* Copyright (C) 2018 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/>.
+ */
+
+#pragma once
+
+#include <histedit.h>
+
+#include "libknot/mm_ctx.h"
+#include "contrib/qp-trie/trie.h"
+
+/*! Lookup context. */
+typedef struct {
+ /*! Memory pool context. */
+ knot_mm_t mm;
+ /*! Main trie storage. */
+ trie_t *trie;
+
+ /*! Current (iteration) data context. */
+ struct {
+ /*! Stored key. */
+ char *key;
+ /*! Corresponding key data. */
+ void *data;
+ } found;
+
+ /*! Iteration context. */
+ struct {
+ /*! Total number of possibilies. */
+ size_t count;
+ /*! The first possibility. */
+ char *first_key;
+ /*! Hat-trie iterator. */
+ trie_it_t *it;
+ } iter;
+} lookup_t;
+
+/*!
+ * Initializes the lookup context.
+ *
+ * \param[in] lookup Lookup context.
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int lookup_init(lookup_t *lookup);
+
+/*!
+ * Deinitializes the lookup context.
+ *
+ * \param[in] lookup Lookup context.
+ */
+void lookup_deinit(lookup_t *lookup);
+
+/*!
+ * Inserts given key and data into the lookup.
+ *
+ * \param[in] lookup Lookup context.
+ * \param[in] str Textual key.
+ * \param[in] data Key textual data.
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int lookup_insert(lookup_t *lookup, const char *str, void *data);
+
+/*!
+ * Searches the lookup container for the given key.
+ *
+ * \note If one candidate, lookup.found contains the key/data,
+ * if more candidates, lookup.found contains the common key prefix and
+ * lookup.iter.first_key is the first candidate key.
+ *
+ * \param[in] lookup Lookup context.
+ * \param[in] str Textual key.
+ * \param[in] str_len Textual key length.
+ *
+ * \return Error code, KNOT_EOK if 1 candidate, KNOT_ENOENT if no candidate,
+ * and KNOT_EFEWDATA if more candidates are possible.
+ */
+int lookup_search(lookup_t *lookup, const char *str, size_t str_len);
+
+/*!
+ * Moves the lookup iterator to the next key candidate.
+ *
+ * \note lookup.found is updated.
+ *
+ * \param[in] lookup Lookup context.
+ */
+void lookup_list(lookup_t *lookup);
+
+/*!
+ * Completes the string based on the lookup content or prints all candidates.
+ *
+ * \param[in] lookup Lookup context.
+ * \param[in] str Textual key.
+ * \param[in] str_len Textual key length.
+ * \param[in] el Editline context.
+ * \param[in] add_space Add one space after completed string flag.
+ */
+void lookup_complete(lookup_t *lookup, const char *str, size_t str_len,
+ EditLine *el, bool add_space);
diff --git a/src/utils/common/msg.c b/src/utils/common/msg.c
new file mode 100644
index 0000000..c125297
--- /dev/null
+++ b/src/utils/common/msg.c
@@ -0,0 +1,40 @@
+/* Copyright (C) 2011 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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils/common/msg.h"
+
+static volatile int MSG_DBG_STATE = 0; /* True if debugging is enabled. */
+
+int msg_enable_debug(int val)
+{
+ return MSG_DBG_STATE = val;
+}
+
+int msg_debug(const char *fmt, ...)
+{
+ int n = 0;
+ if (MSG_DBG_STATE) {
+ va_list ap;
+ va_start(ap, fmt);
+ n = vprintf(fmt, ap);
+ va_end(ap);
+ }
+ return n;
+}
diff --git a/src/utils/common/msg.h b/src/utils/common/msg.h
new file mode 100644
index 0000000..02eedcc
--- /dev/null
+++ b/src/utils/common/msg.h
@@ -0,0 +1,38 @@
+/* Copyright (C) 2018 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/>.
+ */
+
+#pragma once
+
+#include <stdio.h>
+
+#define ERROR_ ";; ERROR: "
+#define INFO_ ";; INFO: "
+#define WARNING_ ";; WARNING: "
+#define DEBUG_ ";; DEBUG: "
+
+#define ERR(msg, ...) { fprintf(stderr, ERROR_ msg, ##__VA_ARGS__); fflush(stderr); }
+#define INFO(msg, ...) { fprintf(stdout, INFO_ msg, ##__VA_ARGS__); fflush(stdout); }
+#define WARN(msg, ...) { fprintf(stderr, WARNING_ msg, ##__VA_ARGS__); fflush(stderr); }
+#define DBG(msg, ...) msg_debug(DEBUG_ msg, ##__VA_ARGS__)
+
+/*! \brief Enable/disable debugging. */
+int msg_enable_debug(int val);
+
+/*! \brief Print debug message. */
+int msg_debug(const char *fmt, ...);
+
+/*! \brief Debug message for null input. */
+#define DBG_NULL DBG("%s: null parameter\n", __func__)
diff --git a/src/utils/common/netio.c b/src/utils/common/netio.c
new file mode 100644
index 0000000..3c75ccb
--- /dev/null
+++ b/src/utils/common/netio.c
@@ -0,0 +1,673 @@
+/* Copyright (C) 2021 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 <fcntl.h>
+#include <netdb.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <sys/types.h> // OpenBSD
+#include <netinet/tcp.h> // TCP_FASTOPEN
+#include <sys/socket.h>
+
+#ifdef HAVE_SYS_UIO_H
+#include <sys/uio.h>
+#endif
+
+#include "utils/common/netio.h"
+#include "utils/common/msg.h"
+#include "utils/common/tls.h"
+#include "libknot/libknot.h"
+#include "contrib/sockaddr.h"
+
+srv_info_t *srv_info_create(const char *name, const char *service)
+{
+ if (name == NULL || service == NULL) {
+ DBG_NULL;
+ return NULL;
+ }
+
+ // Create output structure.
+ srv_info_t *server = calloc(1, sizeof(srv_info_t));
+
+ // Check output.
+ if (server == NULL) {
+ return NULL;
+ }
+
+ // Fill output.
+ server->name = strdup(name);
+ server->service = strdup(service);
+
+ if (server->name == NULL || server->service == NULL) {
+ srv_info_free(server);
+ return NULL;
+ }
+
+ // Return result.
+ return server;
+}
+
+void srv_info_free(srv_info_t *server)
+{
+ if (server == NULL) {
+ DBG_NULL;
+ return;
+ }
+
+ free(server->name);
+ free(server->service);
+ free(server);
+}
+
+int get_iptype(const ip_t ip)
+{
+ switch (ip) {
+ case IP_4:
+ return AF_INET;
+ case IP_6:
+ return AF_INET6;
+ default:
+ return AF_UNSPEC;
+ }
+}
+
+int get_socktype(const protocol_t proto, const uint16_t type)
+{
+ switch (proto) {
+ case PROTO_TCP:
+ return SOCK_STREAM;
+ case PROTO_UDP:
+ return SOCK_DGRAM;
+ default:
+ if (type == KNOT_RRTYPE_AXFR || type == KNOT_RRTYPE_IXFR) {
+ return SOCK_STREAM;
+ } else {
+ return SOCK_DGRAM;
+ }
+ }
+}
+
+const char *get_sockname(const int socktype)
+{
+ switch (socktype) {
+ case SOCK_STREAM:
+ return "TCP";
+ case SOCK_DGRAM:
+ return "UDP";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static int get_addr(const srv_info_t *server,
+ const int iptype,
+ const int socktype,
+ struct addrinfo **info)
+{
+ struct addrinfo hints;
+
+ // Set connection hints.
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = iptype;
+ hints.ai_socktype = socktype;
+
+ // Get connection parameters.
+ int ret = getaddrinfo(server->name, server->service, &hints, info);
+ switch (ret) {
+ case 0:
+ return 0;
+#ifdef EAI_ADDRFAMILY /* EAI_ADDRFAMILY isn't implemented in FreeBSD/macOS anymore. */
+ case EAI_ADDRFAMILY:
+ break;
+#else /* FreeBSD, macOS, and likely others return EAI_NONAME instead. */
+ case EAI_NONAME:
+ if (iptype != AF_UNSPEC) {
+ break;
+ }
+ /* FALLTHROUGH */
+#endif /* EAI_ADDRFAMILY */
+ default:
+ ERR("%s for %s@%s\n", gai_strerror(ret), server->name, server->service);
+ }
+ return -1;
+}
+
+void get_addr_str(const struct sockaddr_storage *ss,
+ const int socktype,
+ char **dst)
+{
+ char addr_str[SOCKADDR_STRLEN] = {0};
+
+ // Get network address string and port number.
+ sockaddr_tostr(addr_str, sizeof(addr_str), ss);
+
+ // Calculate needed buffer size
+ const char *sock_name = get_sockname(socktype);
+ size_t buflen = strlen(addr_str) + strlen(sock_name) + 3 /* () */;
+
+ // Free previous string if any and write result
+ free(*dst);
+ *dst = malloc(buflen);
+ if (*dst != NULL) {
+ int ret = snprintf(*dst, buflen, "%s(%s)", addr_str, sock_name);
+ if (ret <= 0 || ret >= buflen) {
+ **dst = '\0';
+ }
+ }
+}
+
+int net_init(const srv_info_t *local,
+ const srv_info_t *remote,
+ const int iptype,
+ const int socktype,
+ const int wait,
+ const net_flags_t flags,
+ const tls_params_t *tls_params,
+ const https_params_t *https_params,
+ net_t *net)
+{
+ if (remote == NULL || net == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ // Clean network structure.
+ memset(net, 0, sizeof(*net));
+
+ // Get remote address list.
+ if (get_addr(remote, iptype, socktype, &net->remote_info) != 0) {
+ net_clean(net);
+ return KNOT_NET_EADDR;
+ }
+
+ // Set current remote address.
+ net->srv = net->remote_info;
+
+ // Get local address if specified.
+ if (local != NULL) {
+ if (get_addr(local, iptype, socktype, &net->local_info) != 0) {
+ net_clean(net);
+ return KNOT_NET_EADDR;
+ }
+ }
+
+ // Store network parameters.
+ net->iptype = iptype;
+ net->socktype = socktype;
+ net->wait = wait;
+ net->local = local;
+ net->remote = remote;
+ net->flags = flags;
+
+ // Prepare for TLS.
+ if (tls_params != NULL && tls_params->enable) {
+ int ret = tls_ctx_init(&net->tls, tls_params, net->wait);
+ if (ret != KNOT_EOK) {
+ net_clean(net);
+ return ret;
+ }
+
+#ifdef LIBNGHTTP2
+ // Prepare for HTTPS.
+ if (https_params != NULL && https_params->enable) {
+ ret = https_ctx_init(&net->https, &net->tls, https_params);
+ if (ret != KNOT_EOK) {
+ net_clean(net);
+ return ret;
+ }
+ }
+#endif //LIBNGHTTP2
+ }
+
+ return KNOT_EOK;
+}
+
+/*!
+ * Connect with TCP Fast Open.
+ */
+static int fastopen_connect(int sockfd, const struct addrinfo *srv)
+{
+#if defined( __FreeBSD__)
+ const int enable = 1;
+ return setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN, &enable, sizeof(enable));
+#elif defined(__APPLE__)
+ // connection is performed lazily when first data are sent
+ struct sa_endpoints ep = {0};
+ ep.sae_dstaddr = srv->ai_addr;
+ ep.sae_dstaddrlen = srv->ai_addrlen;
+ int flags = CONNECT_DATA_IDEMPOTENT|CONNECT_RESUME_ON_READ_WRITE;
+
+ return connectx(sockfd, &ep, SAE_ASSOCID_ANY, flags, NULL, 0, NULL, NULL);
+#elif defined(__linux__)
+ // connect() will be called implicitly with sendto(), sendmsg()
+ return 0;
+#else
+ errno = ENOTSUP;
+ return -1;
+#endif
+}
+
+/*!
+ * Sends data with TCP Fast Open.
+ */
+static int fastopen_send(int sockfd, const struct msghdr *msg, int timeout)
+{
+#if defined(__FreeBSD__) || defined(__APPLE__)
+ return sendmsg(sockfd, msg, 0);
+#elif defined(__linux__)
+ int ret = sendmsg(sockfd, msg, MSG_FASTOPEN);
+ if (ret == -1 && errno == EINPROGRESS) {
+ struct pollfd pfd = {
+ .fd = sockfd,
+ .events = POLLOUT,
+ .revents = 0,
+ };
+ if (poll(&pfd, 1, 1000 * timeout) != 1) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+ ret = sendmsg(sockfd, msg, 0);
+ }
+ return ret;
+#else
+ errno = ENOTSUP;
+ return -1;
+#endif
+}
+
+int net_connect(net_t *net)
+{
+ if (net == NULL || net->srv == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ // Set remote information string.
+ get_addr_str((struct sockaddr_storage *)net->srv->ai_addr,
+ net->socktype, &net->remote_str);
+
+ // Create socket.
+ int sockfd = socket(net->srv->ai_family, net->socktype, 0);
+ if (sockfd == -1) {
+ WARN("can't create socket for %s\n", net->remote_str);
+ return KNOT_NET_ESOCKET;
+ }
+
+ // Initialize poll descriptor structure.
+ struct pollfd pfd = {
+ .fd = sockfd,
+ .events = POLLOUT,
+ .revents = 0,
+ };
+
+ // Set non-blocking socket.
+ if (fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1) {
+ WARN("can't set non-blocking socket for %s\n", net->remote_str);
+ return KNOT_NET_ESOCKET;
+ }
+
+ // Bind address to socket if specified.
+ if (net->local_info != NULL) {
+ if (bind(sockfd, net->local_info->ai_addr,
+ net->local_info->ai_addrlen) == -1) {
+ WARN("can't assign address %s\n", net->local->name);
+ return KNOT_NET_ESOCKET;
+ }
+ } else {
+ // Ensure source port is always randomized (even for TCP).
+ struct sockaddr_storage local = { .ss_family = net->srv->ai_family };
+ (void)bind(sockfd, (struct sockaddr *)&local, sockaddr_len(&local));
+ }
+
+ if (net->socktype == SOCK_STREAM) {
+ int cs, err, ret = 0;
+ socklen_t err_len = sizeof(err);
+ bool fastopen = net->flags & NET_FLAGS_FASTOPEN;
+
+ // Connect using socket.
+ if (fastopen) {
+ ret = fastopen_connect(sockfd, net->srv);
+ } else {
+ ret = connect(sockfd, net->srv->ai_addr, net->srv->ai_addrlen);
+ }
+ if (ret != 0 && errno != EINPROGRESS) {
+ WARN("can't connect to %s\n", net->remote_str);
+ close(sockfd);
+ return KNOT_NET_ECONNECT;
+ }
+
+ // Check for connection timeout.
+ if (!fastopen && poll(&pfd, 1, 1000 * net->wait) != 1) {
+ WARN("connection timeout for %s\n", net->remote_str);
+ close(sockfd);
+ return KNOT_NET_ECONNECT;
+ }
+
+ // Check if NB socket is writeable.
+ cs = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &err_len);
+ if (cs < 0 || err != 0) {
+ WARN("can't connect to %s\n", net->remote_str);
+ close(sockfd);
+ return KNOT_NET_ECONNECT;
+ }
+
+ if (net->tls.params != NULL) {
+#ifdef LIBNGHTTP2
+ if (net->https.params.enable) {
+ // Establish HTTPS connection.
+ char *remote = NULL;
+ if (net->tls.params->sni != NULL) {
+ remote = net->tls.params->sni;
+ } else if (net->tls.params->hostname != NULL) {
+ remote = net->tls.params->hostname;
+ } else if (strchr(net->remote_str, ':') == NULL) {
+ char *at = strchr(net->remote_str, '@');
+ if (at != NULL && strncmp(net->remote->name, net->remote_str, at - net->remote_str)) {
+ remote = net->remote->name;
+ }
+ }
+ ret = https_ctx_connect(&net->https, sockfd, (struct sockaddr_storage *)net->srv->ai_addr, remote);
+ } else {
+ // Establish TLS connection.
+ ret = tls_ctx_connect(&net->tls, sockfd, net->tls.params->sni);
+ }
+#else
+ ret = tls_ctx_connect(&net->tls, sockfd, net->tls.params->sni);
+#endif //LIBNGHTTP2
+ if (ret != KNOT_EOK) {
+ close(sockfd);
+ return ret;
+ }
+ }
+ }
+
+ // Store socket descriptor.
+ net->sockfd = sockfd;
+
+ return KNOT_EOK;
+}
+
+int net_set_local_info(net_t *net)
+{
+ if (net == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ socklen_t local_addr_len = sizeof(struct sockaddr_storage);
+
+ struct addrinfo *new_info = calloc(1, sizeof(*new_info) + local_addr_len);
+ if (new_info == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ new_info->ai_addr = (struct sockaddr *)(new_info + 1);
+ new_info->ai_family = net->srv->ai_family;
+ new_info->ai_socktype = net->srv->ai_socktype;
+ new_info->ai_protocol = net->srv->ai_protocol;
+ new_info->ai_addrlen = local_addr_len;
+
+ if (getsockname(net->sockfd, new_info->ai_addr, &local_addr_len) == -1) {
+ WARN("can't get local address\n");
+ free(new_info);
+ return KNOT_NET_ESOCKET;
+ }
+
+ if (net->local_info != NULL) {
+ if (net->local == NULL) {
+ free(net->local_info);
+ } else {
+ freeaddrinfo(net->local_info);
+ }
+ }
+
+ net->local_info = new_info;
+
+ get_addr_str((struct sockaddr_storage *)net->local_info->ai_addr,
+ net->socktype, &net->local_str);
+
+ return KNOT_EOK;
+}
+
+int net_send(const net_t *net, const uint8_t *buf, const size_t buf_len)
+{
+ if (net == NULL || buf == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ // Send data over UDP.
+ if (net->socktype == SOCK_DGRAM) {
+ if (sendto(net->sockfd, buf, buf_len, 0, net->srv->ai_addr,
+ net->srv->ai_addrlen) != (ssize_t)buf_len) {
+ WARN("can't send query to %s\n", net->remote_str);
+ return KNOT_NET_ESEND;
+ }
+#ifdef LIBNGHTTP2
+ // Send data over HTTPS
+ } else if (net->https.params.enable) {
+ int ret = https_send_dns_query((https_ctx_t *)&net->https, buf, buf_len);
+ if (ret != KNOT_EOK) {
+ WARN("can't send query to %s\n", net->remote_str);
+ return KNOT_NET_ESEND;
+ }
+#endif //LIBNGHTTP2
+ // Send data over TLS.
+ } else if (net->tls.params != NULL) {
+ int ret = tls_ctx_send((tls_ctx_t *)&net->tls, buf, buf_len);
+ if (ret != KNOT_EOK) {
+ WARN("can't send query to %s\n", net->remote_str);
+ return KNOT_NET_ESEND;
+ }
+ // Send data over TCP.
+ } else {
+ bool fastopen = net->flags & NET_FLAGS_FASTOPEN;
+
+ // Leading packet length bytes.
+ uint16_t pktsize = htons(buf_len);
+
+ struct iovec iov[2];
+ iov[0].iov_base = &pktsize;
+ iov[0].iov_len = sizeof(pktsize);
+ iov[1].iov_base = (uint8_t *)buf;
+ iov[1].iov_len = buf_len;
+
+ // Compute packet total length.
+ ssize_t total = iov[0].iov_len + iov[1].iov_len;
+
+ struct msghdr msg = {0};
+ msg.msg_iov = iov;
+ msg.msg_iovlen = sizeof(iov) / sizeof(*iov);
+ msg.msg_name = net->srv->ai_addr;
+ msg.msg_namelen = net->srv->ai_addrlen;
+
+ int ret = 0;
+ if (fastopen) {
+ ret = fastopen_send(net->sockfd, &msg, net->wait);
+ } else {
+ ret = sendmsg(net->sockfd, &msg, 0);
+ }
+ if (ret != total) {
+ WARN("can't send query to %s\n", net->remote_str);
+ return KNOT_NET_ESEND;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int net_receive(const net_t *net, uint8_t *buf, const size_t buf_len)
+{
+ if (net == NULL || buf == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ // Initialize poll descriptor structure.
+ struct pollfd pfd = {
+ .fd = net->sockfd,
+ .events = POLLIN,
+ .revents = 0,
+ };
+
+ // Receive data over UDP.
+ if (net->socktype == SOCK_DGRAM) {
+ struct sockaddr_storage from;
+ memset(&from, '\0', sizeof(from));
+
+ // Receive replies unless correct reply or timeout.
+ while (true) {
+ socklen_t from_len = sizeof(from);
+
+ // Wait for datagram data.
+ if (poll(&pfd, 1, 1000 * net->wait) != 1) {
+ WARN("response timeout for %s\n",
+ net->remote_str);
+ return KNOT_NET_ETIMEOUT;
+ }
+
+ // Receive whole UDP datagram.
+ ssize_t ret = recvfrom(net->sockfd, buf, buf_len, 0,
+ (struct sockaddr *)&from, &from_len);
+ if (ret <= 0) {
+ WARN("can't receive reply from %s\n",
+ net->remote_str);
+ return KNOT_NET_ERECV;
+ }
+
+ // Compare reply address with the remote one.
+ if (from_len > sizeof(from) ||
+ memcmp(&from, net->srv->ai_addr, from_len) != 0) {
+ char *src = NULL;
+ get_addr_str(&from, net->socktype, &src);
+ WARN("unexpected reply source %s\n", src);
+ free(src);
+ continue;
+ }
+
+ return ret;
+ }
+#ifdef LIBNGHTTP2
+ // Receive data over HTTPS.
+ } else if (net->https.params.enable) {
+ return https_recv_dns_response((https_ctx_t *)&net->https, buf, buf_len);
+#endif //LIBNGHTTP2
+ // Receive data over TLS.
+ } else if (net->tls.params != NULL) {
+ int ret = tls_ctx_receive((tls_ctx_t *)&net->tls, buf, buf_len);
+ if (ret < 0) {
+ WARN("can't receive reply from %s\n", net->remote_str);
+ return KNOT_NET_ERECV;
+ }
+
+ return ret;
+ // Receive data over TCP.
+ } else {
+ uint32_t total = 0;
+
+ uint16_t msg_len = 0;
+ // Receive TCP message header.
+ while (total < sizeof(msg_len)) {
+ if (poll(&pfd, 1, 1000 * net->wait) != 1) {
+ WARN("response timeout for %s\n",
+ net->remote_str);
+ return KNOT_NET_ETIMEOUT;
+ }
+
+ // Receive piece of message.
+ ssize_t ret = recv(net->sockfd, (uint8_t *)&msg_len + total,
+ sizeof(msg_len) - total, 0);
+ if (ret <= 0) {
+ WARN("can't receive reply from %s\n",
+ net->remote_str);
+ return KNOT_NET_ERECV;
+ }
+ total += ret;
+ }
+
+ // Convert number to host format.
+ msg_len = ntohs(msg_len);
+ if (msg_len > buf_len) {
+ return KNOT_ESPACE;
+ }
+
+ total = 0;
+
+ // Receive whole answer message by parts.
+ while (total < msg_len) {
+ if (poll(&pfd, 1, 1000 * net->wait) != 1) {
+ WARN("response timeout for %s\n",
+ net->remote_str);
+ return KNOT_NET_ETIMEOUT;
+ }
+
+ // Receive piece of message.
+ ssize_t ret = recv(net->sockfd, buf + total, msg_len - total, 0);
+ if (ret <= 0) {
+ WARN("can't receive reply from %s\n",
+ net->remote_str);
+ return KNOT_NET_ERECV;
+ }
+ total += ret;
+ }
+
+ return total;
+ }
+
+ return KNOT_NET_ERECV;
+}
+
+void net_close(net_t *net)
+{
+ if (net == NULL) {
+ DBG_NULL;
+ return;
+ }
+
+ tls_ctx_close(&net->tls);
+ close(net->sockfd);
+ net->sockfd = -1;
+}
+
+void net_clean(net_t *net)
+{
+ if (net == NULL) {
+ DBG_NULL;
+ return;
+ }
+
+ free(net->local_str);
+ free(net->remote_str);
+
+ if (net->local_info != NULL) {
+ if (net->local == NULL) {
+ free(net->local_info);
+ } else {
+ freeaddrinfo(net->local_info);
+ }
+ }
+
+ if (net->remote_info != NULL) {
+ freeaddrinfo(net->remote_info);
+ }
+
+#ifdef LIBNGHTTP2
+ https_ctx_deinit(&net->https);
+#endif
+ tls_ctx_deinit(&net->tls);
+}
diff --git a/src/utils/common/netio.h b/src/utils/common/netio.h
new file mode 100644
index 0000000..adea1f2
--- /dev/null
+++ b/src/utils/common/netio.h
@@ -0,0 +1,226 @@
+/* Copyright (C) 2020 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/>.
+ */
+
+#pragma once
+
+#include <netdb.h>
+#include <stdint.h>
+#include <sys/socket.h>
+
+#include "utils/common/https.h"
+#include "utils/common/params.h"
+#include "utils/common/tls.h"
+
+/*! \brief Structure containing server information. */
+typedef struct {
+ /*! List node (for list container). */
+ node_t n;
+ /*! Name or address of the server. */
+ char *name;
+ /*! Name or number of the service. */
+ char *service;
+} srv_info_t;
+
+typedef enum {
+ NET_FLAGS_NONE = 0,
+ NET_FLAGS_FASTOPEN = 1 << 0,
+} net_flags_t;
+
+typedef struct {
+ /*! Socket descriptor. */
+ int sockfd;
+
+ /*! IP protocol type. */
+ int iptype;
+ /*! Socket type. */
+ int socktype;
+ /*! Timeout for all network operations. */
+ int wait;
+ /*! Connection flags. */
+ net_flags_t flags;
+
+ /*! Local interface parameters. */
+ const srv_info_t *local;
+ /*! Remote server parameters. */
+ const srv_info_t *remote;
+
+ /*! Local description string (used for logging). */
+ char *local_str;
+ /*! Remote description string (used for logging). */
+ char *remote_str;
+
+ /*! Output from getaddrinfo for remote server. If the server is
+ * specified using domain name, this structure may contain more
+ * results.
+ */
+ struct addrinfo *remote_info;
+ /*! Currently used result from remote_info. */
+ struct addrinfo *srv;
+ /*! Output from getaddrinfo for local address. Only first result is
+ * used.
+ */
+ struct addrinfo *local_info;
+
+ /*! TLS context. */
+ tls_ctx_t tls;
+#ifdef LIBNGHTTP2
+ /*! HTTPS context. */
+ https_ctx_t https;
+#endif
+} net_t;
+
+/*!
+ * \brief Creates and fills server structure.
+ *
+ * \param name Address or host name.
+ * \param service Port number or service name.
+ *
+ * \retval server if success.
+ * \retval NULL if error.
+ */
+srv_info_t *srv_info_create(const char *name, const char *service);
+
+/*!
+ * \brief Destroys server structure.
+ *
+ * \param server Server structure to destroy.
+ */
+void srv_info_free(srv_info_t *server);
+
+/*!
+ * \brief Translates enum IP version type to int version.
+ *
+ * \param ip IP version to convert.
+ *
+ * \retval AF_INET, AF_INET6 or AF_UNSPEC.
+ */
+int get_iptype(const ip_t ip);
+
+/*!
+ * \brief Translates enum IP protocol type to int version in context to the
+ * current DNS query type.
+ *
+ * \param proto IP protocol type to convert.
+ * \param type DNS query type number.
+ *
+ * \retval SOCK_STREAM or SOCK_DGRAM.
+ */
+int get_socktype(const protocol_t proto, const uint16_t type);
+
+/*!
+ * \brief Translates int socket type to the common string one.
+ *
+ * \param socktype Socket type (SOCK_STREAM or SOCK_DGRAM).
+ *
+ * \retval "TCP" or "UDP".
+ */
+const char *get_sockname(const int socktype);
+
+/*!
+ * \brief Translates int socket type to the common string one.
+ *
+ * \param ss Socket address storage.
+ * \param socktype Socket type (SOCK_STREAM or SOCK_DGRAM).
+ * \param dst Output string.
+ */
+void get_addr_str(const struct sockaddr_storage *ss,
+ const int socktype,
+ char **dst);
+
+/*!
+ * \brief Initializes network structure and resolves local and remote addresses.
+ *
+ * \param local Local address and service description.
+ * \param remote Remote address and service description.
+ * \param iptype IP version.
+ * \param socktype Socket type.
+ * \param wait Network timeout interval.
+ * \param tls_params TLS parameters.
+ * \param https_params HTTPS parameters.
+ * \param flags Connection flags.
+ * \param net Network structure to initialize.
+ *
+ * \retval KNOT_EOK if success.
+ * \retval errcode if error.
+ */
+int net_init(const srv_info_t *local,
+ const srv_info_t *remote,
+ const int iptype,
+ const int socktype,
+ const int wait,
+ const net_flags_t flags,
+ const tls_params_t *tls_params,
+ const https_params_t *https_params,
+ net_t *net);
+
+/*!
+ * \brief Creates socket and connects (if TCP) to remote address specified
+ * by net->srv.
+ *
+ * \param net Connection parameters.
+ *
+ * \retval KNOT_EOK if success.
+ * \retval errcode if error.
+ */
+int net_connect(net_t *net);
+
+/*!
+ * \brief Fills in local address information.
+ *
+ * \param net Connection parameters.
+ *
+ * \retval KNOT_EOK if success.
+ * \retval errcode if error.
+ */
+int net_set_local_info(net_t *net);
+
+/*!
+ * \brief Sends data to connected remote server.
+ *
+ * \param net Connection parameters.
+ * \param buf Data to send.
+ * \param buf_len Length of the data to send.
+ *
+ * \retval KNOT_EOK if success.
+ * \retval errcode if error.
+ */
+int net_send(const net_t *net, const uint8_t *buf, const size_t buf_len);
+
+/*!
+ * \brief Receives data from connected remote server.
+ *
+ * \param net Connection parameters.
+ * \param buf Buffer for incoming data.
+ * \param buf_len Length of the buffer.
+ *
+ * \retval >=0 length of successfully received data.
+ * \retval errcode if error.
+ */
+int net_receive(const net_t *net, uint8_t *buf, const size_t buf_len);
+
+/*!
+ * \brief Closes current network connection.
+ *
+ * \param net Connection parameters.
+ */
+void net_close(net_t *net);
+
+/*!
+ * \brief Cleans up network structure.
+ *
+ * \param net Connection parameters.
+ */
+void net_clean(net_t *net);
diff --git a/src/utils/common/params.c b/src/utils/common/params.c
new file mode 100644
index 0000000..684a7d1
--- /dev/null
+++ b/src/utils/common/params.c
@@ -0,0 +1,347 @@
+/* Copyright (C) 2018 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 <stdio.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#ifdef LIBIDN
+#include LIBIDN_HEADER
+#endif
+
+#include "utils/common/params.h"
+#include "utils/common/msg.h"
+#include "utils/common/resolv.h"
+#include "utils/common/token.h"
+#include "libknot/libknot.h"
+#include "contrib/macros.h"
+#include "contrib/mempattern.h"
+#include "contrib/openbsd/strlcpy.h"
+#include "contrib/strtonum.h"
+
+#define IPV4_REVERSE_DOMAIN "in-addr.arpa."
+#define IPV6_REVERSE_DOMAIN "ip6.arpa."
+
+char *name_from_idn(const char *idn_name) {
+#ifdef LIBIDN
+ char *name = NULL;
+
+ int rc = idna_to_ascii_lz(idn_name, &name, 0);
+ if (rc != IDNA_SUCCESS) {
+ ERR("IDNA (%s)\n", idna_strerror(rc));
+ return NULL;
+ }
+
+ return name;
+#endif
+ return strdup(idn_name);
+}
+
+void name_to_idn(char **name) {
+#ifdef LIBIDN
+ char *idn_name = NULL;
+
+ int rc = idna_to_unicode_8zlz(*name, &idn_name, 0);
+ if (rc != IDNA_SUCCESS) {
+ return;
+ }
+
+ free(*name);
+ *name = idn_name;
+#endif
+ return;
+}
+
+/*!
+ * \brief Checks if string is a prefix of reference string.
+ *
+ * \param pref Prefix string.
+ * \param pref_len Prefix length.
+ * \param str Reference string (must have trailing zero).
+ *
+ * \retval -1 \a pref is not a prefix of \a str.
+ * \retval 0<= number of chars after prefix \a pref in \a str.
+ */
+static int cmp_prefix(const char *pref, const size_t pref_len,
+ const char *str)
+{
+ size_t i = 0;
+ while (1) {
+ // Different characters => NOT prefix.
+ if (pref[i] != str[i]) {
+ return -1;
+ }
+
+ i++;
+
+ // Pref IS a prefix of pref.
+ if (i == pref_len) {
+ size_t rest = 0;
+ while (str[i + rest] != '\0') {
+ rest++;
+ }
+ return rest;
+ // Pref is longer then ref => NOT prefix.
+ } else if (str[i] == '\0') {
+ return -1;
+ }
+ }
+}
+
+int best_param(const char *str, const size_t str_len, const param_t *tbl,
+ bool *unique)
+{
+ if (str == NULL || str_len == 0 || tbl == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ int best_pos = -1;
+ int best_match = INT_MAX;
+ size_t matches = 0;
+ for (int i = 0; tbl[i].name != NULL; i++) {
+ int ret = cmp_prefix(str, str_len, tbl[i].name);
+ switch (ret) {
+ case -1:
+ continue;
+ case 0:
+ *unique = true;
+ return i;
+ default:
+ if (ret < best_match) {
+ best_pos = i;
+ best_match = ret;
+ }
+ matches++;
+ }
+ }
+
+ switch (matches) {
+ case 0:
+ return KNOT_ENOTSUP;
+ case 1:
+ *unique = true;
+ return best_pos;
+ default:
+ *unique = false;
+ return best_pos;
+ }
+}
+
+char *get_reverse_name(const char *name)
+{
+ struct in_addr addr4;
+ struct in6_addr addr6;
+ int ret;
+ char buf[128] = "\0";
+
+ if (name == NULL) {
+ DBG_NULL;
+ return NULL;
+ }
+
+ // Check name for IPv4 address, IPv6 address or other.
+ if (inet_pton(AF_INET, name, &addr4) == 1) {
+ uint32_t num = ntohl(addr4.s_addr);
+
+ // Create IPv4 reverse FQD name.
+ ret = snprintf(buf, sizeof(buf), "%u.%u.%u.%u.%s",
+ (num >> 0) & 0xFF, (num >> 8) & 0xFF,
+ (num >> 16) & 0xFF, (num >> 24) & 0xFF,
+ IPV4_REVERSE_DOMAIN);
+ if (ret < 0 || (size_t)ret >= sizeof(buf)) {
+ return NULL;
+ }
+
+ return strdup(buf);
+ } else if (inet_pton(AF_INET6, name, &addr6) == 1) {
+ char *pos = buf;
+ size_t len = sizeof(buf);
+ uint8_t left, right;
+
+ // Create IPv6 reverse name.
+ for (int i = 15; i >= 0; i--) {
+ left = ((addr6.s6_addr)[i] & 0xF0) >> 4;
+ right = (addr6.s6_addr)[i] & 0x0F;
+
+ ret = snprintf(pos, len, "%x.%x.", right, left);
+ if (ret < 0 || (size_t)ret >= len) {
+ return NULL;
+ }
+
+ pos += ret;
+ len -= ret;
+ }
+
+ // Add IPv6 reverse domain.
+ ret = snprintf(pos, len, "%s", IPV6_REVERSE_DOMAIN);
+ if (ret < 0 || (size_t)ret >= len) {
+ return NULL;
+ }
+
+ return strdup(buf);
+ } else {
+ return NULL;
+ }
+}
+
+char *get_fqd_name(const char *name)
+{
+ char *fqd_name = NULL;
+
+ if (name == NULL) {
+ DBG_NULL;
+ return NULL;
+ }
+
+ size_t name_len = strlen(name);
+
+ // If the name is FQDN, make a copy.
+ if (name[name_len - 1] == '.') {
+ fqd_name = strdup(name);
+ // Else make a copy and append a trailing dot.
+ } else {
+ size_t fqd_name_size = name_len + 2;
+ fqd_name = malloc(fqd_name_size);
+ if (fqd_name != NULL) {
+ strlcpy(fqd_name, name, fqd_name_size);
+ fqd_name[name_len] = '.';
+ fqd_name[name_len + 1] = 0;
+ }
+ }
+
+ return fqd_name;
+}
+
+int params_parse_class(const char *value, uint16_t *rclass)
+{
+ if (value == NULL || rclass == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ if (knot_rrclass_from_string(value, rclass) == 0) {
+ return KNOT_EOK;
+ } else {
+ return KNOT_EINVAL;
+ }
+}
+
+int params_parse_type(const char *value, uint16_t *rtype, int64_t *serial,
+ bool *notify)
+{
+ if (value == NULL || rtype == NULL || serial == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ // Find and parse type name.
+ size_t param_pos = strcspn(value, "=");
+ char *type_char = strndup(value, param_pos);
+
+ if (knot_rrtype_from_string(type_char, rtype) != 0) {
+ size_t cmp_len = MAX(strlen("NOTIFY"), param_pos);
+ if (strncasecmp(type_char, "NOTIFY", cmp_len) == 0) {
+ *rtype = KNOT_RRTYPE_SOA;
+ *notify = true;
+ } else {
+ free(type_char);
+ return KNOT_EINVAL;
+ }
+ } else {
+ *notify = false;
+ }
+
+ free(type_char);
+
+ // Parse additional parameter.
+ if (param_pos == strlen(value)) {
+ // IXFR requires serial parameter.
+ if (*rtype == KNOT_RRTYPE_IXFR) {
+ DBG("SOA serial is required for IXFR query\n");
+ return KNOT_EINVAL;
+ } else {
+ *serial = -1;
+ }
+ } else {
+ // Additional parameter is accepted for IXFR or NOTIFY.
+ if (*rtype == KNOT_RRTYPE_IXFR || *notify) {
+ const char *param_str = value + 1 + param_pos;
+ char *end;
+
+ // Convert string to serial.
+ unsigned long long num = strtoull(param_str, &end, 10);
+
+ // Check for bad serial string.
+ if (end == param_str || *end != '\0' || num > UINT32_MAX) {
+ DBG("bad SOA serial '%s'\n", param_str);
+ return KNOT_EINVAL;
+ }
+
+ *serial = num;
+ } else {
+ DBG("unsupported parameter '%s'\n", value);
+ return KNOT_EINVAL;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int params_parse_server(const char *value, list_t *servers, const char *def_port)
+{
+ if (value == NULL || servers == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ // Add specified nameserver.
+ srv_info_t *server = parse_nameserver(value, def_port);
+ if (server == NULL) {
+ return KNOT_EINVAL;
+ }
+ add_tail(servers, (node_t *)server);
+
+ return KNOT_EOK;
+}
+
+int params_parse_wait(const char *value, int32_t *dst)
+{
+ if (value == NULL || dst == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ uint32_t num = 0;
+ int ret = str_to_u32(value, &num);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Check for minimal value.
+ if (num < 1) {
+ num = 1;
+ // Reduce maximal value. Poll takes signed int in milliseconds.
+ } else if (num > INT32_MAX / 1000) {
+ num = INT32_MAX / 1000;
+ }
+
+ *dst = num;
+
+ return KNOT_EOK;
+}
diff --git a/src/utils/common/params.h b/src/utils/common/params.h
new file mode 100644
index 0000000..6c3cc31
--- /dev/null
+++ b/src/utils/common/params.h
@@ -0,0 +1,165 @@
+/* Copyright (C) 2020 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/>.
+ */
+
+#pragma once
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "libknot/libknot.h"
+#include "contrib/ucw/lists.h"
+
+#define DEFAULT_IPV4_NAME "127.0.0.1"
+#define DEFAULT_IPV6_NAME "::1"
+#define DEFAULT_DNS_PORT "53"
+#define DEFAULT_DNS_HTTPS_PORT "443"
+#define DEFAULT_DNS_TLS_PORT "853"
+#define DEFAULT_UDP_SIZE 512
+#define DEFAULT_EDNS_SIZE 4096
+#define MAX_PACKET_SIZE 65535
+
+#define SEP_CHARS "\n\t "
+
+/*! \brief Variants of IP protocol. */
+typedef enum {
+ IP_ALL,
+ IP_4,
+ IP_6
+} ip_t;
+
+/*! \brief Variants of transport protocol. */
+typedef enum {
+ PROTO_ALL,
+ PROTO_TCP,
+ PROTO_UDP
+} protocol_t;
+
+/*! \brief Variants of output type. */
+typedef enum {
+ /*!< Verbose output (same for host and dig). */
+ FORMAT_FULL,
+ /*!< Short dig output. */
+ FORMAT_DIG,
+ /*!< Brief host output. */
+ FORMAT_HOST,
+ /*!< Brief nsupdate output. */
+ FORMAT_NSUPDATE
+} format_t;
+
+/*! \brief Text output settings. */
+typedef struct {
+ /*!< Output format. */
+ format_t format;
+
+ /*!< Style of rrset dump. */
+ knot_dump_style_t style;
+
+ /*!< Show query packet. */
+ bool show_query;
+ /*!< Show header info. */
+ bool show_header;
+ /*!< Show section name. */
+ bool show_section;
+ /*!< Show EDNS pseudosection. */
+ bool show_edns;
+ /*!< Show unknown EDNS options in printable format. */
+ bool show_edns_opt_text;
+ /*!< Show QUERY/ZONE section. */
+ bool show_question;
+ /*!< Show ANSWER/PREREQ section. */
+ bool show_answer;
+ /*!< Show UPDATE/AUTHORITY section. */
+ bool show_authority;
+ /*!< Show ADDITIONAL section. */
+ bool show_additional;
+ /*!< Show TSIG pseudosection. */
+ bool show_tsig;
+ /*!< Show footer info. */
+ bool show_footer;
+
+ /*!< KHOST - Hide CNAME record in answer (duplicity reduction). */
+ bool hide_cname;
+} style_t;
+
+/*! \brief Parameter handler. */
+typedef int (*param_handle_f)(const char *arg, void *params);
+
+/*! \brief Parameter argument type. */
+typedef enum {
+ ARG_NONE,
+ ARG_REQUIRED,
+ ARG_OPTIONAL
+} arg_t;
+
+/*! \brief Parameter specification. */
+typedef struct {
+ const char *name;
+ arg_t arg;
+ param_handle_f handler;
+} param_t;
+
+inline static void print_version(const char *program_name)
+{
+ printf("%s (Knot DNS), version %s\n", program_name, PACKAGE_VERSION);
+}
+
+/*!
+ * \brief Transforms localized IDN string to ASCII punycode.
+ *
+ * \param idn_name IDN name to transform.
+ *
+ * \retval NULL if transformation fails.
+ * \retval string if ok.
+ */
+char *name_from_idn(const char *idn_name);
+
+/*!
+ * \brief Transforms ASCII punycode to localized IDN string.
+ *
+ * If an error occurs or IDN support is missing, this function does nothing.
+ *
+ * \param name ASCII name to transform and replace with IDN name.
+ */
+void name_to_idn(char **name);
+
+/*!
+ * \brief Find the best parameter match in table based on prefix equality.
+ *
+ * \param str Parameter name to look up.
+ * \param str_len Parameter name length.
+ * \param tbl Parameter table.
+ * \param unique Indication if output is unique result.
+ *
+ * \retval >=0 looked up parameter position in \a tbl.
+ * \retval err if error.
+ */
+int best_param(const char *str, const size_t str_len, const param_t *tbl,
+ bool *unique);
+
+char *get_reverse_name(const char *name);
+
+char *get_fqd_name(const char *name);
+
+int params_parse_class(const char *value, uint16_t *rclass);
+
+int params_parse_type(const char *value, uint16_t *rtype, int64_t *serial,
+ bool *notify);
+
+int params_parse_server(const char *value, list_t *servers, const char *def_port);
+
+int params_parse_wait(const char *value, int32_t *dst);
diff --git a/src/utils/common/resolv.c b/src/utils/common/resolv.c
new file mode 100644
index 0000000..a5aad14
--- /dev/null
+++ b/src/utils/common/resolv.c
@@ -0,0 +1,208 @@
+/* Copyright (C) 2016 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "utils/common/resolv.h"
+#include "utils/common/msg.h"
+#include "utils/common/params.h"
+#include "libknot/libknot.h"
+#include "contrib/ucw/lists.h"
+
+#define RESOLV_FILE "/etc/resolv.conf"
+
+srv_info_t* parse_nameserver(const char *str, const char *def_port)
+{
+ char *host = NULL, *port = NULL;
+ const char *addr = NULL, *sep = NULL;
+ size_t addr_len = 0;
+ char separator = ':';
+
+ if (str == NULL || def_port == NULL) {
+ DBG_NULL;
+ return NULL;
+ }
+
+ const size_t str_len = strlen(str);
+ const char *str_end = str + str_len;
+
+ // [address]:port notation.
+ if (*str == '[') {
+ addr = str + 1;
+ const char *addr_end = strchr(addr, ']');
+ // Missing closing bracket -> stop processing.
+ if (addr_end == NULL) {
+ return NULL;
+ }
+ addr_len = addr_end - addr;
+ str += 1 + addr_len + 1;
+ // Address@port notation.
+ } else if ((sep = strchr(str, '@')) != NULL) {
+ addr = str;
+ addr_len = sep - addr;
+ str += addr_len;
+ separator = '@';
+ // Address#port notation.
+ } else if ((sep = strchr(str, '#')) != NULL) {
+ addr = str;
+ addr_len = sep - addr;
+ str += addr_len;
+ separator = '#';
+ // IPv4:port notation.
+ } else if ((sep = strchr(str, ':')) != NULL) {
+ addr = str;
+ // Not IPv4 address -> no port.
+ if (strchr(sep + 1, ':') != NULL) {
+ addr_len = str_len;
+ str = str_end;
+ } else {
+ addr_len = sep - addr;
+ str += addr_len;
+ }
+ // No port specified.
+ } else {
+ addr = str;
+ addr_len = str_len;
+ str = str_end;
+ }
+
+ // Process port.
+ if (str < str_end) {
+ // Check port separator.
+ if (*str != separator) {
+ return NULL;
+ }
+ str++;
+
+ // Check for missing port.
+ if (str >= str_end) {
+ return NULL;
+ }
+
+ port = strdup(str);
+ } else {
+ port = strdup(def_port);
+ }
+
+ host = strndup(addr, addr_len);
+
+ // Create server structure.
+ srv_info_t *server = srv_info_create(host, port);
+
+ free(host);
+ free(port);
+
+ return server;
+}
+
+static size_t get_resolv_nameservers(list_t *servers, const char *def_port)
+{
+ char line[512];
+
+ // Open config file.
+ FILE *f = fopen(RESOLV_FILE, "r");
+ if (f == NULL) {
+ return 0;
+ }
+
+ // Read lines from config file.
+ while (fgets(line, sizeof(line), f) != NULL) {
+ size_t len;
+ char *pos = line;
+ char *option, *value;
+
+ // Find leading white characters.
+ len = strspn(pos, SEP_CHARS);
+ pos += len;
+
+ // Start of the first token.
+ option = pos;
+
+ // Find length of the token.
+ len = strcspn(pos, SEP_CHARS);
+ pos += len;
+
+ // Check if the token is not empty.
+ if (len == 0) {
+ continue;
+ }
+
+ // Find separating white characters.
+ len = strspn(pos, SEP_CHARS);
+ pos += len;
+
+ // Check if there is a separation between tokens.
+ if (len == 0) {
+ continue;
+ }
+
+ // Copy of the second token.
+ value = strndup(pos, strcspn(pos, SEP_CHARS));
+
+ // Process value with respect to option name.
+ if (strncmp(option, "nameserver", strlen("nameserver")) == 0) {
+ srv_info_t *server;
+
+ server = parse_nameserver(value, def_port);
+
+ // If value is correct, add nameserver to the list.
+ if (server != NULL) {
+ add_tail(servers, (node_t *)server);
+ }
+ }
+
+ // Drop value string.
+ free(value);
+ }
+
+ // Close config file.
+ fclose(f);
+
+ // Return number of servers.
+ return list_size(servers);
+}
+
+void get_nameservers(list_t *servers, const char *def_port)
+{
+ if (servers == NULL || def_port == NULL) {
+ DBG_NULL;
+ return;
+ }
+
+ // Initialize list of servers.
+ init_list(servers);
+
+ // Read nameservers from resolv file or use the default ones.
+ if (get_resolv_nameservers(servers, def_port) == 0) {
+ srv_info_t *server;
+
+ // Add default ipv6 nameservers.
+ server = srv_info_create(DEFAULT_IPV6_NAME, def_port);
+
+ if (server != NULL) {
+ add_tail(servers, (node_t *)server);
+ }
+
+ // Add default ipv4 nameservers.
+ server = srv_info_create(DEFAULT_IPV4_NAME, def_port);
+
+ if (server != NULL) {
+ add_tail(servers, (node_t *)server);
+ }
+ }
+}
diff --git a/src/utils/common/resolv.h b/src/utils/common/resolv.h
new file mode 100644
index 0000000..fb751d1
--- /dev/null
+++ b/src/utils/common/resolv.h
@@ -0,0 +1,24 @@
+/* Copyright (C) 2018 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/>.
+ */
+
+#pragma once
+
+#include "utils/common/netio.h"
+#include "contrib/ucw/lists.h"
+
+srv_info_t* parse_nameserver(const char *str, const char *def_port);
+
+void get_nameservers(list_t *servers, const char *def_port);
diff --git a/src/utils/common/sign.c b/src/utils/common/sign.c
new file mode 100644
index 0000000..84284d3
--- /dev/null
+++ b/src/utils/common/sign.c
@@ -0,0 +1,109 @@
+/* Copyright (C) 2017 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 <string.h>
+
+#include "utils/common/sign.h"
+#include "libknot/errcode.h"
+#include "libknot/tsig-op.h"
+
+int sign_context_init_tsig(sign_context_t *ctx, const knot_tsig_key_t *key)
+{
+ if (!ctx || !key) {
+ return KNOT_EINVAL;
+ }
+
+ size_t digest_size = dnssec_tsig_algorithm_size(key->algorithm);
+ if (digest_size == 0) {
+ return KNOT_EINVAL;
+ }
+
+ uint8_t *digest = calloc(1, digest_size);
+ if (!digest) {
+ return KNOT_ENOMEM;
+ }
+
+ ctx->digest_size = digest_size;
+ ctx->digest = digest;
+ ctx->tsig_key = key;
+
+ return KNOT_EOK;
+}
+
+void sign_context_deinit(sign_context_t *ctx)
+{
+ if (!ctx) {
+ return;
+ }
+
+ free(ctx->digest);
+
+ memset(ctx, 0, sizeof(*ctx));
+}
+
+int sign_packet(knot_pkt_t *pkt, sign_context_t *sign_ctx)
+{
+ if (pkt == NULL || sign_ctx == NULL || sign_ctx->digest == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ uint8_t *wire = pkt->wire;
+ size_t *wire_size = &pkt->size;
+ size_t max_size = pkt->max_size;
+
+ int ret = knot_pkt_reserve(pkt, knot_tsig_wire_size(sign_ctx->tsig_key));
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ return knot_tsig_sign(wire, wire_size, max_size, NULL, 0,
+ sign_ctx->digest, &sign_ctx->digest_size,
+ sign_ctx->tsig_key, 0, 0);
+}
+
+int verify_packet(const knot_pkt_t *pkt, const sign_context_t *sign_ctx)
+{
+ if (pkt == NULL || sign_ctx == NULL || sign_ctx->digest == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ const uint8_t *wire = pkt->wire;
+ const size_t *wire_size = &pkt->size;
+
+ if (pkt->tsig_rr == NULL) {
+ return KNOT_ENOTSIG;
+ }
+
+ int ret = knot_tsig_client_check(pkt->tsig_rr, wire, *wire_size,
+ sign_ctx->digest, sign_ctx->digest_size,
+ sign_ctx->tsig_key, 0);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ switch (knot_tsig_rdata_error(pkt->tsig_rr)) {
+ case KNOT_RCODE_BADSIG:
+ return KNOT_TSIG_EBADSIG;
+ case KNOT_RCODE_BADKEY:
+ return KNOT_TSIG_EBADKEY;
+ case KNOT_RCODE_BADTIME:
+ return KNOT_TSIG_EBADTIME;
+ case KNOT_RCODE_BADTRUNC:
+ return KNOT_TSIG_EBADTRUNC;
+ default:
+ return KNOT_EOK;
+ }
+}
diff --git a/src/utils/common/sign.h b/src/utils/common/sign.h
new file mode 100644
index 0000000..52f41ef
--- /dev/null
+++ b/src/utils/common/sign.h
@@ -0,0 +1,63 @@
+/* Copyright (C) 2015 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/>.
+ */
+
+#pragma once
+
+#include "libknot/packet/pkt.h"
+#include "libknot/tsig.h"
+
+/*!
+ * \brief Holds data required between signing and signature verification.
+ */
+struct sign_context {
+ size_t digest_size;
+ uint8_t *digest;
+ const knot_tsig_key_t *tsig_key;
+};
+
+typedef struct sign_context sign_context_t;
+
+/*!
+ * \brief Initialize signing context for TSIG.
+ */
+int sign_context_init_tsig(sign_context_t *ctx, const knot_tsig_key_t *key);
+
+/*!
+ * \brief Clean up signing context.
+ *
+ * \param ctx Sign context.
+ */
+void sign_context_deinit(sign_context_t *ctx);
+
+/*!
+ * \brief Signs outgoing DNS packet.
+ *
+ * \param pkt Packet to sign.
+ * \param sign_ctx Signing context.
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int sign_packet(knot_pkt_t *pkt, sign_context_t *sign_ctx);
+
+/*!
+ * \brief Verifies signature for incoming DNS packet.
+ *
+ * \param pkt Packet verify sign.
+ * \param sign_ctx Signing context.
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int verify_packet(const knot_pkt_t *pkt, const sign_context_t *sign_ctx);
diff --git a/src/utils/common/tls.c b/src/utils/common/tls.c
new file mode 100644
index 0000000..57dfc9f
--- /dev/null
+++ b/src/utils/common/tls.c
@@ -0,0 +1,681 @@
+/* Copyright (C) 2020 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 <assert.h>
+#include <arpa/inet.h>
+#include <stdbool.h>
+#include <string.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/ocsp.h>
+#include <gnutls/x509.h>
+#include <poll.h>
+
+#include "utils/common/tls.h"
+#include "utils/common/cert.h"
+#include "utils/common/msg.h"
+#include "contrib/base64.h"
+#include "libknot/errcode.h"
+
+void tls_params_init(tls_params_t *params)
+{
+ if (params == NULL) {
+ return;
+ }
+
+ memset(params, 0, sizeof(*params));
+
+ init_list(&params->ca_files);
+ init_list(&params->pins);
+}
+
+int tls_params_copy(tls_params_t *dst, const tls_params_t *src)
+{
+ if (dst == NULL || src == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ tls_params_init(dst);
+
+ dst->enable = src->enable;
+ dst->system_ca = src->system_ca;
+ if (src->hostname != NULL) {
+ dst->hostname = strdup(src->hostname);
+ if (dst->hostname == NULL) {
+ tls_params_clean(dst);
+ return KNOT_ENOMEM;
+ }
+ }
+
+ if (src->sni != NULL) {
+ dst->sni = strdup(src->sni);
+ if (dst->sni == NULL) {
+ tls_params_clean(dst);
+ return KNOT_ENOMEM;
+ }
+ }
+
+ if (src->keyfile != NULL) {
+ dst->keyfile = strdup(src->keyfile);
+ if (dst->keyfile == NULL) {
+ tls_params_clean(dst);
+ return KNOT_ENOMEM;
+ }
+ }
+
+ if (src->certfile != NULL) {
+ dst->certfile = strdup(src->certfile);
+ if (dst->certfile == NULL) {
+ tls_params_clean(dst);
+ return KNOT_ENOMEM;
+ }
+ }
+
+ dst->ocsp_stapling = src->ocsp_stapling;
+
+ ptrnode_t *n;
+ WALK_LIST(n, src->ca_files) {
+ char *src_file = (char *)n->d;
+ char *file = strdup(src_file);
+ if (file == NULL || ptrlist_add(&dst->ca_files, file, NULL) == NULL) {
+ tls_params_clean(dst);
+ return KNOT_ENOMEM;
+ }
+ }
+ WALK_LIST(n, src->pins) {
+ uint8_t *src_pin = (uint8_t *)n->d;
+ uint8_t *pin = malloc(1 + src_pin[0]);
+ if (pin == NULL || ptrlist_add(&dst->pins, pin, NULL) == NULL) {
+ tls_params_clean(dst);
+ return KNOT_ENOMEM;
+ }
+ memcpy(pin, src_pin, 1 + src_pin[0]);
+ }
+
+ return KNOT_EOK;
+}
+
+void tls_params_clean(tls_params_t *params)
+{
+ if (params == NULL) {
+ return;
+ }
+
+ ptrnode_t *node, *nxt;
+ WALK_LIST_DELSAFE(node, nxt, params->ca_files) {
+ free(node->d);
+ }
+ ptrlist_free(&params->ca_files, NULL);
+
+ WALK_LIST_DELSAFE(node, nxt, params->pins) {
+ free(node->d);
+ }
+ ptrlist_free(&params->pins, NULL);
+
+ free(params->hostname);
+ free(params->sni);
+ free(params->keyfile);
+ free(params->certfile);
+
+ memset(params, 0, sizeof(*params));
+}
+
+static bool check_pin(const uint8_t *cert_pin, size_t cert_pin_len, const list_t *pins)
+{
+ if (EMPTY_LIST(*pins)) {
+ return false;
+ }
+
+ ptrnode_t *n;
+ WALK_LIST(n, *pins) {
+ uint8_t *pin = (uint8_t *)n->d;
+ if (pin[0] == cert_pin_len &&
+ memcmp(cert_pin, &pin[1], cert_pin_len) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool verify_ocsp(gnutls_session_t *session)
+{
+ bool ret = false;
+
+ gnutls_ocsp_resp_t ocsp_resp;
+ bool deinit_ocsp_resp = false;
+
+ gnutls_x509_crt_t server_cert;
+ bool deinit_server_cert = false;
+
+ gnutls_certificate_credentials_t xcred;
+ bool deinit_xcreds = false;
+
+ gnutls_x509_crt_t issuer_cert;
+ bool deinit_issuer_cert = false;
+
+ gnutls_datum_t ocsp_resp_raw;
+ if (gnutls_ocsp_status_request_get(*session, &ocsp_resp_raw) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, unable to retrieve stapled OCSP data\n");
+ goto cleanup;
+ }
+ if (gnutls_ocsp_resp_init(&ocsp_resp) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, unable to init OCSP data\n");
+ goto cleanup;
+ }
+ deinit_ocsp_resp = true;
+ if (gnutls_ocsp_resp_import(ocsp_resp, &ocsp_resp_raw) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, unable to import OCSP response\n");
+ goto cleanup;
+ }
+
+ unsigned int cert_list_size = 0;
+ const gnutls_datum_t *cert_list = gnutls_certificate_get_peers(*session, &cert_list_size);
+ if (cert_list_size == 0) {
+ WARN("TLS, unable to retrieve peer certs when verifying OCSP\n");
+ goto cleanup;
+ }
+ if (gnutls_x509_crt_init(&server_cert) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, unable to init server cert when verifying OCSP\n");
+ goto cleanup;
+ }
+ deinit_server_cert = true;
+ if (gnutls_x509_crt_import(server_cert, &cert_list[0], GNUTLS_X509_FMT_DER) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, unable to import server cert when verifying OCSP\n");
+ goto cleanup;
+ }
+
+ if (gnutls_certificate_allocate_credentials(&xcred) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, unable to allocate credentials when verifying OCSP\n");
+ goto cleanup;
+ }
+ deinit_xcreds = true;
+
+ if (gnutls_certificate_get_issuer(xcred, server_cert, &issuer_cert, 0) != GNUTLS_E_SUCCESS) {
+ if (cert_list_size < 2) {
+ WARN("TLS, unable to get issuer (CA) cert when verifying OCSP\n");
+ goto cleanup;
+ }
+ if (gnutls_x509_crt_init(&issuer_cert) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, unable to init issuer cert when verifying OCSP\n");
+ goto cleanup;
+ }
+ deinit_issuer_cert = true;
+ if (gnutls_x509_crt_import(issuer_cert, &cert_list[1], GNUTLS_X509_FMT_DER) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, unable to import issuer cert when verifying OCSP\n");
+ goto cleanup;
+ }
+ }
+
+ unsigned int status;
+ time_t this_upd, next_upd, now = time(0);
+ if (gnutls_ocsp_resp_check_crt(ocsp_resp, 0, server_cert) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, OCSP response either empty or not for provided server cert\n");
+ goto cleanup;
+ }
+ if (gnutls_ocsp_resp_verify_direct(ocsp_resp, issuer_cert, &status, 0) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, unable to verify OCSP response against issuer cert\n");
+ goto cleanup;
+ }
+ if (status != 0) {
+ WARN("TLS, got a non-zero status when verifying OCSP response against issuer cert\n");
+ goto cleanup;
+ }
+ if (gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, &status,
+ &this_upd, &next_upd, NULL, NULL) != GNUTLS_E_SUCCESS) {
+ WARN("TLS, error reading OCSP response\n");
+ goto cleanup;
+ }
+ if (status == GNUTLS_OCSP_CERT_REVOKED) {
+ WARN("TLS, OCSP data shows that cert was revoked\n");
+ goto cleanup;
+ }
+ if (next_upd == -1) {
+ tls_ctx_t *ctx = gnutls_session_get_ptr(*session);
+ assert(now >= this_upd);
+ assert(ctx->params->ocsp_stapling > 0);
+ if (now - this_upd > ctx->params->ocsp_stapling) {
+ WARN("TLS, OCSP response is out of date.\n");
+ goto cleanup;
+ }
+ } else {
+ if (next_upd < now) {
+ WARN("TLS, a newer OCSP response is available but was not sent\n");
+ goto cleanup;
+ }
+ }
+
+ // Only if we get here is the ocsp result completely valid.
+ ret = true;
+
+cleanup:
+ if (deinit_issuer_cert) {
+ gnutls_x509_crt_deinit(issuer_cert);
+ }
+ if (deinit_xcreds) {
+ gnutls_certificate_free_credentials(xcred);
+ }
+ if (deinit_server_cert) {
+ gnutls_x509_crt_deinit(server_cert);
+ }
+ if (deinit_ocsp_resp) {
+ gnutls_ocsp_resp_deinit(ocsp_resp);
+ }
+
+ return ret;
+}
+
+static int check_certificates(gnutls_session_t session, const list_t *pins)
+{
+ if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
+ DBG("TLS, invalid certificate type\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+ unsigned cert_list_size;
+ const gnutls_datum_t *cert_list =
+ gnutls_certificate_get_peers(session, &cert_list_size);
+ if (cert_list == NULL || cert_list_size == 0) {
+ DBG("TLS, empty certificate list\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+ size_t matches = 0;
+
+ DBG("TLS, received certificate hierarchy:\n");
+ for (int i = 0; i < cert_list_size; i++) {
+ gnutls_x509_crt_t cert;
+ int ret = gnutls_x509_crt_init(&cert);
+ if (ret != GNUTLS_E_SUCCESS) {
+ return ret;
+ }
+
+ ret = gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER);
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_x509_crt_deinit(cert);
+ return ret;
+ }
+
+ gnutls_datum_t cert_name = { 0 };
+ ret = gnutls_x509_crt_get_dn2(cert, &cert_name);
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_x509_crt_deinit(cert);
+ return ret;
+ }
+ DBG(" #%i, %s\n", i + 1, cert_name.data);
+ gnutls_free(cert_name.data);
+
+ uint8_t cert_pin[CERT_PIN_LEN] = { 0 };
+ ret = cert_get_pin(cert, cert_pin, sizeof(cert_pin));
+ if (ret != KNOT_EOK) {
+ gnutls_x509_crt_deinit(cert);
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+ // Check if correspond to a specified PIN.
+ bool match = check_pin(cert_pin, sizeof(cert_pin), pins);
+ if (match) {
+ matches++;
+ }
+
+ uint8_t *txt_pin;
+ ret = knot_base64_encode_alloc(cert_pin, sizeof(cert_pin), &txt_pin);
+ if (ret < 0) {
+ gnutls_x509_crt_deinit(cert);
+ return ret;
+ }
+ DBG(" SHA-256 PIN: %.*s%s\n", ret, txt_pin, match ? ", MATCH" : "");
+ free(txt_pin);
+
+ gnutls_x509_crt_deinit(cert);
+ }
+
+ if (matches > 0) {
+ return GNUTLS_E_SUCCESS;
+ } else if (EMPTY_LIST(*pins)) {
+ DBG("TLS, skipping certificate PIN check\n");
+ return GNUTLS_E_SUCCESS;
+ } else {
+ DBG("TLS, no certificate PIN match\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+}
+
+static bool do_verification(const tls_params_t *params)
+{
+ return params->hostname != NULL || params->system_ca ||
+ !EMPTY_LIST(params->ca_files) || params->ocsp_stapling > 0;
+}
+
+static int verify_certificate(gnutls_session_t session)
+{
+ tls_ctx_t *ctx = gnutls_session_get_ptr(session);
+
+ // Check for pinned certificates and print certificate hierarchy.
+ int ret = check_certificates(session, &ctx->params->pins);
+ if (ret != GNUTLS_E_SUCCESS) {
+ return ret;
+ }
+
+ if (!do_verification(ctx->params)) {
+ DBG("TLS, skipping certificate verification\n");
+ return GNUTLS_E_SUCCESS;
+ }
+
+ if (ctx->params->ocsp_stapling > 0 && !verify_ocsp(&session)) {
+ WARN("TLS, failed to validate required OCSP data\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+ // Set server certificate check.
+ gnutls_typed_vdata_st data[2] = {
+ { .type = GNUTLS_DT_KEY_PURPOSE_OID,
+ .data = (void *)GNUTLS_KP_TLS_WWW_SERVER },
+ { .type = GNUTLS_DT_DNS_HOSTNAME,
+ .data = (void *)ctx->params->hostname }
+ };
+ size_t data_count = (ctx->params->hostname != NULL) ? 2 : 1;
+ if (data_count == 1) {
+ WARN("TLS, no hostname provided, will not verify certificate owner\n")
+ }
+
+ unsigned int status;
+ ret = gnutls_certificate_verify_peers(session, data, data_count, &status);
+ if (ret != GNUTLS_E_SUCCESS) {
+ WARN("TLS, failed to verify peer certificate\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+ gnutls_datum_t msg;
+ ret = gnutls_certificate_verification_status_print(
+ status, gnutls_certificate_type_get(session), &msg, 0);
+ if (ret == GNUTLS_E_SUCCESS) {
+ DBG("TLS, %s\n", msg.data);
+ }
+ gnutls_free(msg.data);
+
+ if (status != 0) {
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+ return GNUTLS_E_SUCCESS;
+}
+
+int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params, int wait)
+{
+ if (ctx == NULL || params == NULL || !params->enable) {
+ return KNOT_EINVAL;
+ }
+
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->params = params;
+ ctx->wait = wait;
+ ctx->sockfd = -1;
+
+ int ret = gnutls_certificate_allocate_credentials(&ctx->credentials);
+ if (ret != GNUTLS_E_SUCCESS) {
+ return KNOT_ENOMEM;
+ }
+
+ // Import system certificates.
+ if (ctx->params->system_ca ||
+ (ctx->params->hostname != NULL && EMPTY_LIST(ctx->params->ca_files))) {
+ ret = gnutls_certificate_set_x509_system_trust(ctx->credentials);
+ if (ret < 0) {
+ WARN("TLS, failed to import system certificates (%s)\n",
+ gnutls_strerror_name(ret));
+ return KNOT_ERROR;
+ } else {
+ DBG("TLS, imported %i system certificates\n", ret);
+ }
+ }
+
+ // Import provided certificate files.
+ ptrnode_t *n;
+ WALK_LIST(n, ctx->params->ca_files) {
+ const char *file = (char *)n->d;
+ ret = gnutls_certificate_set_x509_trust_file(ctx->credentials, file,
+ GNUTLS_X509_FMT_PEM);
+ if (ret < 0) {
+ WARN("TLS, failed to import certificate file '%s' (%s)\n",
+ file, gnutls_strerror_name(ret));
+ return KNOT_ERROR;
+ } else {
+ DBG("TLS, imported %i certificates from '%s'\n", ret, file);
+ }
+ }
+
+ gnutls_certificate_set_verify_function(ctx->credentials, verify_certificate);
+
+ // Setup client keypair if specified. Both key and cert files must be provided.
+ if (params->keyfile != NULL && params->certfile != NULL) {
+ // First, try PEM.
+ ret = gnutls_certificate_set_x509_key_file(ctx->credentials,
+ params->certfile, params->keyfile, GNUTLS_X509_FMT_PEM);
+ if (ret != GNUTLS_E_SUCCESS) {
+ // If PEM didn't work, try DER.
+ ret = gnutls_certificate_set_x509_key_file(ctx->credentials,
+ params->certfile, params->keyfile, GNUTLS_X509_FMT_DER);
+ }
+
+ if (ret != GNUTLS_E_SUCCESS) {
+ WARN("TLS, failed to add client certfile '%s' and keyfile '%s'\n",
+ params->certfile, params->keyfile);
+ return KNOT_ERROR;
+ } else {
+ DBG("TLS, added client certfile '%s' and keyfile '%s'\n",
+ params->certfile, params->keyfile);
+ }
+ } else if (params->keyfile != NULL) {
+ WARN("TLS, cannot use client keyfile without a certfile\n");
+ return KNOT_ERROR;
+ } else if (params->certfile != NULL) {
+ WARN("TLS, cannot use client certfile without a keyfile\n");
+ return KNOT_ERROR;
+ }
+
+ return KNOT_EOK;
+}
+
+int tls_ctx_connect(tls_ctx_t *ctx, int sockfd, const char *remote)
+{
+ if (ctx == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret = gnutls_init(&ctx->session, GNUTLS_CLIENT | GNUTLS_NONBLOCK);
+ if (ret != GNUTLS_E_SUCCESS) {
+ return KNOT_NET_ECONNECT;
+ }
+
+ ret = gnutls_set_default_priority(ctx->session);
+ if (ret != GNUTLS_E_SUCCESS) {
+ return KNOT_NET_ECONNECT;
+ }
+
+ ret = gnutls_credentials_set(ctx->session, GNUTLS_CRD_CERTIFICATE,
+ ctx->credentials);
+ if (ret != GNUTLS_E_SUCCESS) {
+ return KNOT_NET_ECONNECT;
+ }
+
+ if (remote != NULL) {
+ ret = gnutls_server_name_set(ctx->session, GNUTLS_NAME_DNS, remote,
+ strlen(remote));
+ if (ret != GNUTLS_E_SUCCESS) {
+ return KNOT_NET_ECONNECT;
+ }
+ }
+
+ gnutls_session_set_ptr(ctx->session, ctx);
+ gnutls_transport_set_int(ctx->session, sockfd);
+ gnutls_handshake_set_timeout(ctx->session, 1000 * ctx->wait);
+
+ // Initialize poll descriptor structure.
+ struct pollfd pfd = {
+ .fd = sockfd,
+ .events = POLLIN,
+ .revents = 0,
+ };
+
+ // Perform the TLS handshake
+ do {
+ ret = gnutls_handshake(ctx->session);
+ if (ret != GNUTLS_E_SUCCESS && gnutls_error_is_fatal(ret) == 0) {
+ if (poll(&pfd, 1, 1000 * ctx->wait) != 1) {
+ WARN("TLS, peer took too long to respond\n");
+ return KNOT_NET_ETIMEOUT;
+ }
+ }
+ } while (ret != GNUTLS_E_SUCCESS && gnutls_error_is_fatal(ret) == 0);
+ if (ret != GNUTLS_E_SUCCESS) {
+ WARN("TLS, handshake failed (%s)\n", gnutls_strerror(ret));
+ tls_ctx_close(ctx);
+ return KNOT_NET_ESOCKET;
+ }
+
+ // Save the socket descriptor.
+ ctx->sockfd = sockfd;
+
+ return KNOT_EOK;
+}
+
+int tls_ctx_send(tls_ctx_t *ctx, const uint8_t *buf, const size_t buf_len)
+{
+ if (ctx == NULL || buf == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ uint16_t msg_len = htons(buf_len);
+
+ gnutls_record_cork(ctx->session);
+
+ if (gnutls_record_send(ctx->session, &msg_len, sizeof(msg_len)) <= 0) {
+ WARN("TLS, failed to send\n");
+ return KNOT_NET_ESEND;
+ }
+ if (gnutls_record_send(ctx->session, buf, buf_len) <= 0) {
+ WARN("TLS, failed to send\n");
+ return KNOT_NET_ESEND;
+ }
+
+ while (gnutls_record_check_corked(ctx->session) > 0) {
+ int ret = gnutls_record_uncork(ctx->session, 0);
+ if (ret < 0 && gnutls_error_is_fatal(ret) != 0) {
+ WARN("TLS, failed to send (%s)\n", gnutls_strerror(ret));
+ return KNOT_NET_ESEND;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int tls_ctx_receive(tls_ctx_t *ctx, uint8_t *buf, const size_t buf_len)
+{
+ if (ctx == NULL || buf == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Initialize poll descriptor structure.
+ struct pollfd pfd = {
+ .fd = ctx->sockfd,
+ .events = POLLIN,
+ .revents = 0,
+ };
+
+ uint32_t total = 0;
+ uint16_t msg_len = 0;
+
+ // Receive message header.
+ while (total < sizeof(msg_len)) {
+ ssize_t ret = gnutls_record_recv(ctx->session,
+ (uint8_t *)&msg_len + total,
+ sizeof(msg_len) - total);
+ if (ret > 0) {
+ total += ret;
+ } else if (ret == 0) {
+ WARN("TLS, peer has closed the connection\n");
+ return KNOT_NET_ERECV;
+ } else if (gnutls_error_is_fatal(ret) != 0) {
+ WARN("TLS, failed to receive reply (%s)\n",
+ gnutls_strerror(ret));
+ return KNOT_NET_ERECV;
+ } else if (poll(&pfd, 1, 1000 * ctx->wait) != 1) {
+ WARN("TLS, peer took too long to respond\n");
+ return KNOT_NET_ETIMEOUT;
+ }
+ }
+
+ // Convert number to host format.
+ msg_len = ntohs(msg_len);
+ if (msg_len > buf_len) {
+ return KNOT_ESPACE;
+ }
+
+ total = 0;
+
+ // Receive data over TLS
+ while (total < msg_len) {
+ ssize_t ret = gnutls_record_recv(ctx->session, buf + total,
+ msg_len - total);
+ if (ret > 0) {
+ total += ret;
+ } else if (ret == 0) {
+ WARN("TLS, peer has closed the connection\n");
+ return KNOT_NET_ERECV;
+ } else if (gnutls_error_is_fatal(ret) != 0) {
+ WARN("TLS, failed to receive reply (%s)\n",
+ gnutls_strerror(ret));
+ return KNOT_NET_ERECV;
+ } else if (poll(&pfd, 1, 1000 * ctx->wait) != 1) {
+ WARN("TLS, peer took too long to respond\n");
+ return KNOT_NET_ETIMEOUT;
+ }
+ }
+
+ return total;
+}
+
+void tls_ctx_close(tls_ctx_t *ctx)
+{
+ if (ctx == NULL || ctx->session == NULL) {
+ return;
+ }
+
+ gnutls_bye(ctx->session, GNUTLS_SHUT_RDWR);
+ gnutls_deinit(ctx->session);
+}
+
+void tls_ctx_deinit(tls_ctx_t *ctx)
+{
+ if (ctx == NULL) {
+ return;
+ }
+
+ if (ctx->credentials != NULL) {
+ gnutls_certificate_free_credentials(ctx->credentials);
+ }
+}
+
+void print_tls(const tls_ctx_t *ctx)
+{
+ if (ctx == NULL || ctx->session == NULL) {
+ return;
+ }
+
+ char *msg = gnutls_session_get_desc(ctx->session);
+ printf(";; TLS session %s\n", msg);
+ gnutls_free(msg);
+}
diff --git a/src/utils/common/tls.h b/src/utils/common/tls.h
new file mode 100644
index 0000000..2e7ecba
--- /dev/null
+++ b/src/utils/common/tls.h
@@ -0,0 +1,70 @@
+/* Copyright (C) 2019 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/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <gnutls/gnutls.h>
+
+#include "contrib/ucw/lists.h"
+
+/*! \brief TLS parameters. */
+typedef struct {
+ /*! Use TLS indicator. */
+ bool enable;
+ /*! Import system certificates indicator. */
+ bool system_ca;
+ /*! Certificate files to import. */
+ list_t ca_files;
+ /*! Pinned certificates. */
+ list_t pins;
+ /*! Required server hostname. */
+ char *hostname;
+ /*! Optional server name indicator. */
+ char *sni;
+ /*! Optional client keyfile name. */
+ char *keyfile;
+ /*! Optional client certfile name. */
+ char *certfile;
+ /*! Optional validity of stapled OCSP response for the server cert. */
+ uint32_t ocsp_stapling;
+} tls_params_t;
+
+/*! \brief TLS context. */
+typedef struct {
+ /*! TLS handshake timeout. */
+ int wait;
+ /*! Socket descriptor. */
+ int sockfd;
+ /*! TLS parameters. */
+ const tls_params_t *params;
+ /*! GnuTLS session handle. */
+ gnutls_session_t session;
+ /*! GnuTLS credentials handle. */
+ gnutls_certificate_credentials_t credentials;
+} tls_ctx_t;
+
+void tls_params_init(tls_params_t *params);
+int tls_params_copy(tls_params_t *dst, const tls_params_t *src);
+void tls_params_clean(tls_params_t *params);
+
+int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params, int wait);
+int tls_ctx_connect(tls_ctx_t *ctx, int sockfd, const char *remote);
+int tls_ctx_send(tls_ctx_t *ctx, const uint8_t *buf, const size_t buf_len);
+int tls_ctx_receive(tls_ctx_t *ctx, uint8_t *buf, const size_t buf_len);
+void tls_ctx_close(tls_ctx_t *ctx);
+void tls_ctx_deinit(tls_ctx_t *ctx);
+void print_tls(const tls_ctx_t *ctx);
diff --git a/src/utils/common/token.c b/src/utils/common/token.c
new file mode 100644
index 0000000..aca994f
--- /dev/null
+++ b/src/utils/common/token.c
@@ -0,0 +1,115 @@
+/* Copyright (C) 2017 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils/common/token.h"
+#include "utils/common/msg.h"
+#include "libknot/libknot.h"
+#include "contrib/ctype.h"
+
+int tok_scan(const char* lp, const char **tbl, int *lpm)
+{
+ if (lp == NULL || tbl == NULL || *tbl == NULL || lpm == NULL) {
+ DBG_NULL;
+ return -1;
+ }
+
+ const char *prefix = lp; /* Ptr to line start. */
+ int i = 0, pl = 1; /* Match index, prefix length. */
+ unsigned char len = 0; /* Read length. */
+ for(;;) {
+ const char *tok = tbl[i];
+ if (*lp == '\0' || is_space(*lp)) {
+ if (tok && TOK_L(tok) == len) { /* Consumed whole w? */
+ return i; /* Identifier */
+ } else { /* Word is shorter than cmd? */
+ break;
+ }
+ }
+
+ /* Find next prefix match. */
+ ++len;
+ while (tok) {
+ if (TOK_L(tok) >= len) { /* Is prefix of current token */
+ if (*lp < tok[pl]) { /* Terminate early. */
+ tok = NULL;
+ break; /* No match could be found. */
+ }
+ if (*lp == tok[pl]) { /* Match */
+ if(lpm) *lpm = i;
+ ++pl;
+ break;
+ }
+ }
+
+ /* No early cut, no match - seek next. */
+ while ((tok = tbl[++i]) != NULL) {
+ if (TOK_L(tok) >= len &&
+ memcmp(TOK_S(tok), prefix, len) == 0) {
+ break;
+ }
+ }
+ }
+
+ if (tok == NULL) {
+ break; /* All tokens exhausted. */
+ } else {
+ ++lp; /* Next char */
+ }
+ }
+
+ return -1;
+}
+
+int tok_find(const char *lp, const char **tbl)
+{
+ if (lp == NULL || tbl == NULL || *tbl == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ int lpm = -1;
+ int bp = 0;
+ if ((bp = tok_scan(lp, tbl, &lpm)) < 0) {
+ if (lpm > -1) {
+ ERR("unexpected literal: '%s', did you mean '%s' ?\n",
+ lp, TOK_S(tbl[lpm]));
+ } else {
+ ERR("unexpected literal: '%s'\n", lp);
+ }
+
+ return KNOT_EPARSEFAIL;
+ }
+
+ return bp;
+}
+
+const char *tok_skipspace(const char *lp)
+{
+ if (lp == NULL) {
+ DBG_NULL;
+ return NULL;
+ }
+
+ while (is_space(*lp)) {
+ lp += 1;
+ }
+
+ return lp;
+}
diff --git a/src/utils/common/token.h b/src/utils/common/token.h
new file mode 100644
index 0000000..fab4ea1
--- /dev/null
+++ b/src/utils/common/token.h
@@ -0,0 +1,65 @@
+/* Copyright (C) 2018 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/>.
+ */
+
+#pragma once
+
+#include <stdio.h>
+
+/*!
+ * \brief Example of token table:
+ *
+ * \warning Table _must_ be lexicographically ordered.
+ *
+ * const char *tok_tbl[] = {
+ * // LEN STRING
+ * "\x4" "abcd",
+ * "\x5" "class",
+ * NULL // END
+ * }
+ */
+/*! \brief String part of the token. */
+#define TOK_S(x) ((x)+1)
+/*! \brief Len of the token. */
+#define TOK_L(x) ((unsigned char)(x)[0])
+
+/*!
+ * \brief Scan for matching token described by a match table.
+ *
+ * Table consists of strings, prefixed with 1B length.
+ *
+ * \param lp Pointer to current line.
+ * \param tbl Match description table.
+ * \param lpm Pointer to longest prefix match.
+ * \retval index to matching record.
+ * \retval -1 if no match is found, lpm may be set to longest prefix match.
+ */
+int tok_scan(const char* lp, const char **tbl, int *lpm);
+
+/*!
+ * \brief Find token from table in a line buffer.
+ * \param lp Pointer to current line.
+ * \param tbl Match description table.
+ * \retval index to matching record.
+ * \retval error code if no match is found
+ */
+int tok_find(const char *lp, const char **tbl);
+
+/*!
+ * \brief Return pointer to next non-blank character.
+ * \param lp Pointer to current line.
+ * \return ptr to next non-blank character.
+ */
+const char *tok_skipspace(const char *lp);