summaryrefslogtreecommitdiffstats
path: root/src/utils/kdig/kdig_params.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/utils/kdig/kdig_params.c2765
1 files changed, 2765 insertions, 0 deletions
diff --git a/src/utils/kdig/kdig_params.c b/src/utils/kdig/kdig_params.c
new file mode 100644
index 0000000..310e890
--- /dev/null
+++ b/src/utils/kdig/kdig_params.c
@@ -0,0 +1,2765 @@
+/* Copyright (C) 2024 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 <locale.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "utils/kdig/kdig_params.h"
+#include "utils/common/hex.h"
+#include "utils/common/msg.h"
+#include "utils/common/netio.h"
+#include "utils/common/params.h"
+#include "utils/common/resolv.h"
+#include "libknot/descriptor.h"
+#include "libknot/libknot.h"
+#include "contrib/base64.h"
+#include "contrib/sockaddr.h"
+#include "contrib/string.h"
+#include "contrib/strtonum.h"
+#include "contrib/time.h"
+#include "contrib/ucw/lists.h"
+#include "libdnssec/error.h"
+#include "libdnssec/random.h"
+
+#define PROGRAM_NAME "kdig"
+
+#define DEFAULT_RETRIES_DIG 2
+#define DEFAULT_TIMEOUT_DIG 5
+#define DEFAULT_ALIGNMENT_SIZE 128
+#define DEFAULT_TLS_OCSP_STAPLING (7 * 24 * 3600)
+
+#define BADCOOKIE_RETRY_MAX 10
+
+static const flags_t DEFAULT_FLAGS_DIG = {
+ .aa_flag = false,
+ .tc_flag = false,
+ .rd_flag = true,
+ .ra_flag = false,
+ .z_flag = false,
+ .ad_flag = true,
+ .cd_flag = false,
+ .do_flag = false
+};
+
+static const style_t DEFAULT_STYLE_DIG = {
+ .format = FORMAT_FULL,
+ .style = {
+ .wrap = false,
+ .show_class = true,
+ .show_ttl = true,
+ .verbose = false,
+ .original_ttl = false,
+ .empty_ttl = false,
+ .human_ttl = false,
+ .human_timestamp = true,
+ .generic = false,
+ .ascii_to_idn = name_to_idn
+ },
+ .show_query = false,
+ .show_header = true,
+ .show_section = true,
+ .show_edns = true,
+ .show_question = true,
+ .show_answer = true,
+ .show_authority = true,
+ .show_additional = true,
+ .show_tsig = true,
+ .show_footer = true
+};
+
+static int opt_multiline(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.wrap = true;
+ q->style.format = FORMAT_FULL;
+ q->style.show_header = true;
+ q->style.show_edns = true;
+ q->style.show_footer = true;
+ q->style.style.verbose = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nomultiline(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.wrap = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_short(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.format = FORMAT_DIG;
+ q->style.show_header = false;
+ q->style.show_edns = false;
+ q->style.show_footer = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_noshort(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.format = FORMAT_FULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_generic(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.generic = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nogeneric(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.generic = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_aaflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.aa_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noaaflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.aa_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_tcflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.tc_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_notcflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.tc_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_rdflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.rd_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nordflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.rd_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_raflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.ra_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noraflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.ra_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_zflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.z_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nozflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.z_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_adflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.ad_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noadflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.ad_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_cdflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.cd_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nocdflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.cd_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_doflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.do_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nodoflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.do_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_all(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_header = true;
+ q->style.show_section = true;
+ q->style.show_edns = true;
+ q->style.show_question = true;
+ q->style.show_answer = true;
+ q->style.show_authority = true;
+ q->style.show_additional = true;
+ q->style.show_tsig = true;
+ q->style.show_footer = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noall(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_header = false;
+ q->style.show_section = false;
+ q->style.show_edns = false;
+ q->style.show_query = false;
+ q->style.show_question = false;
+ q->style.show_answer = false;
+ q->style.show_authority = false;
+ q->style.show_additional = false;
+ q->style.show_tsig = false;
+ q->style.show_footer = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_qr(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_query = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noqr(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_query = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_header(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_header = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noheader(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_header = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_comments(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_section = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nocomments(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_section = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_opt(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_edns = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noopt(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_edns = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_opttext(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_edns_opt_text = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noopttext(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_edns_opt_text = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_question(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_question = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noquestion(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_question = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_optpresent(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.present_edns = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nooptpresent(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.present_edns = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_answer(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_answer = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noanswer(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_answer = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_authority(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_authority = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noauthority(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_authority = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_additional(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_additional = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noadditional(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_additional = false;
+ q->style.show_edns = false;
+ q->style.show_tsig = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_tsig(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_tsig = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_notsig(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_tsig = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_stats(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_footer = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nostats(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_footer = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_class(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.show_class = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noclass(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.show_class = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_ttl(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.show_ttl = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nottl(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.show_ttl = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_ignore(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->ignore_tc = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noignore(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->ignore_tc = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_crypto(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.hide_crypto = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_nocrypto(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.hide_crypto = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_tcp(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->protocol = PROTO_TCP;
+
+ return KNOT_EOK;
+}
+
+static int opt_notcp(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->protocol = PROTO_UDP;
+ return opt_ignore(arg, query);
+}
+
+static int opt_fastopen(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->fastopen = true;
+
+ return opt_tcp(arg, query);
+}
+
+static int opt_nofastopen(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->fastopen = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_keepopen(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->keepopen = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nokeepopen(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->keepopen = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_tls(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->tls.enable = true;
+ if (q->quic.enable) {
+ return KNOT_EOK;
+ }
+ return opt_tcp(arg, query);
+}
+
+static int opt_notls(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ tls_params_clean(&q->tls);
+ tls_params_init(&q->tls);
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_ca(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg == NULL) {
+ q->tls.system_ca = true;
+ return opt_tls(arg, query);
+ } else {
+ if (ptrlist_add(&q->tls.ca_files, strdup(arg), NULL) == NULL) {
+ return KNOT_ENOMEM;
+ }
+ return opt_tls(arg, query);
+ }
+}
+
+static int opt_notls_ca(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->tls.system_ca = false;
+
+ ptrnode_t *node, *nxt;
+ WALK_LIST_DELSAFE(node, nxt, q->tls.ca_files) {
+ free(node->d);
+ }
+ ptrlist_free(&q->tls.ca_files, NULL);
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_pin(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ uint8_t pin[64] = { 0 };
+
+ int ret = knot_base64_decode((const uint8_t *)arg, strlen(arg), pin, sizeof(pin));
+ if (ret < 0) {
+ ERR("invalid +tls-pin=%s", arg);
+ return ret;
+ } else if (ret != CERT_PIN_LEN) { // Check for 256-bit value.
+ ERR("invalid sha256 hash length +tls-pin=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ uint8_t *item = malloc(1 + ret); // 1 ~ leading data length.
+ if (item == NULL) {
+ return KNOT_ENOMEM;
+ }
+ item[0] = ret;
+ memcpy(&item[1], pin, ret);
+
+ if (ptrlist_add(&q->tls.pins, item, NULL) == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ return opt_tls(arg, query);
+}
+
+static int opt_notls_pin(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ ptrnode_t *node, *nxt;
+ WALK_LIST_DELSAFE(node, nxt, q->tls.pins) {
+ free(node->d);
+ }
+ ptrlist_free(&q->tls.pins, NULL);
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_hostname(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.hostname);
+ q->tls.hostname = strdup(arg);
+
+ return opt_tls(arg, query);
+}
+
+static int opt_notls_hostname(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.hostname);
+ q->tls.hostname = NULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_sni(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.sni);
+ q->tls.sni = strdup(arg);
+
+ return opt_tls(arg, query);
+}
+
+static int opt_notls_sni(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.sni);
+ q->tls.sni = NULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_keyfile(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.keyfile);
+ q->tls.keyfile = strdup(arg);
+
+ return opt_tls(arg, query);
+}
+
+static int opt_notls_keyfile(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.keyfile);
+ q->tls.keyfile = NULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_certfile(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.certfile);
+ q->tls.certfile = strdup(arg);
+
+ return opt_tls(arg, query);
+}
+
+static int opt_notls_certfile(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.certfile);
+ q->tls.certfile = NULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_ocsp_stapling(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg == NULL) {
+ q->tls.ocsp_stapling = DEFAULT_TLS_OCSP_STAPLING;
+ return opt_tls(arg, query);
+ } else {
+ uint32_t num = 0;
+ if (str_to_u32(arg, &num) != KNOT_EOK || num == 0) {
+ ERR("invalid +tls-ocsp-stapling=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ q->tls.ocsp_stapling = 3600 * num;
+ return opt_tls(arg, query);
+ }
+}
+
+static int opt_notls_ocsp_stapling(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->tls.ocsp_stapling = 0;
+
+ return KNOT_EOK;
+}
+
+static int opt_https(const char *arg, void *query)
+{
+#ifdef LIBNGHTTP2
+ query_t *q = query;
+
+ q->https.enable = true;
+
+ if (arg != NULL) {
+ char *resource = strstr(arg, "://");
+ if (resource == NULL) {
+ resource = (char *)arg;
+ } else {
+ resource += 3; // strlen("://")
+ if (*resource == '\0') {
+ ERR("invalid +https=%s", arg);
+ return KNOT_EINVAL;
+ }
+ }
+
+ char *tmp_path = strchr(resource, '/');
+ if (tmp_path) {
+ free(q->https.path);
+ q->https.path = strdup(tmp_path);
+
+ if (tmp_path != resource) {
+ free(q->tls.hostname);
+ q->tls.hostname = strndup(resource, (size_t)(tmp_path - resource));
+ }
+ return opt_tls(arg, query);
+ } else {
+ return opt_tls_hostname(arg, query);
+ }
+
+ }
+
+ return opt_tls(arg, query);
+
+#else
+ return KNOT_ENOTSUP;
+#endif //LIBNGHTTP2
+}
+
+static int opt_nohttps(const char *arg, void *query)
+{
+#ifdef LIBNGHTTP2
+ query_t *q = query;
+
+ https_params_clean(&q->https);
+
+ return opt_notls(arg, query);
+#else
+ return KNOT_ENOTSUP;
+#endif //LIBNGHTTP2
+}
+
+static int opt_https_get(const char *arg, void *query)
+{
+#ifdef LIBNGHTTP2
+ query_t *q = query;
+
+ q->https.method = GET;
+
+ return opt_https(arg, query);
+#else
+ return KNOT_ENOTSUP;
+#endif //LIBNGHTTP2
+}
+
+static int opt_nohttps_get(const char *arg, void *query)
+{
+#ifdef LIBNGHTTP2
+ query_t *q = query;
+
+ q->https.method = POST;
+
+ return KNOT_EOK;
+#else
+ return KNOT_ENOTSUP;
+#endif
+}
+
+static int opt_quic(const char *arg, void *query)
+{
+#ifdef ENABLE_QUIC
+ query_t *q = query;
+
+ q->quic.enable = true;
+
+ opt_tls(arg, query);
+ opt_notcp(arg, query);
+
+ return KNOT_EOK;
+#else
+ return KNOT_ENOTSUP;
+#endif //ENABLE_QUIC
+}
+
+static int opt_noquic(const char *arg, void *query)
+{
+#ifdef ENABLE_QUIC
+ query_t *q = query;
+
+ quic_params_clean(&q->quic);
+
+ return opt_notls(arg, query);
+#else
+ return KNOT_ENOTSUP;
+#endif //ENABLE_QUIC
+}
+
+static int opt_nsid(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->nsid = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nonsid(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->nsid = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_bufsize(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ uint16_t num = 0;
+ if (str_to_u16(arg, &num) != KNOT_EOK) {
+ ERR("invalid +bufsize=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ // Disable EDNS if zero bufsize.
+ if (num == 0) {
+ q->udp_size = -1;
+ } else if (num < KNOT_WIRE_HEADER_SIZE) {
+ q->udp_size = KNOT_WIRE_HEADER_SIZE;
+ } else {
+ q->udp_size = num;
+ }
+
+ return KNOT_EOK;
+}
+
+static int opt_nobufsize(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->udp_size = -1;
+
+ return KNOT_EOK;
+}
+
+static int opt_cookie(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg != NULL) {
+ uint8_t *input = NULL;
+ size_t input_len;
+
+ int ret = hex_decode(arg, &input, &input_len);
+ if (ret != KNOT_EOK) {
+ ERR("invalid +cookie=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ if (input_len < KNOT_EDNS_COOKIE_CLNT_SIZE) {
+ ERR("too short client +cookie=%s", arg);
+ free(input);
+ return KNOT_EINVAL;
+ }
+ q->cc.len = KNOT_EDNS_COOKIE_CLNT_SIZE;
+ memcpy(q->cc.data, input, q->cc.len);
+
+ input_len -= q->cc.len;
+ if (input_len > 0) {
+ if (input_len < KNOT_EDNS_COOKIE_SRVR_MIN_SIZE) {
+ ERR("too short server +cookie=%s", arg);
+ free(input);
+ return KNOT_EINVAL;
+ }
+ if (input_len > KNOT_EDNS_COOKIE_SRVR_MAX_SIZE) {
+ ERR("too long server +cookie=%s", arg);
+ free(input);
+ return KNOT_EINVAL;
+ }
+ q->sc.len = input_len;
+ memcpy(q->sc.data, input + q->cc.len, q->sc.len);
+ }
+
+ free(input);
+ } else {
+ q->cc.len = KNOT_EDNS_COOKIE_CLNT_SIZE;
+
+ int ret = dnssec_random_buffer(q->cc.data, q->cc.len);
+ if (ret != DNSSEC_EOK) {
+ return knot_error_from_libdnssec(ret);
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int opt_nocookie(const char *arg, void *query)
+{
+ query_t *q = query;
+ q->cc.len = 0;
+ q->sc.len = 0;
+ return KNOT_EOK;
+}
+
+static int opt_badcookie(const char *arg, void *query)
+{
+ query_t *q = query;
+ q->badcookie = BADCOOKIE_RETRY_MAX;
+ return KNOT_EOK;
+}
+
+static int opt_nobadcookie(const char *arg, void *query)
+{
+ query_t *q = query;
+ q->badcookie = 0;
+ return KNOT_EOK;
+}
+
+static int opt_padding(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg == NULL) {
+ q->padding = -2;
+ return KNOT_EOK;
+ } else {
+ uint16_t num = 0;
+ if (str_to_u16(arg, &num) != KNOT_EOK) {
+ ERR("invalid +padding=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ q->padding = num;
+ return KNOT_EOK;
+ }
+}
+
+static int opt_nopadding(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->padding = -3;
+
+ return KNOT_EOK;
+}
+
+static int opt_alignment(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg == NULL) {
+ q->alignment = DEFAULT_ALIGNMENT_SIZE;
+ return KNOT_EOK;
+ } else {
+ uint16_t num = 0;
+ if (str_to_u16(arg, &num) != KNOT_EOK || num < 2) {
+ ERR("invalid +alignment=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ q->alignment = num;
+ return KNOT_EOK;
+ }
+}
+
+static int opt_noalignment(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->alignment = 0;
+
+ return KNOT_EOK;
+}
+
+static int opt_subnet(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ char *sep = NULL;
+ const size_t arg_len = strlen(arg);
+ const char *arg_end = arg + arg_len;
+ size_t addr_len = 0;
+
+ // Separate address and network mask.
+ if ((sep = strchr(arg, '/')) != NULL) {
+ addr_len = sep - arg;
+ } else {
+ addr_len = arg_len;
+ }
+
+ // Check IP address.
+
+ struct sockaddr_storage ss = { 0 };
+ struct addrinfo hints = { .ai_flags = AI_NUMERICHOST };
+ struct addrinfo *ai = NULL;
+
+ char *addr_str = strndup(arg, addr_len);
+ if (getaddrinfo(addr_str, NULL, &hints, &ai) != 0) {
+ free(addr_str);
+ ERR("invalid address +subnet=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ memcpy(&ss, ai->ai_addr, ai->ai_addrlen);
+ freeaddrinfo(ai);
+ free(addr_str);
+
+ if (knot_edns_client_subnet_set_addr(&q->subnet, &ss) != KNOT_EOK) {
+ ERR("invalid address +subnet=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ // Parse network mask.
+ const char *mask = arg;
+ if (mask + addr_len < arg_end) {
+ mask += addr_len + 1;
+ uint8_t num = 0;
+ if (str_to_u8(mask, &num) != KNOT_EOK || num > q->subnet.source_len) {
+ ERR("invalid network mask +subnet=%s", arg);
+ return KNOT_EINVAL;
+ }
+ q->subnet.source_len = num;
+ }
+
+ return KNOT_EOK;
+}
+
+static int opt_nosubnet(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->subnet.family = AF_UNSPEC;
+
+ return KNOT_EOK;
+}
+
+static int opt_edns(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg == NULL) {
+ q->edns = 0;
+ return KNOT_EOK;
+ } else {
+ uint8_t num = 0;
+ if (str_to_u8(arg, &num) != KNOT_EOK) {
+ ERR("invalid +edns=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ q->edns = num;
+ return KNOT_EOK;
+ }
+}
+
+static int opt_noedns(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->edns = -1;
+ opt_nodoflag(arg, query);
+ opt_nonsid(arg, query);
+ opt_nobufsize(arg, query);
+ opt_nocookie(arg, query);
+ opt_nopadding(arg, query);
+ opt_noalignment(arg, query);
+ opt_nosubnet(arg, query);
+
+ return KNOT_EOK;
+}
+
+static int opt_timeout(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (params_parse_wait(arg, &q->wait) != KNOT_EOK) {
+ ERR("invalid +timeout=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int opt_notimeout(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ (void)params_parse_wait("0", &q->wait);
+
+ return KNOT_EOK;
+}
+
+static int opt_retry(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (str_to_u32(arg, &q->retries) != KNOT_EOK) {
+ ERR("invalid +retry=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int opt_noretry(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->retries = 0;
+
+ return KNOT_EOK;
+}
+
+static int opt_expire(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ ednsopt_t *opt = ednsopt_create(KNOT_EDNS_OPTION_EXPIRE, 0, NULL);
+ add_tail(&q->edns_opts, &opt->n);
+
+ return KNOT_EOK;
+}
+
+static int opt_noexpire(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ ednsopt_t *node, *nxt;
+ WALK_LIST_DELSAFE(node, nxt, q->edns_opts) {
+ ednsopt_t *opt = node;
+ if (opt->code == KNOT_EDNS_OPTION_EXPIRE) {
+ rem_node(&opt->n);
+ ednsopt_free(opt);
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_ednsopt(const char *arg, ednsopt_t **opt_ptr)
+{
+ errno = 0;
+ char *end = NULL;
+ unsigned long code = strtoul(arg, &end, 10);
+ if (errno != 0 || arg == end || code > UINT16_MAX) {
+ return KNOT_EINVAL;
+ }
+
+ size_t length = 0;
+ uint8_t *data = NULL;
+ if (end[0] == ':') {
+ if (end[1] != '\0') {
+ int ret = hex_decode(end + 1, &data, &length);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ if (length > UINT16_MAX) {
+ free(data);
+ return KNOT_ERANGE;
+ }
+ }
+ } else if (end[0] != '\0') {
+ return KNOT_EINVAL;
+ }
+
+ ednsopt_t *opt = ednsopt_create(code, length, data);
+ if (opt == NULL) {
+ free(data);
+ return KNOT_ENOMEM;
+ }
+
+ *opt_ptr = opt;
+ return KNOT_EOK;
+}
+
+static int opt_ednsopt(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ ednsopt_t *opt = NULL;
+ int ret = parse_ednsopt(arg, &opt);
+ if (ret != KNOT_EOK) {
+ ERR("invalid +ednsopt=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ add_tail(&q->edns_opts, &opt->n);
+
+ return KNOT_EOK;
+}
+
+static int opt_noednsopt(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ ednsopt_list_deinit(&q->edns_opts);
+
+ return KNOT_EOK;
+}
+
+static int opt_noidn(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->idn = false;
+ q->style.style.ascii_to_idn = NULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_json(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.format = FORMAT_JSON;
+
+ return KNOT_EOK;
+}
+
+static int opt_nojson(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.format = FORMAT_FULL;
+
+ return KNOT_EOK;
+}
+
+static int parse_addr(struct sockaddr_storage *addr, const char *arg, const char *def_port)
+{
+ srv_info_t *info = parse_nameserver(arg, def_port);
+ if (info == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct addrinfo *addr_info = NULL;
+ int ret = getaddrinfo(info->name, info->service, NULL, &addr_info);
+ srv_info_free(info);
+ if (ret != 0) {
+ return KNOT_EINVAL;
+ }
+
+ memcpy(addr, addr_info->ai_addr, addr_info->ai_addrlen);
+ freeaddrinfo(addr_info);
+
+ return KNOT_EOK;
+}
+
+static int opt_proxy(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ const char *sep = strchr(arg, '-');
+ if (sep == NULL || sep == arg || *(sep + 1) == '\0') {
+ ERR("invalid specification +proxy=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ char *src = strndup(arg, sep - arg);
+ int ret = parse_addr(&q->proxy.src, src, "0");
+ if (ret != KNOT_EOK) {
+ ERR("invalid proxy source address '%s'", src);
+ free(src);
+ return KNOT_EINVAL;
+ }
+
+ const char *dst = sep + 1;
+ ret = parse_addr(&q->proxy.dst, dst, "53");
+ if (ret != KNOT_EOK) {
+ ERR("invalid proxy destination address '%s'", dst);
+ free(src);
+ return KNOT_EINVAL;
+ }
+
+ if (q->proxy.src.ss_family != q->proxy.dst.ss_family) {
+ ERR("proxy address family mismatch '%s' versus '%s'", src, dst);
+ free(src);
+ return KNOT_EINVAL;
+ }
+ free(src);
+
+ return KNOT_EOK;
+}
+
+static int opt_noproxy(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->proxy.src.ss_family = 0;
+ q->proxy.dst.ss_family = 0;
+
+ return KNOT_EOK;
+}
+
+static const param_t kdig_opts2[] = {
+ { "multiline", ARG_NONE, opt_multiline },
+ { "nomultiline", ARG_NONE, opt_nomultiline },
+
+ { "short", ARG_NONE, opt_short },
+ { "noshort", ARG_NONE, opt_noshort },
+
+ { "generic", ARG_NONE, opt_generic },
+ { "nogeneric", ARG_NONE, opt_nogeneric },
+
+ { "aaflag", ARG_NONE, opt_aaflag },
+ { "noaaflag", ARG_NONE, opt_noaaflag },
+
+ { "tcflag", ARG_NONE, opt_tcflag },
+ { "notcflag", ARG_NONE, opt_notcflag },
+
+ { "rdflag", ARG_NONE, opt_rdflag },
+ { "nordflag", ARG_NONE, opt_nordflag },
+
+ { "recurse", ARG_NONE, opt_rdflag },
+ { "norecurse", ARG_NONE, opt_nordflag },
+
+ { "raflag", ARG_NONE, opt_raflag },
+ { "noraflag", ARG_NONE, opt_noraflag },
+
+ { "zflag", ARG_NONE, opt_zflag },
+ { "nozflag", ARG_NONE, opt_nozflag },
+
+ { "adflag", ARG_NONE, opt_adflag },
+ { "noadflag", ARG_NONE, opt_noadflag },
+
+ { "cdflag", ARG_NONE, opt_cdflag },
+ { "nocdflag", ARG_NONE, opt_nocdflag },
+
+ { "dnssec", ARG_NONE, opt_doflag },
+ { "nodnssec", ARG_NONE, opt_nodoflag },
+
+ { "all", ARG_NONE, opt_all },
+ { "noall", ARG_NONE, opt_noall },
+
+ { "qr", ARG_NONE, opt_qr },
+ { "noqr", ARG_NONE, opt_noqr },
+
+ { "header", ARG_NONE, opt_header },
+ { "noheader", ARG_NONE, opt_noheader },
+
+ { "comments", ARG_NONE, opt_comments },
+ { "nocomments", ARG_NONE, opt_nocomments },
+
+ { "opt", ARG_NONE, opt_opt },
+ { "noopt", ARG_NONE, opt_noopt },
+
+ { "opttext", ARG_NONE, opt_opttext },
+ { "noopttext", ARG_NONE, opt_noopttext },
+
+ { "optpresent", ARG_NONE, opt_optpresent },
+ { "nooptpresent", ARG_NONE, opt_nooptpresent },
+
+ { "question", ARG_NONE, opt_question },
+ { "noquestion", ARG_NONE, opt_noquestion },
+
+ { "answer", ARG_NONE, opt_answer },
+ { "noanswer", ARG_NONE, opt_noanswer },
+
+ { "authority", ARG_NONE, opt_authority },
+ { "noauthority", ARG_NONE, opt_noauthority },
+
+ { "additional", ARG_NONE, opt_additional },
+ { "noadditional", ARG_NONE, opt_noadditional },
+
+ { "tsig", ARG_NONE, opt_tsig },
+ { "notsig", ARG_NONE, opt_notsig },
+
+ { "stats", ARG_NONE, opt_stats },
+ { "nostats", ARG_NONE, opt_nostats },
+
+ { "class", ARG_NONE, opt_class },
+ { "noclass", ARG_NONE, opt_noclass },
+
+ { "ttl", ARG_NONE, opt_ttl },
+ { "nottl", ARG_NONE, opt_nottl },
+
+ { "crypto", ARG_NONE, opt_crypto },
+ { "nocrypto", ARG_NONE, opt_nocrypto },
+
+ { "tcp", ARG_NONE, opt_tcp },
+ { "notcp", ARG_NONE, opt_notcp },
+
+ { "fastopen", ARG_NONE, opt_fastopen },
+ { "nofastopen", ARG_NONE, opt_nofastopen },
+
+ { "ignore", ARG_NONE, opt_ignore },
+ { "noignore", ARG_NONE, opt_noignore },
+
+ { "keepopen", ARG_NONE, opt_keepopen },
+ { "nokeepopen", ARG_NONE, opt_nokeepopen },
+
+ { "tls", ARG_NONE, opt_tls },
+ { "notls", ARG_NONE, opt_notls },
+
+ { "tls-ca", ARG_OPTIONAL, opt_tls_ca },
+ { "notls-ca", ARG_NONE, opt_notls_ca },
+
+ { "tls-pin", ARG_REQUIRED, opt_tls_pin },
+ { "notls-pin", ARG_NONE, opt_notls_pin },
+
+ { "tls-hostname", ARG_REQUIRED, opt_tls_hostname },
+ { "notls-hostname", ARG_NONE, opt_notls_hostname },
+
+ { "tls-sni", ARG_REQUIRED, opt_tls_sni },
+ { "notls-sni", ARG_NONE, opt_notls_sni },
+
+ { "tls-keyfile", ARG_REQUIRED, opt_tls_keyfile },
+ { "notls-keyfile", ARG_NONE, opt_notls_keyfile },
+
+ { "tls-certfile", ARG_REQUIRED, opt_tls_certfile },
+ { "notls-certfile", ARG_NONE, opt_notls_certfile },
+
+ { "tls-ocsp-stapling", ARG_OPTIONAL, opt_tls_ocsp_stapling },
+ { "notls-ocsp-stapling", ARG_NONE, opt_notls_ocsp_stapling },
+
+ { "https", ARG_OPTIONAL, opt_https },
+ { "nohttps", ARG_NONE, opt_nohttps },
+
+ { "https-get", ARG_NONE, opt_https_get },
+ { "nohttps-get", ARG_NONE, opt_nohttps_get },
+
+ { "quic", ARG_NONE, opt_quic },
+ { "noquic", ARG_NONE, opt_noquic },
+
+ { "nsid", ARG_NONE, opt_nsid },
+ { "nonsid", ARG_NONE, opt_nonsid },
+
+ { "bufsize", ARG_REQUIRED, opt_bufsize },
+ { "nobufsize", ARG_NONE, opt_nobufsize },
+
+ { "padding", ARG_OPTIONAL, opt_padding },
+ { "nopadding", ARG_NONE, opt_nopadding },
+
+ { "alignment", ARG_OPTIONAL, opt_alignment },
+ { "noalignment", ARG_NONE, opt_noalignment },
+
+ { "subnet", ARG_REQUIRED, opt_subnet },
+ { "nosubnet", ARG_NONE, opt_nosubnet },
+
+ { "proxy", ARG_REQUIRED, opt_proxy },
+ { "noproxy", ARG_NONE, opt_noproxy },
+
+ // Obsolete aliases.
+ { "client", ARG_REQUIRED, opt_subnet },
+ { "noclient", ARG_NONE, opt_nosubnet },
+
+ { "edns", ARG_OPTIONAL, opt_edns },
+ { "noedns", ARG_NONE, opt_noedns },
+
+ { "timeout", ARG_REQUIRED, opt_timeout },
+ { "notimeout", ARG_NONE, opt_notimeout },
+
+ { "retry", ARG_REQUIRED, opt_retry },
+ { "noretry", ARG_NONE, opt_noretry },
+
+ { "expire", ARG_NONE, opt_expire },
+ { "noexpire", ARG_NONE, opt_noexpire },
+
+ { "cookie", ARG_OPTIONAL, opt_cookie },
+ { "nocookie", ARG_NONE, opt_nocookie },
+
+ { "badcookie", ARG_NONE, opt_badcookie },
+ { "nobadcookie", ARG_NONE, opt_nobadcookie },
+
+ { "ednsopt", ARG_REQUIRED, opt_ednsopt },
+ { "noednsopt", ARG_NONE, opt_noednsopt },
+
+ { "json", ARG_NONE, opt_json },
+ { "nojson", ARG_NONE, opt_nojson },
+
+ /* "idn" doesn't work since it must be called before query creation. */
+ { "noidn", ARG_NONE, opt_noidn },
+
+ { NULL }
+};
+
+query_t *query_create(const char *owner, const query_t *conf)
+{
+ // Create output structure.
+ query_t *query = calloc(1, sizeof(query_t));
+
+ if (query == NULL) {
+ DBG_NULL;
+ return NULL;
+ }
+
+ // Initialization with defaults or with reference query.
+ if (conf == NULL) {
+ query->conf = NULL;
+ query->local = NULL;
+ query->operation = OPERATION_QUERY;
+ query->ip = IP_ALL;
+ query->protocol = PROTO_ALL;
+ query->fastopen = false;
+ query->port = strdup("");
+ query->udp_size = -1;
+ query->retries = DEFAULT_RETRIES_DIG;
+ query->wait = DEFAULT_TIMEOUT_DIG;
+ query->ignore_tc = false;
+ query->class_num = -1;
+ query->type_num = -1;
+ query->serial = -1;
+ query->notify = false;
+ query->flags = DEFAULT_FLAGS_DIG;
+ query->style = DEFAULT_STYLE_DIG;
+ query->style.style.now = knot_time();
+ query->idn = true;
+ query->nsid = false;
+ query->edns = -1;
+ query->cc.len = 0;
+ query->sc.len = 0;
+ query->badcookie = BADCOOKIE_RETRY_MAX;
+ query->padding = -1;
+ query->alignment = 0;
+ tls_params_init(&query->tls);
+ //query->tsig_key
+ query->subnet.family = AF_UNSPEC;
+ ednsopt_list_init(&query->edns_opts);
+#if USE_DNSTAP
+ query->dt_reader = NULL;
+ query->dt_writer = NULL;
+#endif // USE_DNSTAP
+ } else {
+ *query = *conf;
+ query->conf = conf;
+ if (conf->local != NULL) {
+ query->local = srv_info_create(conf->local->name,
+ conf->local->service);
+ if (query->local == NULL) {
+ query_free(query);
+ return NULL;
+ }
+ } else {
+ query->local = NULL;
+ }
+ query->port = strdup(conf->port);
+ tls_params_copy(&query->tls, &conf->tls);
+ https_params_copy(&query->https, &conf->https);
+ quic_params_copy(&query->quic, &conf->quic);
+ if (conf->tsig_key.name != NULL) {
+ int ret = knot_tsig_key_copy(&query->tsig_key,
+ &conf->tsig_key);
+ if (ret != KNOT_EOK) {
+ query_free(query);
+ return NULL;
+ }
+ }
+
+ int ret = ednsopt_list_dup(&query->edns_opts, &conf->edns_opts);
+ if (ret != KNOT_EOK) {
+ query_free(query);
+ return NULL;
+ }
+
+#if USE_DNSTAP
+ query->dt_reader = conf->dt_reader;
+ query->dt_writer = conf->dt_writer;
+#endif // USE_DNSTAP
+ }
+
+ // Initialize list of servers.
+ init_list(&query->servers);
+
+ // Set the query owner if any.
+ if (owner != NULL) {
+ if ((query->owner = strdup(owner)) == NULL) {
+ query_free(query);
+ return NULL;
+ }
+ }
+
+ // Check dynamic allocation.
+ if (query->port == NULL) {
+ query_free(query);
+ return NULL;
+ }
+
+ return query;
+}
+
+void query_free(query_t *query)
+{
+ node_t *n, *nxt;
+
+ if (query == NULL) {
+ DBG_NULL;
+ return;
+ }
+
+ // Cleanup servers.
+ WALK_LIST_DELSAFE(n, nxt, query->servers) {
+ srv_info_free((srv_info_t *)n);
+ }
+
+ // Cleanup local address.
+ if (query->local != NULL) {
+ srv_info_free(query->local);
+ }
+
+ tls_params_clean(&query->tls);
+ https_params_clean(&query->https);
+ quic_params_clean(&query->quic);
+
+ // Cleanup signing key.
+ knot_tsig_key_deinit(&query->tsig_key);
+
+ // Cleanup EDNS options.
+ ednsopt_list_deinit(&query->edns_opts);
+
+#if USE_DNSTAP
+ if (query->dt_reader != NULL) {
+ dt_reader_free(query->dt_reader);
+ }
+ if (query->dt_writer != NULL) {
+ // Global writer can be shared!
+ if (query->conf == NULL ||
+ query->conf->dt_writer != query->dt_writer) {
+ dt_writer_free(query->dt_writer);
+ }
+ }
+#endif // USE_DNSTAP
+
+ free(query->owner);
+ free(query->port);
+ free(query);
+}
+
+ednsopt_t *ednsopt_create(uint16_t code, uint16_t length, uint8_t *data)
+{
+ ednsopt_t *opt = calloc(1, sizeof(*opt));
+ if (opt == NULL) {
+ return NULL;
+ }
+
+ opt->code = code;
+ opt->length = length;
+ opt->data = data;
+
+ return opt;
+}
+
+ednsopt_t *ednsopt_dup(const ednsopt_t *opt)
+{
+ ednsopt_t *dup = calloc(1, sizeof(*opt));
+ if (dup == NULL) {
+ return NULL;
+ }
+
+ dup->code = opt->code;
+ dup->length = opt->length;
+ dup->data = memdup(opt->data, opt->length);
+ if (dup->data == NULL) {
+ free(dup);
+ return NULL;
+ }
+
+ return dup;
+}
+
+void ednsopt_free(ednsopt_t *opt)
+{
+ if (opt == NULL) {
+ return;
+ }
+
+ free(opt->data);
+ free(opt);
+}
+
+void ednsopt_list_init(list_t *list)
+{
+ init_list(list);
+}
+
+void ednsopt_list_deinit(list_t *list)
+{
+ node_t *n, *next;
+ WALK_LIST_DELSAFE(n, next, *list) {
+ ednsopt_t *opt = (ednsopt_t *)n;
+ ednsopt_free(opt);
+ }
+
+ init_list(list);
+}
+
+int ednsopt_list_dup(list_t *dest, const list_t *src)
+{
+ list_t backup = *dest;
+ init_list(dest);
+
+ node_t *n;
+ WALK_LIST(n, *src) {
+ ednsopt_t *opt = (ednsopt_t *)n;
+ ednsopt_t *dup = ednsopt_dup(opt);
+ if (dup == NULL) {
+ ednsopt_list_deinit(dest);
+ *dest = backup;
+ return KNOT_ENOMEM;
+ }
+
+ add_tail(dest, &dup->n);
+ }
+
+ return KNOT_EOK;
+}
+
+bool ednsopt_list_empty(const list_t *list)
+{
+ return EMPTY_LIST(*list);
+}
+
+int kdig_init(kdig_params_t *params)
+{
+ if (params == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ memset(params, 0, sizeof(*params));
+
+ params->stop = false;
+
+ // Initialize list of queries.
+ init_list(&params->queries);
+
+ // Create config query.
+ if ((params->config = query_create(NULL, NULL)) == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ return KNOT_EOK;
+}
+
+void kdig_clean(kdig_params_t *params)
+{
+ node_t *n, *nxt;
+
+ if (params == NULL) {
+ DBG_NULL;
+ return;
+ }
+
+ // Clean up queries.
+ WALK_LIST_DELSAFE(n, nxt, params->queries) {
+ query_free((query_t *)n);
+ }
+
+ // Clean up config.
+ query_free(params->config);
+
+ // Clean up the structure.
+ memset(params, 0, sizeof(*params));
+}
+
+static int parse_class(const char *value, query_t *query)
+{
+ uint16_t rclass;
+
+ if (params_parse_class(value, &rclass) != KNOT_EOK) {
+ return KNOT_EINVAL;
+ }
+
+ query->class_num = rclass;
+
+ return KNOT_EOK;
+}
+
+static int parse_keyfile(const char *value, query_t *query)
+{
+ knot_tsig_key_deinit(&query->tsig_key);
+
+ if (knot_tsig_key_init_file(&query->tsig_key, value) != KNOT_EOK) {
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_local(const char *value, query_t *query)
+{
+ srv_info_t *local = parse_nameserver(value, "0");
+ if (local == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (query->local != NULL) {
+ srv_info_free(query->local);
+ }
+
+ query->local = local;
+
+ return KNOT_EOK;
+}
+
+static int parse_name(const char *value, list_t *queries, const query_t *conf)
+{
+ query_t *query = NULL;
+ char *ascii_name = (char *)value;
+ char *fqd_name = NULL;
+
+ if (value != NULL) {
+ if (conf->idn) {
+ ascii_name = name_from_idn(value);
+ if (ascii_name == NULL) {
+ return KNOT_EINVAL;
+ }
+ }
+
+ // If name is not FQDN, append trailing dot.
+ fqd_name = get_fqd_name(ascii_name);
+
+ if (conf->idn) {
+ free(ascii_name);
+ }
+ }
+
+ // Create new query.
+ query = query_create(fqd_name, conf);
+
+ free(fqd_name);
+
+ if (query == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Add new query to the queries.
+ add_tail(queries, (node_t *)query);
+
+ return KNOT_EOK;
+}
+
+static int parse_port(const char *value, query_t *query)
+{
+ char **port;
+
+ // Set current server port (last or query default).
+ if (list_size(&query->servers) > 0) {
+ srv_info_t *server = TAIL(query->servers);
+ port = &(server->service);
+ } else {
+ port = &(query->port);
+ }
+
+ char *new_port = strdup(value);
+
+ if (new_port == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Deallocate old string.
+ free(*port);
+
+ *port = new_port;
+
+ return KNOT_EOK;
+}
+
+static int parse_reverse(const char *value, list_t *queries, const query_t *conf)
+{
+ query_t *query = NULL;
+
+ // Create reverse name.
+ char *reverse = get_reverse_name(value);
+
+ if (reverse == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Create reverse query for given address.
+ query = query_create(reverse, conf);
+
+ free(reverse);
+
+ if (query == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Set type for reverse query.
+ query->type_num = KNOT_RRTYPE_PTR;
+
+ // Add new query to the queries.
+ add_tail(queries, (node_t *)query);
+
+ return KNOT_EOK;
+}
+
+static int parse_server(const char *value, kdig_params_t *params)
+{
+ query_t *query;
+
+ // Set current query (last or config).
+ if (list_size(&params->queries) > 0) {
+ query = TAIL(params->queries);
+ } else {
+ query = params->config;
+ }
+
+ if (params_parse_server(value, &query->servers, query->port) != KNOT_EOK) {
+ ERR("invalid server @%s", value);
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_tsig(const char *value, query_t *query)
+{
+ knot_tsig_key_deinit(&query->tsig_key);
+
+ if (knot_tsig_key_init_str(&query->tsig_key, value) != KNOT_EOK) {
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_type(const char *value, query_t *query)
+{
+ uint16_t rtype;
+ int64_t serial;
+ bool notify;
+
+ if (params_parse_type(value, &rtype, &serial, &notify) != KNOT_EOK) {
+ return KNOT_EINVAL;
+ }
+
+ query->type_num = rtype;
+ query->serial = serial;
+ query->notify = notify;
+
+ // If NOTIFY, reset default RD flag.
+ if (query->notify) {
+ query->flags.rd_flag = false;
+ }
+
+ return KNOT_EOK;
+}
+
+#if USE_DNSTAP
+static int parse_dnstap_output(const char *value, query_t *query)
+{
+ if (query->dt_writer != NULL) {
+ if (query->conf == NULL ||
+ query->conf->dt_writer != query->dt_writer) {
+ dt_writer_free(query->dt_writer);
+ }
+ }
+
+ query->dt_writer = dt_writer_create(value, "kdig " PACKAGE_VERSION);
+ if (query->dt_writer == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_dnstap_input(const char *value, query_t *query)
+{
+ // Just in case, shouldn't happen.
+ if (query->dt_reader != NULL) {
+ dt_reader_free(query->dt_reader);
+ }
+
+ query->dt_reader = dt_reader_create(value);
+ if (query->dt_reader == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+#endif // USE_DNSTAP
+
+static void complete_servers(query_t *query, const query_t *conf)
+{
+ node_t *n;
+ char *def_port;
+
+ // Decide which default port use.
+ if (strlen(query->port) > 0) {
+ def_port = query->port;
+ } else if (strlen(conf->port) > 0) {
+ def_port = conf->port;
+ } else if (query->https.enable) {
+ def_port = DEFAULT_DNS_HTTPS_PORT;
+ } else if (query->quic.enable) {
+ def_port = DEFAULT_DNS_QUIC_PORT;
+ } else if (query->tls.enable) {
+ def_port = DEFAULT_DNS_TLS_PORT;
+ } else {
+ def_port = DEFAULT_DNS_PORT;
+ }
+
+ // Complete specified nameservers if any.
+ if (list_size(&query->servers) > 0) {
+ WALK_LIST(n, query->servers) {
+ srv_info_t *s = (srv_info_t *)n;
+
+ // If the port isn't specified yet use the default one.
+ if (strlen(s->service) == 0) {
+ free(s->service);
+ s->service = strdup(def_port);
+ if (s->service == NULL) {
+ WARN("can't set port %s", def_port);
+ return;
+ }
+ }
+
+ // Use server name as hostname for TLS if necessary.
+ if (query->tls.enable && query->tls.hostname == NULL &&
+ (query->tls.system_ca || !EMPTY_LIST(query->tls.ca_files))) {
+ query->tls.hostname = strdup(s->name);
+ }
+ }
+ // Use servers from config if any.
+ } else if (list_size(&conf->servers) > 0) {
+ WALK_LIST(n, conf->servers) {
+ srv_info_t *s = (srv_info_t *)n;
+ char *port = def_port;
+
+ // If the port is already specified, use it.
+ if (strlen(s->service) > 0) {
+ port = s->service;
+ }
+
+ srv_info_t *server = srv_info_create(s->name, port);
+ if (server == NULL) {
+ WARN("can't set nameserver %s port %s",
+ s->name, s->service);
+ return;
+ }
+ add_tail(&query->servers, (node_t *)server);
+
+ // Use server name as hostname for TLS if necessary.
+ if (query->tls.enable && query->tls.hostname == NULL &&
+ (query->tls.system_ca || !EMPTY_LIST(query->tls.ca_files))) {
+ query->tls.hostname = strdup(s->name);
+ }
+ }
+ // Use system specific.
+ } else {
+ get_nameservers(&query->servers, def_port);
+ }
+}
+
+static bool compare_servers(list_t *s1, list_t *s2)
+{
+ if (list_size(s1) != list_size(s2)) {
+ return false;
+ }
+
+ node_t *n1, *n2 = HEAD(*s2);
+ WALK_LIST(n1, *s1) {
+ srv_info_t *i1 = (srv_info_t *)n1, *i2 = (srv_info_t *)n2;
+ if (strcmp(i1->service, i2->service) != 0 ||
+ strcmp(i1->name, i2->name) != 0)
+ {
+ return false;
+ }
+ n2 = n2->next;
+ }
+ return true;
+}
+
+void complete_queries(list_t *queries, const query_t *conf)
+{
+ node_t *n;
+
+ if (queries == NULL || conf == NULL) {
+ DBG_NULL;
+ return;
+ }
+
+ // If there is no query, add default query: NS to ".".
+ if (list_size(queries) == 0) {
+ query_t *q = query_create(".", conf);
+ if (q == NULL) {
+ WARN("can't create query . NS IN");
+ return;
+ }
+ q->class_num = KNOT_CLASS_IN;
+ q->type_num = KNOT_RRTYPE_NS;
+ add_tail(queries, (node_t *)q);
+ }
+
+ WALK_LIST(n, *queries) {
+ query_t *q = (query_t *)n;
+ query_t *q_prev = (HEAD(*queries) != n) ? (query_t *)n->prev : NULL;
+
+ // Fill class number if missing.
+ if (q->class_num < 0) {
+ if (conf->class_num >= 0) {
+ q->class_num = conf->class_num;
+ } else {
+ q->class_num = KNOT_CLASS_IN;
+ }
+ }
+
+ // Fill type number if missing.
+ if (q->type_num < 0) {
+ if (conf->type_num >= 0) {
+ q->type_num = conf->type_num;
+ q->serial = conf->serial;
+ } else {
+ q->type_num = KNOT_RRTYPE_A;
+ }
+ }
+
+ // Set zone transfer if any.
+ if (q->type_num == KNOT_RRTYPE_AXFR ||
+ q->type_num == KNOT_RRTYPE_IXFR) {
+ q->operation = OPERATION_XFR;
+ }
+
+ // Retries only apply to pure UDP.
+ if (q->protocol == PROTO_TCP ||
+ q->tls.enable || q->https.enable || q->quic.enable) {
+ q->retries = 0;
+ }
+
+ // Complete nameservers list.
+ complete_servers(q, conf);
+
+ // Check if using previous connection makes sense.
+ if (q_prev != NULL && q_prev->keepopen &&
+ (get_socktype(q->protocol, q->type_num) !=
+ get_socktype(q_prev->protocol, q_prev->type_num) ||
+ q->https.enable != q_prev->https.enable ||
+ q->tls.enable != q_prev->tls.enable ||
+ strcmp(q->port, q_prev->port) != 0 ||
+ !compare_servers(&q->servers, &q_prev->servers)))
+ {
+ WARN("connection parameters mismatch for query (%s), "
+ "ignoring keepopen", q->owner);
+ q_prev->keepopen = false;
+ }
+ }
+}
+
+static void print_help(void)
+{
+ printf("Usage: %s [-4] [-6] [-d] [-b address] [-c class] [-p port]\n"
+ " [-q name] [-t type] [-x address] [-k keyfile]\n"
+ " [-y [algo:]keyname:key] [-E tapfile] [-G tapfile]\n"
+ " name [type] [class] [@server]\n"
+ "\n"
+ " +[no]multiline Wrap long records to more lines.\n"
+ " +[no]short Show record data only.\n"
+ " +[no]generic Use generic representation format.\n"
+ " +[no]aaflag Set AA flag.\n"
+ " +[no]tcflag Set TC flag.\n"
+ " +[no]rdflag Set RD flag.\n"
+ " +[no]recurse Same as +[no]rdflag\n"
+ " +[no]raflag Set RA flag.\n"
+ " +[no]zflag Set zero flag bit.\n"
+ " +[no]adflag Set AD flag.\n"
+ " +[no]cdflag Set CD flag.\n"
+ " +[no]dnssec Set DO flag.\n"
+ " +[no]all Show all packet sections.\n"
+ " +[no]qr Show query packet.\n"
+ " +[no]header Show packet header.\n"
+ " +[no]comments Show commented section names.\n"
+ " +[no]opt Show EDNS pseudosection.\n"
+ " +[no]opttext Try to show unknown EDNS options as text.\n"
+ " +[no]optpresent Show EDNS in presenatation format.\n"
+ " +[no]question Show question section.\n"
+ " +[no]answer Show answer section.\n"
+ " +[no]authority Show authority section.\n"
+ " +[no]additional Show additional section.\n"
+ " +[no]tsig Show TSIG pseudosection.\n"
+ " +[no]stats Show trailing packet statistics.\n"
+ " +[no]class Show DNS class.\n"
+ " +[no]ttl Show TTL value.\n"
+ " +[no]crypto Show binary parts of RRSIGs and DNSKEYs.\n"
+ " +[no]tcp Use TCP protocol.\n"
+ " +[no]fastopen Use TCP Fast Open.\n"
+ " +[no]ignore Don't use TCP automatically if truncated.\n"
+ " +[no]keepopen Don't close the TCP connection to be reused.\n"
+ " +[no]tls Use TLS with Opportunistic privacy profile.\n"
+ " +[no]tls-ca[=FILE] Use TLS with Out-Of-Band privacy profile.\n"
+ " +[no]tls-pin=BASE64 Use TLS with pinned certificate.\n"
+ " +[no]tls-hostname=STR Use TLS with remote server hostname.\n"
+ " +[no]tls-sni=STR Use TLS with Server Name Indication.\n"
+ " +[no]tls-keyfile=FILE Use TLS with a client keyfile.\n"
+ " +[no]tls-certfile=FILE Use TLS with a client certfile.\n"
+ " +[no]tls-ocsp-stapling[=H] Use TLS with a valid stapled OCSP response for the\n"
+ " server certificate (%u or specify hours).\n"
+#ifdef LIBNGHTTP2
+ " +[no]https[=URL] Use HTTPS protocol. It's also possible to specify\n"
+ " URL as [authority][/path] where query will be sent.\n"
+ " +[no]https-get Use HTTPS protocol with GET method instead of POST.\n"
+#endif
+#ifdef ENABLE_QUIC
+ " +[no]quic Use QUIC protocol.\n"
+#endif
+ " +[no]nsid Request NSID.\n"
+ " +[no]bufsize=B Set EDNS buffer size.\n"
+ " +[no]padding[=N] Pad with EDNS(0) (default or specify size).\n"
+ " +[no]alignment[=N] Pad with EDNS(0) to blocksize (%u or specify size).\n"
+ " +[no]subnet=SUBN Set EDNS(0) client subnet addr/prefix.\n"
+ " +[no]edns[=N] Use EDNS(=version).\n"
+ " +[no]timeout=T Set wait for reply interval in seconds.\n"
+ " +[no]retry=N Set number of retries.\n"
+ " +[no]expire Set the EXPIRE EDNS option.\n"
+ " +[no]cookie[=HEX] Attach EDNS(0) cookie to the query.\n"
+ " +[no]badcookie Repeat a query with the correct cookie.\n"
+ " +[no]ednsopt=CODE[:HEX] Set custom EDNS option.\n"
+ " +[no]proxy=SADDR-DADDR Add PROXYv2 header with src and dest addresses.\n"
+ " +[no]json Use JSON for output encoding (RFC 8427).\n"
+ " +noidn Disable IDN transformation.\n"
+ "\n"
+ " -h, --help Print the program help.\n"
+ " -V, --version Print the program version.\n",
+ PROGRAM_NAME, DEFAULT_TLS_OCSP_STAPLING / 3600, DEFAULT_ALIGNMENT_SIZE);
+}
+
+static int parse_opt1(const char *opt, const char *value, kdig_params_t *params,
+ int *index)
+{
+ const char *val = value;
+ size_t len = strlen(opt);
+ int add = 1;
+ query_t *query;
+
+ // Set current query (last or config).
+ if (list_size(&params->queries) > 0) {
+ query = TAIL(params->queries);
+ } else {
+ query = params->config;
+ }
+
+ // If there is no space between option and argument.
+ if (len > 1) {
+ val = opt + 1;
+ add = 0;
+ }
+
+ switch (opt[0]) {
+ case '4':
+ if (len > 1) {
+ ERR("invalid option -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+
+ query->ip = IP_4;
+ break;
+ case '6':
+ if (len > 1) {
+ ERR("invalid option -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+
+ query->ip = IP_6;
+ break;
+ case 'b':
+ if (val == NULL) {
+ ERR("missing address");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_local(val, query) != KNOT_EOK) {
+ ERR("bad address %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'd':
+ msg_enable_debug(1);
+ break;
+ case 'h':
+ if (len > 1) {
+ ERR("invalid option -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+
+ print_help();
+ params->stop = true;
+ break;
+ case 'c':
+ if (val == NULL) {
+ ERR("missing class");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_class(val, query) != KNOT_EOK) {
+ ERR("bad class %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'k':
+ if (val == NULL) {
+ ERR("missing filename");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_keyfile(val, query) != KNOT_EOK) {
+ ERR("bad keyfile %s", value);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'p':
+ if (val == NULL) {
+ ERR("missing port");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_port(val, query) != KNOT_EOK) {
+ ERR("bad port %s", value);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'q':
+ // Allow empty QNAME.
+ if (parse_name(val, &params->queries, params->config)
+ != KNOT_EOK) {
+ ERR("bad query name %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 't':
+ if (val == NULL) {
+ ERR("missing type");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_type(val, query) != KNOT_EOK) {
+ ERR("bad type %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'V':
+ if (len > 1) {
+ ERR("invalid option -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+
+ print_version(PROGRAM_NAME);
+ params->stop = true;
+ break;
+ case 'x':
+ if (val == NULL) {
+ ERR("missing address");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_reverse(val, &params->queries, params->config)
+ != KNOT_EOK) {
+ ERR("bad reverse name %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'y':
+ if (val == NULL) {
+ ERR("missing key");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_tsig(val, query) != KNOT_EOK) {
+ ERR("bad key %s", value);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'E':
+#if USE_DNSTAP
+ if (val == NULL) {
+ ERR("missing filename");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_dnstap_output(val, query) != KNOT_EOK) {
+ ERR("unable to open dnstap output file %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+#else
+ ERR("no dnstap support but -E specified");
+ return KNOT_EINVAL;
+#endif // USE_DNSTAP
+ break;
+ case 'G':
+#if USE_DNSTAP
+ if (val == NULL) {
+ ERR("missing filename");
+ return KNOT_EINVAL;
+ }
+
+ query = query_create(NULL, params->config);
+ if (query == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ if (parse_dnstap_input(val, query) != KNOT_EOK) {
+ ERR("unable to open dnstap input file %s", val);
+ query_free(query);
+ return KNOT_EINVAL;
+ }
+
+ query->operation = OPERATION_LIST_DNSTAP;
+ add_tail(&params->queries, (node_t *)query);
+
+ *index += add;
+#else
+ ERR("no dnstap support but -G specified");
+ return KNOT_EINVAL;
+#endif // USE_DNSTAP
+ break;
+ case '-':
+ if (strcmp(opt, "-help") == 0) {
+ print_help();
+ params->stop = true;
+ } else if (strcmp(opt, "-version") == 0) {
+ print_version(PROGRAM_NAME);
+ params->stop = true;
+ } else {
+ ERR("invalid option: -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+ break;
+ default:
+ ERR("invalid option: -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_opt2(const char *value, kdig_params_t *params)
+{
+ query_t *query;
+
+ // Set current query (last or config).
+ if (list_size(&params->queries) > 0) {
+ query = TAIL(params->queries);
+ } else {
+ query = params->config;
+ }
+
+ // Get option name.
+ const char *arg_sep = "=";
+ size_t opt_len = strcspn(value, arg_sep);
+ if (opt_len < 1) {
+ ERR("invalid option: +%s", value);
+ return KNOT_ENOTSUP;
+ }
+
+ // Get option argument if any.
+ const char *arg = NULL;
+ const char *rest = value + opt_len;
+ if (strlen(rest) > 0) {
+ arg = rest + strspn(rest, arg_sep);
+ }
+
+ // Check if the given option is supported.
+ bool unique;
+ int ret = best_param(value, opt_len, kdig_opts2, &unique);
+ if (ret < 0) {
+ ERR("invalid option: +%s", value);
+ return KNOT_ENOTSUP;
+ } else if (!unique) {
+ ERR("ambiguous option: +%s", value);
+ return KNOT_ENOTSUP;
+ }
+
+ // Check argument presence.
+ switch (kdig_opts2[ret].arg) {
+ case ARG_NONE:
+ if (arg != NULL && *arg != '\0') {
+ WARN("superfluous option argument: +%s", value);
+ }
+ break;
+ case ARG_REQUIRED:
+ if (arg == NULL) {
+ ERR("missing argument: +%s", value);
+ return KNOT_EFEWDATA;
+ }
+ // FALLTHROUGH
+ case ARG_OPTIONAL:
+ if (arg != NULL && *arg == '\0') {
+ ERR("empty argument: +%s", value);
+ return KNOT_EFEWDATA;
+ }
+ break;
+ }
+
+ // Call option handler.
+ return kdig_opts2[ret].handler(arg, query);
+}
+
+static int parse_token(const char *value, kdig_params_t *params)
+{
+ query_t *query;
+
+ // Set current query (last or config).
+ if (list_size(&params->queries) > 0) {
+ query = TAIL(params->queries);
+ } else {
+ query = params->config;
+ }
+
+ // Try to guess the meaning of the token.
+ if (strlen(value) == 0) {
+ ERR("invalid empty parameter");
+ } else if (parse_type(value, query) == KNOT_EOK) {
+ return KNOT_EOK;
+ } else if (parse_class(value, query) == KNOT_EOK) {
+ return KNOT_EOK;
+ } else if (parse_name(value, &params->queries, params->config) == KNOT_EOK) {
+ return KNOT_EOK;
+ } else {
+ ERR("invalid parameter: %s", value);
+ }
+
+ return KNOT_EINVAL;
+}
+
+int kdig_parse(kdig_params_t *params, int argc, char *argv[])
+{
+ if (params == NULL || argv == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ // Initialize parameters.
+ if (kdig_init(params) != KNOT_EOK) {
+ return KNOT_ERROR;
+ }
+
+#ifdef LIBIDN
+ // Set up localization.
+ if (setlocale(LC_CTYPE, "") == NULL) {
+ WARN("can't setlocale, disabling IDN");
+ params->config->idn = false;
+ params->config->style.style.ascii_to_idn = NULL;
+ }
+#endif
+
+ // Command line parameters processing.
+ for (int i = 1; i < argc; i++) {
+ int ret = KNOT_ERROR;
+
+ // Process parameter.
+ switch (argv[i][0]) {
+ case '@':
+ ret = parse_server(argv[i] + 1, params);
+ break;
+ case '-':
+ ret = parse_opt1(argv[i] + 1, argv[i + 1], params, &i);
+ break;
+ case '+':
+ ret = parse_opt2(argv[i] + 1, params);
+ break;
+ default:
+ ret = parse_token(argv[i], params);
+ break;
+ }
+
+ // Check return.
+ switch (ret) {
+ case KNOT_EOK:
+ if (params->stop) {
+ return KNOT_EOK;
+ }
+ break;
+ case KNOT_ENOTSUP:
+ print_help();
+ default: // Fall through.
+ return ret;
+ }
+ }
+
+ // Complete missing data in queries based on defaults.
+ complete_queries(&params->queries, params->config);
+
+ return KNOT_EOK;
+}