diff options
Diffstat (limited to 'src/utils/nsupdate/nsupdate_exec.c')
-rw-r--r-- | src/utils/nsupdate/nsupdate_exec.c | 929 |
1 files changed, 929 insertions, 0 deletions
diff --git a/src/utils/nsupdate/nsupdate_exec.c b/src/utils/nsupdate/nsupdate_exec.c new file mode 100644 index 0000000..705e7c0 --- /dev/null +++ b/src/utils/nsupdate/nsupdate_exec.c @@ -0,0 +1,929 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <config.h> +#include <stdio.h> +#include <errno.h> +#include <assert.h> +#include <stdlib.h> +#include <ctype.h> +#include <sys/socket.h> +#include <unistd.h> + +#include "utils/nsupdate/nsupdate_exec.h" +#include "utils/common/params.h" +#include "utils/common/msg.h" +#include "utils/common/exec.h" +#include "utils/common/netio.h" +#include "utils/common/token.h" +#include "common/errcode.h" +#include "common/mempattern.h" +#include "common/descriptor.h" +#include "libknot/libknot.h" + +/* Declarations of cmd parse functions. */ +typedef int (*cmd_handle_f)(const char *lp, nsupdate_params_t *params); +int cmd_add(const char* lp, nsupdate_params_t *params); +int cmd_answer(const char* lp, nsupdate_params_t *params); +int cmd_class(const char* lp, nsupdate_params_t *params); +int cmd_debug(const char* lp, nsupdate_params_t *params); +int cmd_del(const char* lp, nsupdate_params_t *params); +int cmd_gsstsig(const char* lp, nsupdate_params_t *params); +int cmd_key(const char* lp, nsupdate_params_t *params); +int cmd_local(const char* lp, nsupdate_params_t *params); +int cmd_oldgsstsig(const char* lp, nsupdate_params_t *params); +int cmd_origin(const char* lp, nsupdate_params_t *params); +int cmd_prereq(const char* lp, nsupdate_params_t *params); +int cmd_realm(const char* lp, nsupdate_params_t *params); +int cmd_send(const char* lp, nsupdate_params_t *params); +int cmd_server(const char* lp, nsupdate_params_t *params); +int cmd_show(const char* lp, nsupdate_params_t *params); +int cmd_ttl(const char* lp, nsupdate_params_t *params); +int cmd_update(const char* lp, nsupdate_params_t *params); +int cmd_zone(const char* lp, nsupdate_params_t *params); + +/* Sorted list of commands. + * This way we could identify command byte-per-byte and + * cancel early if the next is lexicographically greater. + */ +const char* cmd_array[] = { + "\x3" "add", + "\x6" "answer", + "\x5" "class", /* {classname} */ + "\x5" "debug", + "\x3" "del", + "\x6" "delete", + "\x7" "gsstsig", + "\x3" "key", /* {name} {secret} */ + "\x5" "local", /* {address} [port] */ + "\xa" "oldgsstsig", + "\x6" "origin", /* {name} */ + "\x6" "prereq", /* (nx|yx)(domain|rrset) {domain-name} ... */ + "\x5" "realm", /* {[realm_name]} */ + "\x4" "send", + "\x6" "server", /* {servername} [port] */ + "\x4" "show", + "\x3" "ttl", /* {seconds} */ + "\x6" "update", /* (add|delete) {domain-name} ... */ + "\x4" "zone", /* {zonename} */ + NULL +}; + +cmd_handle_f cmd_handle[] = { + cmd_add, + cmd_answer, + cmd_class, + cmd_debug, + cmd_del, + cmd_del, /* delete/del synonyms */ + cmd_gsstsig, + cmd_key, + cmd_local, + cmd_oldgsstsig, + cmd_origin, + cmd_prereq, + cmd_realm, + cmd_send, + cmd_server, + cmd_show, + cmd_ttl, + cmd_update, + cmd_zone, +}; + +/* {prereq} command table. */ +const char* pq_array[] = { + "\x8" "nxdomain", + "\x7" "nxrrset", + "\x8" "yxdomain", + "\x7" "yxrrset", + NULL +}; +enum { + PQ_NXDOMAIN = 0, + PQ_NXRRSET, + PQ_YXDOMAIN, + PQ_YXRRSET, + UP_ADD, + UP_DEL +}; + +/* RR parser flags */ +enum { + PARSE_NODEFAULT = 1 << 0, /* Do not fill defaults. */ + PARSE_NAMEONLY = 1 << 1 /* Parse only name. */ +}; + +static int dname_isvalid(const char *lp, size_t len) { + knot_dname_t *dn = knot_dname_new_from_str(lp, len, NULL); + if (dn == NULL) { + return 0; + } + knot_dname_free(&dn); + return 1; +} + +/* This is probably redundant, but should be a bit faster so let's keep it. */ +static int parse_full_rr(scanner_t *s, const char* lp) +{ + if (scanner_process(lp, lp + strlen(lp), 0, s) < 0) { + return KNOT_EPARSEFAIL; + } + char nl = '\n'; /* Ensure newline after complete RR */ + if (scanner_process(&nl, &nl+sizeof(char), 1, s) < 0) { /* Terminate */ + return KNOT_EPARSEFAIL; + } + + /* Class must not differ from specified. */ + if (s->r_class != s->default_class) { + char cls_s[16] = {0}; + knot_rrclass_to_string(s->default_class, cls_s, sizeof(cls_s)); + ERR("class mismatch: '%s'\n", cls_s); + return KNOT_EPARSEFAIL; + } + + return KNOT_EOK; +} + +static int parse_partial_rr(scanner_t *s, const char *lp, unsigned flags) { + int ret = KNOT_EOK; + char b1[32], b2[32]; /* Should suffice for both class/type */ + + /* Extract owner. */ + size_t len = strcspn(lp, SEP_CHARS); + knot_dname_t *owner = knot_dname_new_from_str(lp, len, NULL); + if (owner == NULL) { + return KNOT_EPARSEFAIL; + } + + /* ISC nsupdate doesn't do this, but it seems right to me. */ + if (!knot_dname_is_fqdn(owner)) { + knot_dname_t* suf = knot_dname_new_from_wire(s->zone_origin, + s->zone_origin_length, + NULL); + if (suf == NULL) { + knot_dname_free(&owner); + return KNOT_ENOMEM; + } + knot_dname_cat(owner, suf); + knot_dname_free(&suf); + } + + s->r_owner_length = knot_dname_size(owner); + memcpy(s->r_owner, knot_dname_name(owner), s->r_owner_length); + lp = tok_skipspace(lp + len); + + /* Initialize */ + s->r_type = KNOT_RRTYPE_ANY; + s->r_class = s->default_class; + s->r_data_length = 0; + if (flags & PARSE_NODEFAULT) { + s->r_ttl = 0; + } else { + s->r_ttl = s->default_ttl; + } + + /* Parse only name? */ + if (flags & PARSE_NAMEONLY) { + knot_dname_free(&owner); + return KNOT_EOK; + } + + /* Now there could be [ttl] [class] [type [data...]]. */ + /*! \todo support for fancy time format in ttl */ + char *np = NULL; + long ttl = strtol(lp, &np, 10); + if (ttl >= 0 && np && (*np == '\0' || isspace((unsigned char)(*np)))) { + s->r_ttl = ttl; + DBG("%s: parsed ttl=%lu\n", __func__, ttl); + lp = tok_skipspace(np); + } + + len = strcspn(lp, SEP_CHARS); /* Try to find class */ + memset(b1, 0, sizeof(b1)); + strncpy(b1, lp, len < sizeof(b1) ? len : sizeof(b1)); + + uint16_t v; + if (knot_rrclass_from_string(b1, &v) == 0) { + s->r_class = v; + DBG("%s: parsed class=%u\n", __func__, s->r_class); + lp = tok_skipspace(lp + len); + } + + /* Class must not differ from specified. */ + if (s->r_class != s->default_class) { + char cls_s[16] = {0}; + knot_rrclass_to_string(s->default_class, cls_s, sizeof(cls_s)); + ERR("class mismatch: '%s'\n", cls_s); + knot_dname_free(&owner); + return KNOT_EPARSEFAIL; + } + + len = strcspn(lp, SEP_CHARS); /* Type */ + memset(b2, 0, sizeof(b2)); + strncpy(b2, lp, len < sizeof(b2) ? len : sizeof(b2)); + if (knot_rrtype_from_string(b2, &v) == 0) { + s->r_type = v; + DBG("%s: parsed type=%u '%s'\n", __func__, s->r_type, b2); + lp = tok_skipspace(lp + len); + } + + /* Remainder */ + if (*lp == '\0') { + knot_dname_free(&owner); + return ret; /* No RDATA */ + } + + /* Synthetize full RR line to prevent consistency errors. */ + char *owner_s = knot_dname_to_str(owner); + knot_rrclass_to_string(s->r_class, b1, sizeof(b1)); + knot_rrtype_to_string(s->r_type, b2, sizeof(b2)); + + /* Need to parse rdata, synthetize input. */ + char *rr = sprintf_alloc("%s %u %s %s %s\n", + owner_s, s->r_ttl, b1, b2, lp); + if (scanner_process(rr, rr + strlen(rr), 1, s) < 0) { + ret = KNOT_EPARSEFAIL; + } + + free(owner_s); + free(rr); + knot_dname_free(&owner); + return ret; +} + +static server_t *parse_host(const char *lp, const char* default_port) +{ + /* Extract server address. */ + server_t *srv = NULL; + size_t len = strcspn(lp, SEP_CHARS); + char *addr = strndup(lp, len); + if (!addr) return NULL; + DBG("%s: parsed addr: %s\n", __func__, addr); + + /* Store port/service if present. */ + lp = tok_skipspace(lp + len); + if (*lp == '\0') { + srv = server_create(addr, default_port); + free(addr); + return srv; + } + + len = strcspn(lp, SEP_CHARS); + char *port = strndup(lp, len); + if (!port) { + free(addr); + return NULL; + } + DBG("%s: parsed port: %s\n", __func__, port); + + /* Create server struct. */ + srv = server_create(addr, port); + free(addr); + free(port); + return srv; +} + +static int pkt_append(nsupdate_params_t *p, int sect) +{ + /* Check packet state first. */ + int ret = KNOT_EOK; + scanner_t *s = p->rrp; + if (!p->pkt) { + p->pkt = create_empty_packet(KNOT_PACKET_PREALLOC_RESPONSE, + MAX_PACKET_SIZE); + knot_question_t q; + q.qclass = p->class_num; + q.qtype = p->type_num; + q.qname = knot_dname_new_from_nonfqdn_str(p->zone, strlen(p->zone), NULL); + ret = knot_query_set_question(p->pkt, &q); + if (ret != KNOT_EOK) { + return ret; + } + knot_query_set_opcode(p->pkt, KNOT_OPCODE_UPDATE); + } + + /* Form a rrset. */ + knot_dname_t *o = knot_dname_new_from_wire(s->r_owner, s->r_owner_length, NULL); + if (!o) { + DBG("%s: failed to create dname - %s\n", + __func__, knot_strerror(ret)); + return KNOT_ENOMEM; + } + knot_rrset_t *rr = knot_rrset_new(o, s->r_type, s->r_class, s->r_ttl); + knot_dname_release(o); + if (!rr) { + DBG("%s: failed to create rrset - %s\n", + __func__, knot_strerror(ret)); + return KNOT_ENOMEM; + } + + /* Create RDATA (not for NXRRSET prereq). */ + if (s->r_data_length > 0 && sect != PQ_NXRRSET) { + size_t pos = 0; + ret = knot_rrset_rdata_from_wire_one(rr, s->r_data, &pos, + s->r_data_length, + s->r_data_length); + if (ret != KNOT_EOK) { + DBG("%s: failed to set rrset from wire - %s\n", + __func__, knot_strerror(ret)); + knot_rrset_free(&rr); + return ret; + } + } + + /* Add to correct section. + * ZONES ... QD section. + * UPDATE ... NS section. + * PREREQ ... AN section. + * ADDIT. ... same. + */ + switch(sect) { + case UP_ADD: + case UP_DEL: + ret = knot_response_add_rrset_authority(p->pkt, rr, 0, 0, 0); + break; + case PQ_NXDOMAIN: + case PQ_NXRRSET: + case PQ_YXDOMAIN: + case PQ_YXRRSET: + ret = knot_response_add_rrset_answer(p->pkt, rr, 0, 0, 0); + break; + default: + assert(0); /* Should never happen. */ + break; + } + + if (ret != KNOT_EOK) { + DBG("%s: failed to append rdata to appropriate section - %s\n", + __func__, knot_strerror(ret)); + if (ret == KNOT_ESPACE) { + ERR("exceeded UPDATE message maximum size %zu\n", + knot_packet_max_size(p->pkt)); + } + } + + return ret; +} + +static int pkt_sendrecv(nsupdate_params_t *params, + uint8_t *qwire, size_t qlen, + uint8_t *rwire, size_t rlen) +{ + net_t net; + int ret; + + ret = net_init(params->srcif, + params->server, + get_iptype(params->ip), + get_socktype(params->protocol, KNOT_RRTYPE_SOA), + params->wait, + &net); + + ret = net_connect(&net); + DBG("%s: send_msg = %d\n", __func__, net.sockfd); + if (ret != KNOT_EOK) return -1; + + ret = net_send(&net, qwire, qlen); + if (ret != KNOT_EOK) { + net_close(&net); + net_clean(&net); + return -1; + } + + /* Wait for reception. */ + int rb = net_receive(&net, rwire, rlen); + DBG("%s: receive_msg = %d\n", __func__, rb); + if (rb <= 0) { + net_close(&net); + net_clean(&net); + return -1; + } + + net_close(&net); + net_clean(&net); + + return rb; +} + +static int nsupdate_process_line(char *lp, int len, void *arg) +{ + nsupdate_params_t *params = (nsupdate_params_t *)arg; + + /* Remove trailing white space chars. */ + for (int i = len - 1; i >= 0; i--) { + if (isspace((unsigned char)lp[i]) == 0) { + break; + } + lp[i] = '\0'; + } + + /* Check for empty line or comment. */ + if (lp[0] == '\0' || lp[0] == ';') { + return KNOT_EOK; + } + + int ret = tok_find(lp, cmd_array); + if (ret < 0) { + return KNOT_EOK; /* Syntax error - do nothing. */ + } + + const char *cmd = cmd_array[ret]; + const char *val = tok_skipspace(lp + TOK_L(cmd)); + ret = cmd_handle[ret](val, params); + if (ret != KNOT_EOK) { + DBG("operation '%s' failed (%s)\n", + TOK_S(cmd), knot_strerror(ret)); + } + + return KNOT_EOK; +} + +static int nsupdate_process(nsupdate_params_t *params, FILE *fp) +{ + /* Process lines. */ + int ret = tok_process_lines(fp, nsupdate_process_line, params); + + /* Check for longing query. */ + if (params->pkt && ret == KNOT_EOK) { + cmd_send("", params); + } + + /* Free last answer. */ + if (params->resp) { + knot_packet_free(¶ms->resp); + } + + return ret; +} + +int nsupdate_exec(nsupdate_params_t *params) +{ + if (!params) { + return KNOT_EINVAL; + } + + int ret = KNOT_EOK; + + /* If not file specified, use stdin. */ + if (EMPTY_LIST(params->qfiles)) { + return nsupdate_process(params, stdin); + } + + /* Read from each specified file. */ + strnode_t *n = NULL; + WALK_LIST(n, params->qfiles) { + if (strcmp(n->str, "-") == 0) { + ret = nsupdate_process(params, stdin); + continue; + } + FILE *fp = fopen(n->str, "r"); + if (!fp) { + ERR("could not open '%s': %s\n", + n->str, strerror(errno)); + return KNOT_ERROR; + } + ret = nsupdate_process(params, fp); + fclose(fp); + } + + return ret; +} + +int cmd_update(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + /* update is optional token, next add|del|delete */ + int bp = tok_find(lp, cmd_array); + if (bp < 0) return bp; /* Syntax error. */ + + /* allow only specific tokens */ + cmd_handle_f *h = cmd_handle; + if (h[bp] != cmd_add && h[bp] != cmd_del) { + ERR("unexpected token '%s' after 'update', allowed: '%s'\n", + lp, "{add|del|delete}"); + return KNOT_EPARSEFAIL; + } + + return h[bp](tok_skipspace(lp + TOK_L(cmd_array[bp])), params); +} + + +int cmd_add(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + scanner_t *rrp = params->rrp; + if (parse_full_rr(rrp, lp) != KNOT_EOK) { + return KNOT_EPARSEFAIL; + } + + /* Parsed RR */ + DBG("%s: parsed rr cls=%u, ttl=%u, type=%u (rdata len=%u)\n", + __func__, rrp->r_class, rrp->r_ttl,rrp->r_type, rrp->r_data_length); + + return pkt_append(params, UP_ADD); /* Append to packet. */ +} + +int cmd_del(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + scanner_t *rrp = params->rrp; + if (parse_partial_rr(rrp, lp, PARSE_NODEFAULT) != KNOT_EOK) { + return KNOT_EPARSEFAIL; + } + + /* Check owner name. */ + if (rrp->r_owner_length == 0) { + ERR("failed to parse prereq owner name '%s'\n", lp); + return KNOT_EPARSEFAIL; + } + + rrp->r_ttl = 0; /* Set TTL = 0 when deleting. */ + + /* When deleting whole RRSet, use ANY class */ + if (rrp->r_data_length == 0) { + rrp->r_class = KNOT_CLASS_ANY; + } else { + rrp->r_class = KNOT_CLASS_NONE; + } + + /* Parsed RR */ + DBG("%s: parsed rr cls=%u, ttl=%u, type=%u (rdata len=%u)\n", + __func__, rrp->r_class, rrp->r_ttl,rrp->r_type, rrp->r_data_length); + + return pkt_append(params, UP_DEL); /* Append to packet. */ +} + +int cmd_class(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + uint16_t cls; + + if (knot_rrclass_from_string(lp, &cls) != 0) { + ERR("failed to parse class '%s'\n", lp); + return KNOT_EPARSEFAIL; + } else { + params->class_num = cls; + scanner_t *s = params->rrp; + s->default_class = params->class_num; + } + + return KNOT_EOK; +} + +int cmd_ttl(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + uint32_t ttl = 0; + + if (params_parse_num(lp, &ttl) != KNOT_EOK) { + return KNOT_EPARSEFAIL; + } + + return nsupdate_set_ttl(params, ttl); +} + +int cmd_debug(const char* lp, nsupdate_params_t *params) +{ + UNUSED(params); + DBG("%s: lp='%s'\n", __func__, lp); + + msg_enable_debug(1); + return KNOT_EOK; +} + +int cmd_prereq_domain(const char *lp, nsupdate_params_t *params, unsigned type) +{ + UNUSED(type); + DBG("%s: lp='%s'\n", __func__, lp); + + scanner_t *s = params->rrp; + int ret = parse_partial_rr(s, lp, PARSE_NODEFAULT|PARSE_NAMEONLY); + if (ret != KNOT_EOK) { + return ret; + } + + return ret; +} + +int cmd_prereq_rrset(const char *lp, nsupdate_params_t *params, unsigned type) +{ + UNUSED(type); + DBG("%s: lp='%s'\n", __func__, lp); + + scanner_t *rrp = params->rrp; + if (parse_partial_rr(rrp, lp, 0) != KNOT_EOK) { + return KNOT_EPARSEFAIL; + } + + /* Check owner name. */ + if (rrp->r_owner_length == 0) { + ERR("failed to parse prereq owner name '%s'\n", lp); + return KNOT_EPARSEFAIL; + } + + /* Parsed RR */ + DBG("%s: parsed rr cls=%u, ttl=%u, type=%u (rdata len=%u)\n", + __func__, rrp->r_class, rrp->r_ttl,rrp->r_type, rrp->r_data_length); + + return KNOT_EOK; +} + +int cmd_prereq(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + /* Scan prereq specifier ([ny]xrrset|[ny]xdomain) */ + int ret = KNOT_EOK; + int bp = tok_find(lp, pq_array); + if (bp < 0) return bp; /* Syntax error. */ + + const char *tok = pq_array[bp]; + DBG("%s: type %s\n", __func__, TOK_S(tok)); + lp = tok_skipspace(lp + TOK_L(tok)); + switch(bp) { + case PQ_NXDOMAIN: + case PQ_YXDOMAIN: + ret = cmd_prereq_domain(lp, params, bp); + break; + case PQ_NXRRSET: + case PQ_YXRRSET: + ret = cmd_prereq_rrset(lp, params, bp); + break; + default: + return KNOT_ERROR; + } + + /* Append to packet. */ + if (ret == KNOT_EOK) { + scanner_t *s = params->rrp; + s->r_ttl = 0; /* Set TTL = 0 for prereq. */ + /* YX{RRSET,DOMAIN} - cls ANY */ + if (bp == PQ_YXRRSET || bp == PQ_YXDOMAIN) { + s->r_class = KNOT_CLASS_ANY; + } else { /* NX{RRSET,DOMAIN} - cls NONE */ + s->r_class = KNOT_CLASS_NONE; + } + + ret = pkt_append(params, bp); + } + + return ret; +} + +int cmd_send(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + DBG("sending packet\n"); + + /* Create wireformat. */ + int ret = KNOT_EOK; + uint8_t *wire = NULL; + size_t len = 0; + + if ((ret = knot_packet_to_wire(params->pkt, &wire, &len))!= KNOT_EOK) { + ERR("couldn't serialize packet, %s\n", knot_strerror(ret)); + return ret; + } + + sign_context_t sign_ctx; + memset(&sign_ctx, '\0', sizeof(sign_context_t)); + + /* Sign if key specified. */ + if (params->key_params.name) { + ret = sign_packet(params->pkt, &sign_ctx, ¶ms->key_params); + if (ret != KNOT_EOK) { + ERR("failed to sign UPDATE message - %s\n", + knot_strerror(ret)); + return ret; + } + len = params->pkt->size; + } + + int rb = 0; + /* Send/recv message (1 try + N retries). */ + int tries = 1 + params->retries; + for (; tries > 0; --tries) { + memset(params->rwire, 0, sizeof(params->rwire)); + rb = pkt_sendrecv(params, wire, len, + params->rwire, sizeof(params->rwire)); + if (rb > 0) break; + } + + /* Clear sent packet. */ + knot_question_t *q = knot_packet_question(params->pkt); + knot_dname_release(q->qname); + knot_packet_free_rrsets(params->pkt); + knot_packet_free(¶ms->pkt); + + /* Clear previous response. */ + if (params->resp) { + knot_packet_free(¶ms->resp); + } + + /* Check Send/recv result. */ + if (rb <= 0) { + free_sign_context(&sign_ctx); + return KNOT_ECONNREFUSED; + } + + /* Parse response. */ + params->resp = knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + if (!params->resp) { + free_sign_context(&sign_ctx); + return KNOT_ENOMEM; + } + ret = knot_packet_parse_from_wire(params->resp, params->rwire, rb, 0, 0); + if (ret != KNOT_EOK) { + ERR("failed to parse response, %s\n", knot_strerror(ret)); + free_sign_context(&sign_ctx); + return ret; + } + + /* Check signature if expected. */ + if (params->key_params.name) { + ret = verify_packet(params->resp, &sign_ctx, ¶ms->key_params); + free_sign_context(&sign_ctx); + if (ret != KNOT_EOK) { /* Collect TSIG error. */ + fprintf(stderr, "%s: %s\n", "; TSIG error with server", + knot_strerror(ret)); + return ret; + } + } + + /* Check return code. */ + knot_lookup_table_t *rcode; + int rc = knot_packet_rcode(params->resp); + DBG("%s: received rcode=%d\n", __func__, rc); + rcode = knot_lookup_by_id(knot_rcode_names, rc); + if (rcode && rcode->id > KNOT_RCODE_NOERROR) { + ERR("update failed: %s\n", rcode->name); + } + + /*! \todo Should we check TC bit? */ + + return KNOT_EOK; +} + +int cmd_zone(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + /* Check zone name. */ + size_t len = strcspn(lp, SEP_CHARS); + if (!dname_isvalid(lp, len)) { + ERR("failed to parse zone '%s'\n", lp); + return KNOT_EPARSEFAIL; + } + + free(params->zone); + params->zone = strndup(lp, len); + + return KNOT_EOK; +} + +int cmd_server(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + /* Parse host. */ + server_t *srv = parse_host(lp, params->server->service); + + /* Enqueue. */ + if (!srv) return KNOT_ENOMEM; + + server_free(params->server); + params->server = srv; + + return KNOT_EOK; +} + +int cmd_local(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + /* Parse host. */ + server_t *srv = parse_host(lp, "0"); + + /* Enqueue. */ + if (!srv) return KNOT_ENOMEM; + + server_free(params->srcif); + params->srcif = srv; + + return KNOT_EOK; +} + +int cmd_show(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + /* Show current packet. */ + if (!params->pkt) return KNOT_EOK; + printf("Outgoing update query:\n"); + size_t len = knot_packet_size(params->pkt); + print_packet(params->pkt, len, NULL, -1, false, ¶ms->style); + return KNOT_EOK; +} + +int cmd_answer(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + /* Show current answer. */ + if (!params->resp) return KNOT_EOK; + printf("\nAnswer:\n"); + size_t len = knot_packet_size(params->resp); + print_packet(params->resp, len, NULL, -1, true, ¶ms->style); + return KNOT_EOK; +} + +int cmd_key(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + char *kstr = strdup(lp); /* Convert to default format. */ + if (!kstr) return KNOT_ENOMEM; + + int ret = KNOT_EOK; + size_t len = strcspn(lp, SEP_CHARS); + if(kstr[len] == '\0') { + ERR("command 'key' without {secret} specified\n"); + ret = KNOT_EINVAL; + } else { + // override existing key + knot_free_key_params(¶ms->key_params); + + kstr[len] = ':'; /* Replace ' ' with ':' sep */ + ret = params_parse_tsig(kstr, ¶ms->key_params); + } + + free(kstr); + return ret; +} + +/* + * Not implemented. + */ + +int cmd_gsstsig(const char* lp, nsupdate_params_t *params) +{ + UNUSED(params); + DBG("%s: lp='%s'\n", __func__, lp); + + return KNOT_ENOTSUP; +} + +int cmd_oldgsstsig(const char* lp, nsupdate_params_t *params) +{ + UNUSED(params); + DBG("%s: lp='%s'\n", __func__, lp); + + return KNOT_ENOTSUP; +} + +int cmd_origin(const char* lp, nsupdate_params_t *params) +{ + DBG("%s: lp='%s'\n", __func__, lp); + + /* Check zone name. */ + size_t len = strcspn(lp, SEP_CHARS); + if (!dname_isvalid(lp, len)) { + ERR("failed to parse zone '%s'\n", lp); + return KNOT_EPARSEFAIL; + } + + char *name = strndup(lp, len); + + int ret = nsupdate_set_origin(params, name); + + free(name); + + return ret; +} + +int cmd_realm(const char* lp, nsupdate_params_t *params) +{ + UNUSED(params); + DBG("%s: lp='%s'\n", __func__, lp); + + return KNOT_ENOTSUP; +} |