static int sign_ctx_add_self(dnssec_sign_ctx_t *ctx, const uint8_t *rdata) { assert(ctx); assert(rdata); int result; // static header dnssec_binary_t header = { 0 }; header.data = (uint8_t *)rdata; header.size = RRSIG_RDATA_SIGNER_OFFSET; result = dnssec_sign_add(ctx, &header); if (result != DNSSEC_EOK) { return result; } // signer name const uint8_t *rdata_signer = rdata + RRSIG_RDATA_SIGNER_OFFSET; dnssec_binary_t signer = { 0 }; signer.data = knot_dname_copy(rdata_signer, NULL); signer.size = knot_dname_size(signer.data); result = dnssec_sign_add(ctx, &signer); free(signer.data); return result; }
/** * Adjust TTL in wire format. * @param wire RR Set in wire format. * @param wire_size Size of the wire data portion. * @param new_ttl TTL value to be set for all RRs. * @return 0 or error code. */ static int adjust_wire_ttl(uint8_t *wire, size_t wire_size, uint32_t new_ttl) { assert(wire); assert(sizeof(uint16_t) == 2); assert(sizeof(uint32_t) == 4); uint16_t rdlen; int ret; new_ttl = htonl(new_ttl); size_t i = 0; /* RR wire format in RFC1035 3.2.1 */ while(i < wire_size) { ret = knot_dname_size(wire + i); if (ret < 0) { return ret; } i += ret + 4; memcpy(wire + i, &new_ttl, sizeof(uint32_t)); i += sizeof(uint32_t); memcpy(&rdlen, wire + i, sizeof(uint16_t)); rdlen = ntohs(rdlen); i += sizeof(uint16_t) + rdlen; assert(i <= wire_size); } return kr_ok(); }
conf_val_t conf_zone_get_txn( conf_t *conf, knot_db_txn_t *txn, const yp_name_t *key1_name, const knot_dname_t *dname) { conf_val_t val = { NULL }; if (key1_name == NULL || dname == NULL) { val.code = KNOT_EINVAL; CONF_LOG(LOG_DEBUG, "conf_zone_get (%s)", knot_strerror(val.code)); return val; } int dname_size = knot_dname_size(dname); // Try to get explicit value. conf_db_get(conf, txn, C_ZONE, key1_name, dname, dname_size, &val); switch (val.code) { case KNOT_EOK: return val; default: CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)", C_ZONE + 1, key1_name + 1, knot_strerror(val.code)); // FALLTHROUGH case KNOT_ENOENT: break; } // Check if a template is available. conf_db_get(conf, txn, C_ZONE, C_TPL, dname, dname_size, &val); switch (val.code) { case KNOT_EOK: // Use the specified template. conf_val(&val); conf_db_get(conf, txn, C_TPL, key1_name, val.data, val.len, &val); break; default: CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)", C_ZONE + 1, C_TPL + 1, knot_strerror(val.code)); // FALLTHROUGH case KNOT_ENOENT: // Use the default template. conf_db_get(conf, txn, C_TPL, key1_name, CONF_DEFAULT_ID + 1, CONF_DEFAULT_ID[0], &val); } switch (val.code) { default: CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)", C_TPL + 1, key1_name + 1, knot_strerror(val.code)); // FALLTHROUGH case KNOT_EOK: case KNOT_ENOENT: break; } return val; }
/*----------------------------------------------------------------------------*/ _public_ knot_dname_t *knot_dname_copy(const knot_dname_t *name, mm_ctx_t *mm) { if (name == NULL) return NULL; return knot_dname_copy_part(name, knot_dname_size(name), mm); }
static int eval_nsrep(const char *k, void *v, void *baton) { struct kr_query *qry = baton; struct kr_nsrep *ns = &qry->ns; struct kr_context *ctx = ns->ctx; unsigned score = KR_NS_MAX_SCORE; unsigned reputation = 0; uint8_t *addr = NULL; /* Fetch NS reputation */ if (ctx->cache_rep) { unsigned *cached = lru_get(ctx->cache_rep, k, knot_dname_size((const uint8_t *)k)); if (cached) { reputation = *cached; } } /* Favour nameservers with unknown addresses to probe them, * otherwise discover the current best address for the NS. */ pack_t *addr_set = (pack_t *)v; if (addr_set->len == 0) { score = KR_NS_UNKNOWN; /* If the server doesn't have IPv6, give it disadvantage. */ if (reputation & KR_NS_NOIP6) { score += FAVOUR_IPV6; /* If the server is unknown but has rep record, treat it as timeouted */ if (reputation & KR_NS_NOIP4) { score = KR_NS_TIMEOUT; reputation = 0; /* Start with clean slate */ } } } else { score = eval_addr_set(addr_set, ctx->cache_rtt, score, &addr); } /* Probabilistic bee foraging strategy (naive). * The fastest NS is preferred by workers until it is depleted (timeouts or degrades), * at the same time long distance scouts probe other sources (low probability). * Servers on TIMEOUT (depleted) can be probed by the dice roll only */ if (score < ns->score && (qry->flags & QUERY_NO_THROTTLE || score < KR_NS_TIMEOUT)) { update_nsrep(ns, (const knot_dname_t *)k, addr, score); ns->reputation = reputation; } else { /* With 5% chance, probe server with a probability given by its RTT / MAX_RTT */ if ((kr_rand_uint(100) < 5) && (kr_rand_uint(KR_NS_MAX_SCORE) >= score)) { /* If this is a low-reliability probe, go with TCP to get ICMP reachability check. */ if (score >= KR_NS_LONG) { qry->flags |= QUERY_TCP; } update_nsrep(ns, (const knot_dname_t *)k, addr, score); ns->reputation = reputation; return 1; /* Stop evaluation */ } } return kr_ok(); }
static int reverse_rr(char *addr_str, synth_template_t *tpl, knot_pkt_t *pkt, knot_rrset_t *rr) { /* Synthetize PTR record data. */ knot_dname_t *ptrname = synth_ptrname(addr_str, tpl); if (ptrname == NULL) { return KNOT_ENOMEM; } rr->type = KNOT_RRTYPE_PTR; knot_rrset_add_rdata(rr, ptrname, knot_dname_size(ptrname), tpl->ttl, &pkt->mm); knot_dname_free(&ptrname, NULL); return KNOT_EOK; }
/*----------------------------------------------------------------------------*/ _public_ int knot_dname_to_wire(uint8_t *dst, const knot_dname_t *src, size_t maxlen) { if (dst == NULL || src == NULL) { return KNOT_EINVAL; } int len = knot_dname_size(src); if (len > maxlen) { return KNOT_ESPACE; } memcpy(dst, src, len); return len; }
int kr_nsrep_update_rep(struct kr_nsrep *ns, unsigned reputation, kr_nsrep_lru_t *cache) { if (!ns || !cache ) { return kr_error(EINVAL); } /* Store in the struct */ ns->reputation = reputation; /* Store reputation in the LRU cache */ unsigned *cur = lru_set(cache, (const char *)ns->name, knot_dname_size(ns->name)); if (!cur) { return kr_error(ENOMEM); } *cur = reputation; return kr_ok(); }
/*----------------------------------------------------------------------------*/ _public_ knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *name, unsigned labels, const knot_dname_t *suffix) { if (name == NULL) return NULL; /* Calculate prefix and suffix lengths. */ int dname_lbs = knot_dname_labels(name, NULL); assert(dname_lbs >= labels); unsigned prefix_lbs = dname_lbs - labels; /* Trim 1 octet from prefix, as it is measured as FQDN. */ int prefix_len = knot_dname_prefixlen(name, prefix_lbs, NULL) - 1; int suffix_len = knot_dname_size(suffix); if (prefix_len < 0 || suffix_len < 0) return NULL; /* Create target name. */ int new_len = prefix_len + suffix_len; knot_dname_t *out = malloc(new_len); if (out == NULL) return NULL; /* Copy prefix. */ uint8_t *dst = out; while (prefix_lbs > 0) { memcpy(dst, name, *name + 1); dst += *name + 1; name = knot_wire_next_label(name, NULL); --prefix_lbs; } /* Copy suffix. */ while (*suffix != '\0') { memcpy(dst, suffix, *suffix + 1); dst += *suffix + 1; suffix = knot_wire_next_label(suffix, NULL); } *dst = '\0'; return out; }
/*! * \brief Create NSEC3 owner name from regular owner name. * * \param owner Node owner name. * \param zone_apex Zone apex name. * \param params Params for NSEC3 hashing function. * * \return NSEC3 owner name, NULL in case of error. */ knot_dname_t *knot_create_nsec3_owner(const knot_dname_t *owner, const knot_dname_t *zone_apex, const knot_nsec3_params_t *params) { if (owner == NULL || zone_apex == NULL || params == NULL) { return NULL; } int owner_size = knot_dname_size(owner); if (owner_size < 0) { return NULL; } dnssec_binary_t data = { 0 }; data.data = (uint8_t *)owner; data.size = owner_size; dnssec_binary_t hash = { 0 }; dnssec_nsec3_params_t xparams = { .algorithm = params->algorithm, .flags = params->flags, .iterations = params->iterations, .salt = { .data = params->salt, .size = params->salt_length } }; int r = dnssec_nsec3_hash(&data, &xparams, &hash); if (r != DNSSEC_EOK) { return NULL; } knot_dname_t *result = knot_nsec3_hash_to_dname(hash.data, hash.size, zone_apex); dnssec_binary_free(&hash); return result; }
/*! * \brief Create NSEC3 owner name from hash and zone apex. */ knot_dname_t *knot_nsec3_hash_to_dname(const uint8_t *hash, size_t hash_size, const knot_dname_t *zone_apex) { assert(zone_apex); // encode raw hash to first label uint8_t label[KNOT_DNAME_MAXLEN]; int32_t label_size; label_size = base32hex_encode(hash, hash_size, label, sizeof(label)); if (label_size <= 0) { return NULL; } // allocate result size_t zone_apex_size = knot_dname_size(zone_apex); size_t result_size = 1 + label_size + zone_apex_size; knot_dname_t *result = malloc(result_size); if (!result) { return NULL; } // build the result uint8_t *write = result; *write = (uint8_t)label_size; write += 1; memcpy(write, label, label_size); write += label_size; memcpy(write, zone_apex, zone_apex_size); write += zone_apex_size; assert(write == result + result_size); knot_dname_to_lower(result); return result; }
/*! * \brief Create NSEC RR set. * * \param from Node that should contain the new RRSet * \param to Node that should be pointed to from 'from' * \param ttl Record TTL (SOA's minimum TTL). * * \return NSEC RR set, NULL on error. */ static knot_rrset_t *create_nsec_rrset(const zone_node_t *from, const zone_node_t *to, uint32_t ttl) { assert(from); assert(to); knot_rrset_t *rrset = knot_rrset_new(from->owner, KNOT_RRTYPE_NSEC, KNOT_CLASS_IN, NULL); if (!rrset) { return NULL; } // Create bitmap bitmap_t rr_types = { 0 }; bitmap_add_node_rrsets(&rr_types, from); bitmap_add_type(&rr_types, KNOT_RRTYPE_NSEC); bitmap_add_type(&rr_types, KNOT_RRTYPE_RRSIG); if (node_rrtype_exists(from, KNOT_RRTYPE_SOA)) { bitmap_add_type(&rr_types, KNOT_RRTYPE_DNSKEY); } // Create RDATA assert(to->owner); size_t next_owner_size = knot_dname_size(to->owner); size_t rdata_size = next_owner_size + bitmap_size(&rr_types); uint8_t rdata[rdata_size]; // Fill RDATA memcpy(rdata, to->owner, next_owner_size); bitmap_write(&rr_types, rdata + next_owner_size); int ret = knot_rrset_add_rdata(rrset, rdata, rdata_size, ttl, NULL); if (ret != KNOT_EOK) { knot_rrset_free(&rrset, NULL); return NULL; } return rrset; }
static int rrl_clsname(char *dst, size_t maxlen, uint8_t cls, rrl_req_t *p, const knot_zone_t *z) { const knot_dname_t *dn = NULL; const uint8_t *n = (const uint8_t*)"\x00"; /* Fallback zone (for errors etc.) */ int nb = 1; if (z) { /* Found associated zone. */ dn = knot_zone_name(z); } switch (cls) { case CLS_ERROR: /* Could be a non-existent zone or garbage. */ case CLS_NXDOMAIN: /* Queries to non-existent names in zone. */ case CLS_WILDCARD: /* Queries to names covered by a wildcard. */ dbg_rrl_verb("%s: using zone/fallback name\n", __func__); break; default: if (p->qst) dn = p->qst->qname; break; } if (dn) { /* Check used dname. */ assert(dn); /* Should be always set. */ n = knot_dname_name(dn); nb = (int)knot_dname_size(dn); } /* Write to wire */ if (nb > maxlen) return KNOT_ESPACE; if (memcpy(dst, n, nb) == NULL) { dbg_rrl("%s: failed to serialize name=%p len=%u\n", __func__, n, nb); return KNOT_ERROR; } return nb; }
/*----------------------------------------------------------------------------*/ _public_ char *knot_dname_to_str(char *dst, const knot_dname_t *name, size_t maxlen) { if (name == NULL) { return NULL; } int dname_size = knot_dname_size(name); if (dname_size <= 0) { return NULL; } /* Check the size for len(dname) + 1 char termination. */ size_t alloc_size = (dst == NULL) ? dname_size + 1 : maxlen; if (alloc_size < dname_size + 1) { return NULL; } char *res = (dst == NULL) ? malloc(alloc_size) : dst; if (res == NULL) { return NULL; } uint8_t label_len = 0; size_t str_len = 0; for (unsigned i = 0; i < dname_size; i++) { uint8_t c = name[i]; /* Read next label size. */ if (label_len == 0) { label_len = c; /* Write label separation. */ if (str_len > 0 || dname_size == 1) { res[str_len++] = '.'; } continue; } if (isalnum(c) != 0 || c == '-' || c == '_' || c == '*' || c == '/') { res[str_len++] = c; } else if (ispunct(c) != 0 && c != '#') { /* Exclusion of '#' character is to avoid possible * collision with rdata hex notation '\#'. So it is * encoded in \ddd notation. */ if (dst != NULL) { if (maxlen <= str_len + 2) { return NULL; } } else { /* Extend output buffer for \x format. */ alloc_size += 1; char *extended = realloc(res, alloc_size); if (extended == NULL) { free(res); return NULL; } res = extended; } /* Write encoded character. */ res[str_len++] = '\\'; res[str_len++] = c; } else { if (dst != NULL) { if (maxlen <= str_len + 4) { return NULL; } } else { /* Extend output buffer for \DDD format. */ alloc_size += 3; char *extended = realloc(res, alloc_size); if (extended == NULL) { free(res); return NULL; } res = extended; } /* Write encoded character. */ int ret = snprintf(res + str_len, alloc_size - str_len, "\\%03u", c); if (ret <= 0 || ret >= alloc_size - str_len) { if (dst == NULL) { free(res); } return NULL; } str_len += ret; } label_len--; } /* String_termination. */ res[str_len] = 0; return res; }
/*! * \brief Entry point of 'knsec3hash'. */ int main(int argc, char *argv[]) { bool enable_idn = true; struct option options[] = { { "version", no_argument, 0, 'V' }, { "help", no_argument, 0, 'h' }, { NULL } }; #ifdef LIBIDN // Set up localization. if (setlocale(LC_CTYPE, "") == NULL) { enable_idn = false; } #endif int opt = 0; int li = 0; while ((opt = getopt_long(argc, argv, "hV", options, &li)) != -1) { switch(opt) { case 'V': printf("%s, version %s\n", PROGRAM_NAME, PACKAGE_VERSION); return 0; case 'h': usage(stdout); return 0; default: usage(stderr); return 1; } } // knsec3hash <salt> <algorithm> <iterations> <domain> if (argc != 5) { usage(stderr); return 1; } atexit(knot_crypto_cleanup); int exit_code = 1; knot_nsec3_params_t nsec3_params = { 0 }; knot_dname_t *dname = NULL; uint8_t *digest = NULL; size_t digest_size = 0; uint8_t *b32_digest = NULL; int32_t b32_length = 0; int result = 0; if (!parse_nsec3_params(&nsec3_params, argv[1], argv[2], argv[3])) { goto fail; } if (enable_idn) { char *ascii_name = name_from_idn(argv[4]); if (ascii_name == NULL) { fprintf(stderr, "Cannot transform IDN domain name.\n"); goto fail; } dname = knot_dname_from_str(ascii_name); free(ascii_name); } else { dname = knot_dname_from_str(argv[4]); } if (dname == NULL) { fprintf(stderr, "Cannot parse domain name.\n"); goto fail; } result = knot_nsec3_hash(&nsec3_params, dname, knot_dname_size(dname), &digest, &digest_size); if (result != KNOT_EOK) { fprintf(stderr, "Cannot compute hash: %s\n", knot_strerror(result)); goto fail; } b32_length = base32hex_encode_alloc(digest, digest_size, &b32_digest); if (b32_length < 0) { fprintf(stderr, "Cannot encode computed hash: %s\n", knot_strerror(b32_length)); goto fail; } exit_code = 0; printf("%.*s (salt=%s, hash=%d, iterations=%d)\n", b32_length, b32_digest, argv[1], nsec3_params.algorithm, nsec3_params.iterations); fail: knot_nsec3param_free(&nsec3_params); knot_dname_free(&dname, NULL); free(digest); free(b32_digest); return exit_code; }
} return 0; /* Not found, try all. */ } /* @internal We don't need to deal with locale here */ KR_CONST static inline bool isletter(unsigned chr) { return (chr | 0x20 /* tolower */) - 'a' <= 'z' - 'a'; } /* Randomize QNAME letter case. * This adds 32 bits of randomness at maximum, but that's more than an average domain name length. * https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00 */ static void randomized_qname_case(knot_dname_t * restrict qname, uint32_t secret) { assert(qname); const int len = knot_dname_size(qname) - 2; /* Skip first, last label. */ for (int i = 0; i < len; ++i) { if (isletter(*++qname)) { *qname ^= ((secret >> (i & 31)) & 1) * 0x20; } } } /** Invalidate current NS/addr pair. */ static int invalidate_ns(struct kr_rplan *rplan, struct kr_query *qry) { if (qry->ns.addr[0].ip.sa_family != AF_UNSPEC) { uint8_t *addr = kr_nsrep_inaddr(qry->ns.addr[0]); size_t addr_len = kr_nsrep_inaddr_len(qry->ns.addr[0]); /* @warning _NOT_ thread-safe */ static knot_rdata_t rdata_arr[RDATA_ARR_MAX];
static MDB_val pack_key(const knot_dname_t *name) { MDB_val key = { knot_dname_size(name), (void *)name }; return key; }